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 command: scripts/tests.sh --junit_report test_results
- run_regressions: &run_regressions - run_regressions: &run_regressions
name: Regression tests 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 - solc_artifact: &solc_artifact
path: build/solc/solc path: build/solc/solc
destination: solc destination: solc
@ -43,12 +45,14 @@ defaults:
- ossfuzz_artifacts: &ossfuzz_artifacts - ossfuzz_artifacts: &ossfuzz_artifacts
root: build root: build
paths: paths:
- test/tools/ossfuzz/solc_opt_ossfuzz
- test/tools/ossfuzz/solc_noopt_ossfuzz
- test/tools/ossfuzz/const_opt_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/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_diff_ossfuzz
- test/tools/ossfuzz/yul_proto_ossfuzz
version: 2 version: 2
jobs: jobs:
@ -134,6 +138,24 @@ jobs:
command: | command: |
test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js 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: build_x86_linux:
docker: docker:
- image: buildpack-deps:bionic - image: buildpack-deps:bionic
@ -337,6 +359,7 @@ jobs:
ulimit -a ulimit -a
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
ulimit -s 16384 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 build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test
- run: - run:
name: Run commandline tests with ASAN name: Run commandline tests with ASAN
@ -344,6 +367,7 @@ jobs:
ulimit -a ulimit -a
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
ulimit -s 16384 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 test/cmdlineTests.sh
- store_test_results: - store_test_results:
path: test_results/ path: test_results/
@ -416,11 +440,11 @@ jobs:
build_x86_linux_ossfuzz: build_x86_linux_ossfuzz:
docker: docker:
- image: buildpack-deps:cosmic - image: buildpack-deps:disco
environment: environment:
TERM: xterm TERM: xterm
CC: /usr/bin/clang-7 CC: /usr/bin/clang-8
CXX: /usr/bin/clang++-7 CXX: /usr/bin/clang++-8
CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
steps: steps:
- checkout - checkout
@ -428,16 +452,18 @@ jobs:
name: Install build dependencies name: Install build dependencies
command: | command: |
apt-get -qq update 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_lpm.sh
./scripts/install_libfuzzer.sh ./scripts/install_libfuzzer.sh
# Install evmone and dependencies (intx and ethash)
./scripts/install_evmone.sh
- run: *setup_prerelease_commit_hash - run: *setup_prerelease_commit_hash
- run: *run_build_ossfuzz - run: *run_build_ossfuzz
- persist_to_workspace: *ossfuzz_artifacts - persist_to_workspace: *ossfuzz_artifacts
test_x86_ossfuzz_regression: test_x86_ossfuzz_regression:
docker: docker:
- image: buildpack-deps:cosmic - image: buildpack-deps:disco
environment: environment:
TERM: xterm TERM: xterm
steps: steps:
@ -448,13 +474,11 @@ jobs:
name: Install dependencies name: Install dependencies
command: | command: |
apt-get -qq update 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 ./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: mkdir -p test_results
- run: *run_regressions - run: *run_regressions
- store_test_results:
path: test_results/
- store_artifacts: - store_artifacts:
path: test_results/ path: test_results/
destination: test_results/ destination: test_results/
@ -471,14 +495,6 @@ workflows:
<<: *build_on_tags <<: *build_on_tags
requires: requires:
- build_emscripten - 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: *build_on_tags
- build_x86_linux_cxx17: *build_on_tags - build_x86_linux_cxx17: *build_on_tags
- build_x86_clang7_asan: *build_on_tags - build_x86_clang7_asan: *build_on_tags
@ -521,8 +537,13 @@ workflows:
<<: *build_on_tags <<: *build_on_tags
requires: requires:
- build_emscripten - build_emscripten
- test_emscripten_external_colony:
<<: *build_on_tags
requires:
- build_emscripten
- build_x86_linux_ossfuzz: *build_on_tags - build_x86_linux_ossfuzz: *build_on_tags
- test_x86_ossfuzz_regression: - test_x86_ossfuzz_regression:
<<: *build_on_tags <<: *build_on_tags
requires: requires:
- build_x86_linux_ossfuzz - build_x86_linux_ossfuzz

View File

@ -33,7 +33,7 @@ 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. Please also mention any command line flags that are necessary for triggering the bug.
Provide as much information as necessary to reproduce the bug. Provide as much information as necessary to reproduce the bug.
``` ```solidity
// Some *minimal* Solidity source code to reproduce the bug. // Some *minimal* Solidity source code to reproduce the bug.
// ... // ...
``` ```

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.5.8") set(PROJECT_VERSION "0.5.9")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
option(LLL "Build LLL" OFF) 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) ### 0.5.8 (2019-04-30)
Important Bugfixes: 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. * 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: 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: 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. * 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_COMPILER="${ETH_BUILD_COMPILER}"
-DETH_BUILD_PLATFORM="${ETH_BUILD_PLATFORM}" -DETH_BUILD_PLATFORM="${ETH_BUILD_PLATFORM}"
-DPROJECT_VERSION="${PROJECT_VERSION}" -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" -P "${ETH_SCRIPTS_DIR}/buildinfo.cmake"
) )
include_directories("${PROJECT_BINARY_DIR}/include") include_directories("${PROJECT_BINARY_DIR}/include")

View File

@ -144,7 +144,13 @@ else ()
endif () endif ()
if (SANITIZE) 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() endif()
# Code coverage support. # Code coverage support.

View File

@ -1,6 +1,9 @@
#pragma once #pragma once
#define ETH_PROJECT_VERSION "@PROJECT_VERSION@" #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 SOL_COMMIT_HASH "@SOL_COMMIT_HASH@"
#define ETH_BUILD_TYPE "@ETH_BUILD_TYPE@" #define ETH_BUILD_TYPE "@ETH_BUILD_TYPE@"
#define ETH_BUILD_OS "@ETH_BUILD_OS@" #define ETH_BUILD_OS "@ETH_BUILD_OS@"

View File

@ -67,7 +67,7 @@ jsoncpp:
license you like. license you like.
scanner/token: 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: code originating from the V8 project licensed under the following terms:
Copyright 2006-2012, the V8 project authors. All rights reserved. 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; pragma solidity >=0.4.16 <0.7.0;
contract Foo { contract Foo {
function bar(bytes3[2] 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 baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function sam(bytes memory, bool, uint[] memory) public pure {} function sam(bytes memory, bool, uint[] memory) public pure {}
} }
@ -485,12 +486,13 @@ For example,
pragma solidity >=0.5.0 <0.7.0; pragma solidity >=0.5.0 <0.7.0;
contract Test { contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; } constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b); event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b); event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); } function foo(uint a) public { emit Event(a, b); }
bytes32 b; bytes32 b;
} }
would result in the JSON: would result in the JSON:
@ -533,11 +535,12 @@ As an example, the code
pragma solidity >=0.4.19 <0.7.0; pragma solidity >=0.4.19 <0.7.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
contract Test { contract Test {
struct S { uint a; uint[] b; T[] c; } struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; } struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public; function f(S memory s, T memory t, uint a) public;
function g() public returns (S memory s, T memory t, uint a); function g() public returns (S memory s, T memory t, uint a);
} }
would result in the JSON: would result in the JSON:

View File

@ -104,47 +104,48 @@ efficient code, for example:
pragma solidity >=0.4.16 <0.7.0; pragma solidity >=0.4.16 <0.7.0;
library VectorSum { library VectorSum {
// This function is less efficient because the optimizer currently fails to // This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access. // 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) 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. // 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 // 0x20 needs to be added to an array because the first slot contains the
// array length. // 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) { for (uint i = 0; i < _data.length; ++i) {
assembly { 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. // 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 { assembly {
// Load the length (first 32 bytes) // Load the length (first 32 bytes)
let len := mload(_data) let len := mload(_data)
// Skip over the length field. // Skip over the length field.
// //
// Keep temporary variable so it can be incremented in place. // Keep temporary variable so it can be incremented in place.
// //
// NOTE: incrementing _data would result in an unusable // NOTE: incrementing _data would result in an unusable
// _data variable after this assembly block // _data variable after this assembly block
let data := add(_data, 0x20) let data := add(_data, 0x20)
// Iterate until the bound is not met. // Iterate until the bound is not met.
for for
{ let end := add(data, mul(len, 0x20)) } { let end := add(data, mul(len, 0x20)) }
lt(data, end) lt(data, end)
{ data := add(data, 0x20) } { data := add(data, 0x20) }
{ {
o_sum := add(o_sum, mload(data)) 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; pragma solidity >=0.4.16 <0.7.0;
contract C { contract C {
function f(uint x) public pure returns (uint y) { function f(uint x) public pure returns (uint y) {
y = 1; y = 1;
for (uint i = 0; i < x; i++) for (uint i = 0; i < x; i++)
y = 2 * y; y = 2 * y;
} }
} }
The following assembly will be generated:: 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", "name": "UninitializedFunctionPointerInConstructor",
"summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.", "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": { "0.4.16": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -462,6 +463,7 @@
}, },
"0.4.17": { "0.4.17": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -473,6 +475,7 @@
}, },
"0.4.18": { "0.4.18": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -483,6 +486,7 @@
}, },
"0.4.19": { "0.4.19": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x", "ABIEncoderV2PackedStorage_0.4.x",
@ -510,6 +514,7 @@
}, },
"0.4.20": { "0.4.20": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x", "ABIEncoderV2PackedStorage_0.4.x",
@ -521,6 +526,7 @@
}, },
"0.4.21": { "0.4.21": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x", "ABIEncoderV2PackedStorage_0.4.x",
@ -532,6 +538,7 @@
}, },
"0.4.22": { "0.4.22": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x", "ABIEncoderV2PackedStorage_0.4.x",
@ -543,6 +550,7 @@
}, },
"0.4.23": { "0.4.23": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x", "ABIEncoderV2PackedStorage_0.4.x",
@ -553,6 +561,7 @@
}, },
"0.4.24": { "0.4.24": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x", "ABIEncoderV2PackedStorage_0.4.x",
@ -563,6 +572,7 @@
}, },
"0.4.25": { "0.4.25": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x" "ABIEncoderV2PackedStorage_0.4.x"
@ -570,7 +580,9 @@
"released": "2018-09-12" "released": "2018-09-12"
}, },
"0.4.26": { "0.4.26": {
"bugs": [], "bugs": [
"DynamicConstructorArgumentsClippedABIV2"
],
"released": "2019-04-29" "released": "2019-04-29"
}, },
"0.4.3": { "0.4.3": {
@ -677,6 +689,7 @@
}, },
"0.5.0": { "0.5.0": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage" "ABIEncoderV2PackedStorage"
@ -685,6 +698,7 @@
}, },
"0.5.1": { "0.5.1": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage" "ABIEncoderV2PackedStorage"
@ -693,6 +707,7 @@
}, },
"0.5.2": { "0.5.2": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage" "ABIEncoderV2PackedStorage"
@ -701,6 +716,7 @@
}, },
"0.5.3": { "0.5.3": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage" "ABIEncoderV2PackedStorage"
@ -709,6 +725,7 @@
}, },
"0.5.4": { "0.5.4": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage" "ABIEncoderV2PackedStorage"
@ -717,6 +734,7 @@
}, },
"0.5.5": { "0.5.5": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage", "ABIEncoderV2PackedStorage",
@ -727,6 +745,7 @@
}, },
"0.5.6": { "0.5.6": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage", "ABIEncoderV2PackedStorage",
@ -736,13 +755,20 @@
}, },
"0.5.7": { "0.5.7": {
"bugs": [ "bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries" "IncorrectEventSignatureInLibraries"
], ],
"released": "2019-03-26" "released": "2019-03-26"
}, },
"0.5.8": { "0.5.8": {
"bugs": [], "bugs": [
"DynamicConstructorArgumentsClippedABIV2"
],
"released": "2019-04-30" "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; pragma solidity >=0.4.22 <0.7.0;
contract OwnedToken { contract OwnedToken {
// `TokenCreator` is a contract type that is defined below. // `TokenCreator` is a contract type that is defined below.
// It is fine to reference it as long as it is not used // 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 { contract TokenCreator {
function createToken(bytes32 name) function createToken(bytes32 name)
public public
returns (OwnedToken tokenAddress) returns (OwnedToken tokenAddress)
{ {
// Create a new `Token` contract and return its address. // Create a new `Token` contract and return its address.
// From the JavaScript side, the return type is // 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 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 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 When a contract inherits from other contracts, only a single
contract is created on the blockchain, and the code from all the base contracts 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 The general inheritance system is very similar to
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_, `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; pragma solidity >=0.5.0 <0.7.0;
contract owned {
contract Owned {
constructor() public { owner = msg.sender; } constructor() public { owner = msg.sender; }
address payable owner; address payable owner;
} }
// Use `is` to derive from another contract. Derived // Use `is` to derive from another contract. Derived
// contracts can access all non-private members including // contracts can access all non-private members including
// internal functions and state variables. These cannot be // internal functions and state variables. These cannot be
// accessed externally via `this`, though. // accessed externally via `this`, though.
contract mortal is owned { contract Mortal is Owned {
function kill() public { function kill() public {
if (msg.sender == owner) selfdestruct(owner); if (msg.sender == owner) selfdestruct(owner);
} }
} }
// These abstract contracts are only provided to make the // These abstract contracts are only provided to make the
// interface known to the compiler. Note the function // interface known to the compiler. Note the function
// without body. If a contract does not implement all // 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); function lookup(uint id) public returns (address adr);
} }
contract NameReg { contract NameReg {
function register(bytes32 name) public; function register(bytes32 name) public;
function unregister() public; function unregister() public;
} }
// Multiple inheritance is possible. Note that `owned` is // Multiple inheritance is possible. Note that `owned` is
// also a base class of `mortal`, yet there is only a single // also a base class of `mortal`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++). // instance of `owned` (as for virtual inheritance in C++).
contract named is owned, mortal { contract Named is Owned, Mortal {
constructor(bytes32 name) public { constructor(bytes32 name) public {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name); NameReg(config.lookup(1)).register(name);
@ -73,22 +80,23 @@ Details are given in the following example.
NameReg(config.lookup(1)).unregister(); NameReg(config.lookup(1)).unregister();
// It is still possible to call a specific // It is still possible to call a specific
// overridden function. // overridden function.
mortal.kill(); Mortal.kill();
} }
} }
} }
// If a constructor takes an argument, it needs to be // If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at // provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)). // the constructor of the derived contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") { contract PriceFeed is Owned, Mortal, Named("GoldFeed") {
function updateInfo(uint newInfo) public { function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo; 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 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; pragma solidity >=0.4.22 <0.7.0;
library Set { library Set {
// We define a new struct datatype that will be used to // We define a new struct datatype that will be used to
// hold its data in the calling contract. // hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; } struct Data { mapping(uint => bool) flags; }
// Note that the first parameter is of type "storage // Note that the first parameter is of type "storage
// reference" and thus only its storage address and not // reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a // its contents is passed as part of the call. This is a
// special feature of library functions. It is idiomatic // special feature of library functions. It is idiomatic
// to call the first parameter `self`, if the function can // to call the first parameter `self`, if the function can
// be seen as a method of that object. // be seen as a method of that object.
function insert(Data storage self, uint value) function insert(Data storage self, uint value)
public public
returns (bool) returns (bool)
{ {
if (self.flags[value]) if (self.flags[value])
return false; // already there return false; // already there
self.flags[value] = true; self.flags[value] = true;
return true; return true;
} }
function remove(Data storage self, uint value) function remove(Data storage self, uint value)
public public
returns (bool) returns (bool)
{ {
if (!self.flags[value]) if (!self.flags[value])
return false; // not there return false; // not there
self.flags[value] = false; self.flags[value] = false;
return true; return true;
} }
function contains(Data storage self, uint value) function contains(Data storage self, uint value)
public public
view view
returns (bool) returns (bool)
{ {
return self.flags[value]; return self.flags[value];
} }
} }
contract C { contract C {
Set.Data knownValues; Set.Data knownValues;

View File

@ -43,7 +43,7 @@ For state variables, ``external`` is not possible.
.. note:: .. note::
Everything that is inside a contract is visible to Everything that is inside a contract is visible to
all observers external to the blockchain. Making something ``private`` 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 the information, but it will still be visible to the
whole world outside of the blockchain. 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``. ``--no-ipc`` disables those that require ``aleth``.
If you want to run the ipc tests (that test the semantics of the generated code), 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``. To run the actual tests, use: ``./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc``.

View File

@ -4,10 +4,18 @@
Modular Contracts Modular Contracts
***************** *****************
A modular approach to building your contracts helps you prevent overflow risks A modular approach to building your contracts helps you reduce the complexity
and unexpected tokens. In the example below, the contract uses the ``move`` method 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 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.* *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 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 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 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 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 Docker
====== ======
We provide up to date docker builds for the compiler. The ``stable`` Docker images of Solidity builds are available using the ``solc`` image from the ``ethereum`` organisation.
repository contains released versions while the ``nightly`` Use the ``stable`` tag for the latest released version, and ``nightly`` for potentially unstable changes in the develop branch.
repository contains 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 .. code-block:: bash
docker run ethereum/solc:stable --version docker run ethereum/solc:stable --help
Currently, the docker image only contains the compiler executable, You can also specify release build versions in the tag, for example, for the 0.5.4 release.
so you have to do some additional work to link in the source and
output directories. .. 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 Binary Packages
=============== ===============

View File

@ -31,10 +31,9 @@ Storage Example
} }
} }
The first line simply tells that the source code is written for The first line tells you that the source code is written for
Solidity version 0.4.0 or anything newer that does not break functionality Solidity version 0.4.0, or a newer version of the language up to, but not including version 0.7.0.
(up to, but not including, version 0.7.0). This is to ensure that the This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently.
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 :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>`_). 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 data (its *state*) that resides at a specific address on the Ethereum
blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of 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 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 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. or retrieve the value of the variable.
To access a state variable, you do not need the prefix ``this.`` as is common in 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 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 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 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 this number. Anyone could call ``set`` again with a different value
and overwrite your number, but the number will still be stored in the history and overwrite your number, but the number is still stored in the history
of the blockchain. Later, we will see how you can impose access restrictions of the blockchain. Later, you will see how you can impose access restrictions
so that only you can alter the number. so that only you can alter the number.
.. note:: .. note::
@ -64,7 +63,7 @@ so that only you can alter the number.
.. warning:: .. warning::
Be careful with using Unicode text, as similar looking (or even identical) characters can 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 .. 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, 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 `CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might
contain more keys (see below) and the beginning of that 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 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 encoding. The current version of the Solidity compiler usually adds the following
to the end of the deployed bytecode:: 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 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. 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:: .. note::
The CBOR mapping can also contain other keys, so it is better to fully 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 For example, if any experimental features that affect code generation
are used, the mapping will also contain ``"experimental": true``. 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:: .. note::
The compiler currently uses the "swarm version 0" hash of the metadata, 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 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 add additional data to this CBOR structure, so the
best option is to use a proper CBOR parser. 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. you should be more careful.
This section will list some pitfalls and general security recommendations but 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 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 have a bug. A list of some publicly known security-relevant bugs of the compiler
can be found in the can be found in the
@ -31,6 +32,10 @@ Solidity compiler.
As always, with open source documentation, please help us extend this section As always, with open source documentation, please help us extend this section
(especially, some examples would not hurt)! (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 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 give you some unexpected behaviour and allows you to bypass some security
features of the compiler, so be sure to test that the features of the compiler, so be sure to test that the
result is what you want! Take the following example where you are converting 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); uint x = uint(y);
At the end of this code snippet, ``x`` will have the value ``0xfffff..fd`` (64 hex 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. ``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 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. 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. 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. 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 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. storage, you receive a ``Value must be an lvalue`` error.
**push**: **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. 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**: **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:: .. warning::
If you use ``.length--`` on an empty array, it causes an underflow and 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 storage is assumed to be zero-initialised, while decreasing
the length has at least linear cost (but in most cases worse than linear), the length has at least linear cost (but in most cases worse than linear),
because it includes explicitly clearing the removed 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:: .. note::
It is not yet possible to use arrays of arrays in external functions 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 campaignID = numCampaigns++; // campaignID is return variable
// Creates new struct in memory and copies it to storage. // Creates new struct in memory and copies it to storage.
// We leave out the mapping type, because it is not valid in memory. // 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. // are always omitted, because they cannot be enumerated.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
} }

View File

@ -194,7 +194,7 @@ Mathematical and Cryptographic Functions
the ecrecover function remained unchanged. the ecrecover function remained unchanged.
This is usually not a problem unless you require signatures to be unique or 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:: .. 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`` The following example program assumes that the EVM opcodes ``mul``, ``div``
and ``mod`` are available either natively or as functions and computes exponentiation. 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:: .. code::
{ {
function power(base:u256, exponent:u256) -> result:u256 function power(base, exponent) -> result
{ {
switch exponent switch exponent
case 0:u256 { result := 1:u256 } case 0 { result := 1 }
case 1:u256 { result := base } case 1 { result := base }
default default
{ {
result := power(mul(base, base), div(exponent, 2:u256)) result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2:u256) switch mod(exponent, 2)
case 1:u256 { result := mul(base, result) } case 1 { result := mul(base, result) }
} }
} }
} }
@ -72,10 +73,10 @@ and ``add`` to be available.
.. code:: .. code::
{ {
function power(base:u256, exponent:u256) -> result:u256 function power(base, exponent) -> result
{ {
result := 1:u256 result := 1
for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) } for { let i := 0 } lt(i, exponent) { i := add(i, 1) }
{ {
result := mul(result, base) result := mul(result, base)
} }
@ -593,15 +594,21 @@ An example Yul Object is shown below:
// are in scope without nested access. // are in scope without nested access.
object "Contract1" { object "Contract1" {
code { code {
function allocate(size) -> ptr {
ptr := mload(0x40)
if iszero(ptr) { ptr := 0x60 }
mstore(0x40, add(ptr, size))
}
// first create "runtime.Contract2" // first create "runtime.Contract2"
let size = datasize("runtime.Contract2") let size := datasize("runtime.Contract2")
let offset = allocate(size) let offset := allocate(size)
// This will turn into a memory->memory copy for eWASM and // This will turn into a memory->memory copy for eWASM and
// a codecopy for EVM // a codecopy for EVM
datacopy(offset, dataoffset("runtime.Contract2"), size) datacopy(offset, dataoffset("runtime.Contract2"), size)
// constructor parameter is a single number 0x1234 // constructor parameter is a single number 0x1234
mstore(add(offset, size), 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 // now return the runtime object (this is
// constructor code) // constructor code)
@ -617,16 +624,22 @@ An example Yul Object is shown below:
object "runtime" { object "runtime" {
code { code {
function allocate(size) -> ptr {
ptr := mload(0x40)
if iszero(ptr) { ptr := 0x60 }
mstore(0x40, add(ptr, size))
}
// runtime code // runtime code
let size = datasize("Contract2") let size := datasize("Contract2")
let offset = allocate(size) let offset := allocate(size)
// This will turn into a memory->memory copy for eWASM and // This will turn into a memory->memory copy for eWASM and
// a codecopy for EVM // a codecopy for EVM
datacopy(offset, dataoffset("Contract2"), size) datacopy(offset, dataoffset("Contract2"), size)
// constructor parameter is a single number 0x1234 // constructor parameter is a single number 0x1234
mstore(add(offset, size), 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, // 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 {
// code here ... // code here ...
} }
} }
data "Table1" hex"4123" data "Table1" hex"4123"
} }
} }
} }

View File

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

View File

@ -91,6 +91,20 @@ inline u256 s2u(s256 _u)
return u256(c_end + _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) inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes)
{ {
std::ostringstream ss; std::ostringstream ss;

View File

@ -111,7 +111,7 @@ enum class HexCase
/// @example toHex("A\x69") == "4169" /// @example toHex("A\x69") == "4169"
std::string toHex(bytes const& _data, HexPrefix _prefix = HexPrefix::DontAdd, HexCase _case = HexCase::Lower); 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 /// @example fromHex('A') == 10 && fromHex('f') == 15 && fromHex('5') == 5
int fromHex(char _i, WhenError _throw); 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{}; 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 /// Convenience function for conversion of a u256 to hex
inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd) 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 needle;
return ""; 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(InvalidAddress);
DEV_SIMPLE_EXCEPTION(BadHexCharacter); DEV_SIMPLE_EXCEPTION(BadHexCharacter);
DEV_SIMPLE_EXCEPTION(FileError); DEV_SIMPLE_EXCEPTION(FileError);
DEV_SIMPLE_EXCEPTION(DataTooLong);
// error information to be added to exceptions // error information to be added to exceptions
using errinfo_invalidSymbol = boost::error_info<struct tag_invalidSymbol, char>; 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; 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. /// Formats large numbers to be easily readable by humans.
/// Returns decimal representation for smaller numbers; hex for large numbers. /// Returns decimal representation for smaller numbers; hex for large numbers.
/// "Special" numbers, powers-of-two and powers-of-two minus 1, are returned in /// "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 std;
using namespace dev; using namespace dev;
Whiskers::Whiskers(string const& _template): Whiskers::Whiskers(string _template):
m_template(_template) m_template(move(_template))
{ {
} }
Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value) Whiskers& Whiskers::operator()(string _parameter, string _value)
{ {
assertThrow( checkParameterUnknown(_parameter);
m_parameters.count(_parameter) == 0, m_parameters[move(_parameter)] = move(_value);
WhiskersError,
_parameter + " already set."
);
assertThrow(
m_listParameters.count(_parameter) == 0,
WhiskersError,
_parameter + " already set as list parameter."
);
m_parameters[_parameter] = _value;
return *this; return *this;
} }
Whiskers& Whiskers::operator ()( Whiskers& Whiskers::operator()(string _parameter, bool _value)
string const& _listParameter, {
vector<map<string, string>> const& _values checkParameterUnknown(_parameter);
m_conditions[move(_parameter)] = _value;
return *this;
}
Whiskers& Whiskers::operator()(
string _listParameter,
vector<map<string, string>> _values
) )
{ {
assertThrow( checkParameterUnknown(_listParameter);
m_listParameters.count(_listParameter) == 0, m_listParameters[move(_listParameter)] = move(_values);
WhiskersError,
_listParameter + " already set."
);
assertThrow(
m_parameters.count(_listParameter) == 0,
WhiskersError,
_listParameter + " already set as value parameter."
);
m_listParameters[_listParameter] = _values;
return *this; return *this;
} }
string Whiskers::render() const 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 Whiskers::replace(
string const& _template, string const& _template,
StringMap const& _parameters, StringMap const& _parameters,
map<string, bool> const& _conditions,
map<string, vector<StringMap>> const& _listParameters map<string, vector<StringMap>> const& _listParameters
) )
{ {
using namespace boost; 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 return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string
{ {
string tagName(_match[1]); string tagName(_match[1]);
string listName(_match[2]);
string conditionName(_match[4]);
if (!tagName.empty()) if (!tagName.empty())
{ {
assertThrow( assertThrow(
@ -99,20 +108,32 @@ string Whiskers::replace(
); );
return _parameters.at(tagName); return _parameters.at(tagName);
} }
else else if (!listName.empty())
{ {
string listName(_match[2]);
string templ(_match[3]); string templ(_match[3]);
assertThrow(!listName.empty(), WhiskersError, "");
assertThrow( assertThrow(
_listParameters.count(listName), _listParameters.count(listName),
WhiskersError, "List parameter " + listName + " not set." WhiskersError, "List parameter " + listName + " not set."
); );
string replacement; string replacement;
for (auto const& parameters: _listParameters.at(listName)) for (auto const& parameters: _listParameters.at(listName))
replacement += replace(templ, joinMaps(_parameters, parameters)); replacement += replace(templ, joinMaps(_parameters, parameters), _conditions);
return replacement; 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); DEV_SIMPLE_EXCEPTION(WhiskersError);
/// /**
/// Moustache-like templates. * Moustache-like templates.
/// *
/// Usage: * Usage:
/// std::vector<std::map<std::string, std::string>> listValues(2); * std::vector<std::map<std::string, std::string>> listValues(2);
/// listValues[0]["k"] = "key1"; * listValues[0]["k"] = "key1";
/// listValues[0]["v"] = "value1"; * listValues[0]["v"] = "value1";
/// listValues[1]["k"] = "key2"; * listValues[1]["k"] = "key2";
/// listValues[1]["v"] = "value2"; * listValues[1]["v"] = "value2";
/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>") * auto s = Whiskers("<?c><p1><!c>y</c>\n<#list><k> -> <v>\n</list>")
/// ("p1", "HEAD") * ("p1", "HEAD")
/// ("list", listValues) * ("c", true)
/// .render(); * ("list", listValues)
/// * .render();
/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" *
/// * results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
/// Note that lists cannot themselves contain lists - this would be a future feature. *
* 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 class Whiskers
{ {
public: public:
using StringMap = std::map<std::string, std::string>; using StringMap = std::map<std::string, std::string>;
using StringListMap = std::map<std::string, std::vector<StringMap>>; 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>. /// Sets a single regular parameter, <paramName>.
Whiskers& operator()(std::string const& _parameter, std::string const& _value); 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>. /// Sets a list parameter, <#listName> </listName>.
Whiskers& operator()( Whiskers& operator()(
std::string const& _listParameter, std::string _listParameter,
std::vector<StringMap> const& _values std::vector<StringMap> _values
); );
std::string render() const; std::string render() const;
private: private:
void checkParameterUnknown(std::string const& _parameter);
static std::string replace( static std::string replace(
std::string const& _template, std::string const& _template,
StringMap const& _parameters, StringMap const& _parameters,
std::map<std::string, bool> const& _conditions,
StringListMap const& _listParameters = StringListMap() StringListMap const& _listParameters = StringListMap()
); );
@ -81,6 +99,7 @@ private:
std::string m_template; std::string m_template;
StringMap m_parameters; StringMap m_parameters;
std::map<std::string, bool> m_conditions;
StringListMap m_listParameters; 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 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 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 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."); 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 AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
{ {
bytes data = toBigEndian(m_value); bytes data = toBigEndian(m_value);
assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
AssemblyItems actualCopyRoutine = copyRoutine(); AssemblyItems actualCopyRoutine = copyRoutine();
actualCopyRoutine[4] = _assembly.newData(data); actualCopyRoutine[4] = _assembly.newData(data);
return actualCopyRoutine; return actualCopyRoutine;
@ -159,15 +160,25 @@ AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
AssemblyItems const& CodeCopyMethod::copyRoutine() AssemblyItems const& CodeCopyMethod::copyRoutine()
{ {
AssemblyItems static copyRoutine{ AssemblyItems static copyRoutine{
// constant to be reused 3+ times
u256(0), u256(0),
// back up memory
// mload(0)
Instruction::DUP1, Instruction::DUP1,
Instruction::MLOAD, // back up memory Instruction::MLOAD,
// codecopy(0, <offset>, 32)
u256(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::DUP4,
Instruction::CODECOPY, Instruction::CODECOPY,
// mload(0)
Instruction::DUP2, Instruction::DUP2,
Instruction::MLOAD, Instruction::MLOAD,
// restore original memory
Instruction::SWAP2, Instruction::SWAP2,
Instruction::MSTORE Instruction::MSTORE
}; };
@ -211,11 +222,16 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
if (lowerPart != 0) if (lowerPart != 0)
newRoutine += findRepresentation(u256(abs(lowerPart))); newRoutine += findRepresentation(u256(abs(lowerPart)));
if (m_params.evmVersion.hasBitwiseShifting()) if (m_params.evmVersion.hasBitwiseShifting())
newRoutine += AssemblyItems{u256(1), u256(bits), Instruction::SHL}; {
newRoutine += findRepresentation(upperPart);
newRoutine += AssemblyItems{u256(bits), Instruction::SHL};
}
else else
{
newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP}; newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
if (upperPart != 1) if (upperPart != 1)
newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
}
if (lowerPart > 0) if (lowerPart > 0)
newRoutine += AssemblyItems{Instruction::ADD}; newRoutine += AssemblyItems{Instruction::ADD};
else if (lowerPart < 0) 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)); 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 // simplificationRuleList below was split up into parts to prevent
// stack overflows in the JavaScript optimizer for emscripten builds // stack overflows in the JavaScript optimizer for emscripten builds
// that affected certain browser versions. // that affected certain browser versions.
@ -93,7 +100,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1(
{{Instruction::SHL, {A, B}}, [=]{ {{Instruction::SHL, {A, B}}, [=]{
if (A.d() > 255) if (A.d() > 255)
return u256(0); return u256(0);
return bigintShiftLeftWorkaround(B.d(), unsigned(A.d())); return shlWorkaround(B.d(), unsigned(A.d()));
}, false}, }, false},
{{Instruction::SHR, {A, B}}, [=]{ {{Instruction::SHR, {A, B}}, [=]{
if (A.d() > 255) if (A.d() > 255)
@ -365,6 +372,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
} }
} }
// Combine two SHL by constant
rules.push_back({ rules.push_back({
// SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X) // SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X)
{Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}}, {Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}},
@ -378,6 +386,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
false false
}); });
// Combine two SHR by constant
rules.push_back({ rules.push_back({
// SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X) // SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X)
{Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}}, {Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}},
@ -391,6 +400,93 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
false 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 = [=]() { std::function<bool()> feasibilityFunction = [=]() {
if (B.d() > 256) if (B.d() > 256)

View File

@ -136,7 +136,13 @@ bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item)
{ {
if (_item.type() != Operation) if (_item.type() != Operation)
return false; return false;
switch (_item.instruction()) else
return terminatesControlFlow(_item.instruction());
}
bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
{
switch (_instruction)
{ {
case Instruction::RETURN: case Instruction::RETURN:
case Instruction::SELFDESTRUCT: case Instruction::SELFDESTRUCT:
@ -202,6 +208,22 @@ bool SemanticInformation::movable(Instruction _instruction)
return true; 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) bool SemanticInformation::invalidatesMemory(Instruction _instruction)
{ {
switch (_instruction) switch (_instruction)

View File

@ -48,6 +48,7 @@ struct SemanticInformation
static bool isJumpInstruction(AssemblyItem const& _item); static bool isJumpInstruction(AssemblyItem const& _item);
static bool altersControlFlow(AssemblyItem const& _item); static bool altersControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(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 /// @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. /// the information in the current block header, memory, storage or stack.
static bool isDeterministic(AssemblyItem const& _item); 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, /// 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. /// cannot have any side-effects, but it can depend on a call-constant state of the blockchain.
static bool movable(Instruction _instruction); 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. /// @returns true if the given instruction modifies memory.
static bool invalidatesMemory(Instruction _instruction); static bool invalidatesMemory(Instruction _instruction);
/// @returns true if the given instruction modifies storage (even indirectly). /// @returns true if the given instruction modifies storage (even indirectly).

View File

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

View File

@ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount)
return get(); 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 string CharStream::lineAtPosition(int _position) const
{ {
// if _position points to \n, it returns the line before the \n // 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); 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 get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; }
char advanceAndGet(size_t _chars = 1); 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); 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; } 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 #pragma once
#include <libevmasm/Instruction.h>
#include <string> #include <string>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/operators.hpp> #include <boost/operators.hpp>
namespace langutil namespace langutil
{ {
@ -78,6 +81,8 @@ public:
bool hasCreate2() const { return *this >= constantinople(); } bool hasCreate2() const { return *this >= constantinople(); }
bool hasExtCodeHash() 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), /// Whether we have to retain the costs for the call opcode itself (false),
/// or whether we can just forward easily all remaining gas (true). /// or whether we can just forward easily all remaining gas (true).
bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); }
@ -90,5 +95,4 @@ private:
Version m_version = Version::Petersburg; 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); m_errorList.push_back(err);
} }
bool ErrorReporter::hasExcessiveErrors() const
{
return m_errorCount > c_maxErrorsAllowed;
}
bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
{ {
if (_type == Error::Type::Warning) if (_type == Error::Type::Warning)

View File

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

View File

@ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const
return m_scanner->peekNextToken(); return m_scanner->peekNextToken();
} }
std::string ParserBase::currentLiteral() const string ParserBase::currentLiteral() const
{ {
return m_scanner->currentLiteral(); return m_scanner->currentLiteral();
} }
@ -57,34 +57,83 @@ Token ParserBase::advance()
return m_scanner->next(); 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) void ParserBase::expectToken(Token _value, bool _advance)
{ {
Token tok = m_scanner->currentToken(); Token tok = m_scanner->currentToken();
if (tok != _value) if (tok != _value)
{ {
auto tokenName = [this](Token _token) string const expectedToken = ParserBase::tokenName(_value);
{ if (m_parserErrorRecovery)
if (_token == Token::Identifier) parserError("Expected " + expectedToken + " but got " + tokenName(tok));
return string("identifier"); else
else if (_token == Token::EOS) fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok));
return string("end of source"); // Do not advance so that recovery can sync or make use of the current token.
else if (TokenTraits::isReservedKeyword(_token)) // This is especially useful if the expected token
return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'"; // is the only one that is missing and is at the end of a construct.
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting // "{ ... ; }" is such an example.
{ // ^
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); _advance = false;
return string("'") + elemTypeName.toString() + "'";
}
else
return string("'") + TokenTraits::friendlyName(_token) + "'";
};
fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok));
} }
if (_advance) if (_advance)
m_scanner->next(); 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() void ParserBase::increaseRecursionDepth()
{ {
m_recursionDepth++; m_recursionDepth++;
@ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth()
m_recursionDepth--; 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) 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) 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 class ParserBase
{ {
public: 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(); } std::shared_ptr<CharStream> source() const { return m_scanner->charStream(); }
@ -62,10 +69,17 @@ protected:
///@{ ///@{
///@name Helper functions ///@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); 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 currentToken() const;
Token peekNextToken() const; Token peekNextToken() const;
std::string tokenName(Token _token);
std::string currentLiteral() const; std::string currentLiteral() const;
Token advance(); Token advance();
///@} ///@}
@ -77,16 +91,26 @@ protected:
/// Creates a @ref ParserError and annotates it with the current position and the /// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description. /// given @a _description.
void parserError(std::string const& _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 /// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description. Throws the FatalError. /// given @a _description. Throws the FatalError.
void fatalParserError(std::string const& _description); void fatalParserError(std::string const& _description);
void fatalParserError(SourceLocation const& _location, std::string const& _description);
std::shared_ptr<Scanner> m_scanner; std::shared_ptr<Scanner> m_scanner;
/// The reference to the list of errors and warning to add errors/warnings during parsing /// The reference to the list of errors and warning to add errors/warnings during parsing
ErrorReporter& m_errorReporter; ErrorReporter& m_errorReporter;
/// Current recursion depth during parsing. /// Current recursion depth during parsing.
size_t m_recursionDepth = 0; 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(); next();
} }
void Scanner::setPosition(size_t _offset)
{
m_char = m_source->setPosition(_offset);
scanToken();
next();
}
void Scanner::supportPeriodInIdentifier(bool _value) void Scanner::supportPeriodInIdentifier(bool _value)
{ {
m_supportPeriodInIdentifier = _value; m_supportPeriodInIdentifier = _value;

View File

@ -110,6 +110,9 @@ public:
/// @returns the next token and advances input /// @returns the next token and advances input
Token next(); 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 ///@name Information about the current token

View File

@ -21,10 +21,11 @@
*/ */
#include <libsolc/libsolc.h> #include <libsolc/libsolc.h>
#include <libdevcore/Common.h>
#include <libdevcore/JSON.h>
#include <libsolidity/interface/StandardCompiler.h> #include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/Version.h> #include <libsolidity/interface/Version.h>
#include <libyul/YulString.h>
#include <libdevcore/Common.h>
#include <libdevcore/JSON.h>
#include <string> #include <string>
@ -100,6 +101,9 @@ extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _
} }
extern void solidity_free() noexcept 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(); s_outputBuffer.clear();
} }
} }

View File

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

View File

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

View File

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

View File

@ -309,6 +309,19 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
"Arithmetic modulo zero." "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; return true;
} }

View File

@ -21,6 +21,9 @@
#include <libsolidity/ast/ExperimentalFeatures.h> #include <libsolidity/ast/ExperimentalFeatures.h>
#include <libsolidity/interface/Version.h> #include <libsolidity/interface/Version.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/AsmData.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <liblangutil/SemVerHandler.h> #include <liblangutil/SemVerHandler.h>
@ -254,6 +257,23 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation)
return true; 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&) bool SyntaxChecker::visit(PlaceholderStatement const&)
{ {
m_placeholderFound = true; m_placeholderFound = true;

View File

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

View File

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

View File

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

View File

@ -400,6 +400,21 @@ SourceUnit const& Scopable::sourceUnit() const
return dynamic_cast<SourceUnit const&>(*s); 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 string Scopable::sourceUnitName() const
{ {
return sourceUnit().annotation().path; return sourceUnit().annotation().path;

View File

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

View File

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

View File

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

View File

@ -2068,7 +2068,8 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
{ {
TypePointer type = variable->annotation().type; TypePointer type = variable->annotation().type;
solAssert(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()) if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
continue; continue;
members.emplace_back( members.emplace_back(

View File

@ -255,39 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
return suffix; 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( string ABIFunctions::abiEncodingFunction(
Type const& _from, Type const& _from,
Type const& _to, Type const& _to,
@ -609,7 +576,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
break; break;
case DataLocation::Storage: case DataLocation::Storage:
if (_from.baseType()->isValueType()) if (_from.baseType()->isValueType())
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
else else
templ("arrayElementAccess", "srcPtr"); templ("arrayElementAccess", "srcPtr");
break; break;
@ -806,7 +773,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
items[i]["inRange"] = "1"; items[i]["inRange"] = "1";
else else
items[i]["inRange"] = "0"; 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); templ("items", items);
return templ.render(); return templ.render();
@ -893,7 +860,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
previousSlotOffset = storageSlotOffset; previousSlotOffset = storageSlotOffset;
} }
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; members.back()["retrieveValue"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
} }
else 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) string ABIFunctions::calldataAccessFunction(Type const& _type)
{ {
solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), ""); solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), "");

View File

@ -128,15 +128,6 @@ private:
std::string toFunctionNameSuffix() const; 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 /// @returns the name of the ABI encoding function with the given type
/// and queues the generation of the function to the requested functions. /// and queues the generation of the function to the requested functions.
/// @param _fromStack if false, the input value was just loaded from storage /// @param _fromStack if false, the input value was just loaded from storage
@ -231,19 +222,6 @@ private:
/// Part of @a abiDecodingFunction for array types. /// Part of @a abiDecodingFunction for array types.
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); 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. /// @returns the name of a function that retrieves an element from calldata.
std::string calldataAccessFunction(Type const& _type); std::string calldataAccessFunction(Type const& _type);

View File

@ -33,6 +33,7 @@
#include <libyul/backends/evm/AsmCodeGen.h> #include <libyul/backends/evm/AsmCodeGen.h>
#include <libyul/backends/evm/EVMDialect.h> #include <libyul/backends/evm/EVMDialect.h>
#include <libyul/optimiser/Suite.h> #include <libyul/optimiser/Suite.h>
#include <libyul/optimiser/Metrics.h>
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
@ -382,7 +383,8 @@ void CompilerContext::appendInlineAssembly(
ErrorList errors; ErrorList errors;
ErrorReporter errorReporter(errors); ErrorReporter errorReporter(errors);
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--")); 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 #ifdef SOL_OUTPUT_ASM
cout << yul::AsmPrinter()(*parserResult) << endl; cout << yul::AsmPrinter()(*parserResult) << endl;
#endif #endif
@ -409,7 +411,7 @@ void CompilerContext::appendInlineAssembly(
analysisInfo, analysisInfo,
errorReporter, errorReporter,
boost::none, boost::none,
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), dialect,
identifierAccess.resolve identifierAccess.resolve
).analyze(*parserResult); ).analyze(*parserResult);
if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) if (!parserResult || !errorReporter.errors().empty() || !analyzerResult)
@ -419,8 +421,10 @@ void CompilerContext::appendInlineAssembly(
// so we essentially only optimize the ABI functions. // so we essentially only optimize the ABI functions.
if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
{ {
bool const isCreation = m_runtimeContext != nullptr;
yul::OptimiserSuite::run( yul::OptimiserSuite::run(
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), dialect,
yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment),
*parserResult, *parserResult,
analysisInfo, analysisInfo,
_optimiserSettings.optimizeStackAllocation, _optimiserSettings.optimizeStackAllocation,
@ -431,7 +435,7 @@ void CompilerContext::appendInlineAssembly(
analysisInfo, analysisInfo,
errorReporter, errorReporter,
boost::none, boost::none,
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), dialect,
identifierAccess.resolve identifierAccess.resolve
).analyze(*parserResult)) ).analyze(*parserResult))
reportError("Optimizer introduced error into inline assembly."); 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 // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
if (!_constructor.parameters().empty()) 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(); CompilerUtils(m_context).fetchFreeMemoryPointer();
if (argumentSize == 0) // CODESIZE returns the actual size of the code,
{ // which is the size of the generated code (``programSize``)
// argument size is dynamic, use CODESIZE to determine it // plus the constructor arguments added to the transaction payload.
m_context.appendProgramSize(); // program itself m_context.appendProgramSize();
// CODESIZE is program plus manually added arguments m_context << Instruction::CODESIZE << Instruction::SUB;
m_context << Instruction::CODESIZE << Instruction::SUB;
}
else
m_context << u256(argumentSize);
// stack: <memptr> <argument size> // stack: <memptr> <argument size>
m_context << Instruction::DUP1; m_context << Instruction::DUP1;
m_context.appendProgramSize(); m_context.appendProgramSize();
m_context << Instruction::DUP4 << Instruction::CODECOPY; m_context << Instruction::DUP4 << Instruction::CODECOPY;
m_context << Instruction::DUP2 << Instruction::ADD; // stack: <memptr> <argument size>
m_context << Instruction::DUP1; m_context << Instruction::DUP2 << Instruction::DUP2 << Instruction::ADD;
// stack: <memptr> <argument size> <mem end>
CompilerUtils(m_context).storeFreeMemoryPointer(); CompilerUtils(m_context).storeFreeMemoryPointer();
// stack: <memptr> // stack: <memptr> <argument size>
CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true); CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true);
} }
_constructor.accept(*this); _constructor.accept(*this);

View File

@ -206,6 +206,12 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
CompilerUtils(m_context).splitExternalFunctionType(false); CompilerUtils(m_context).splitExternalFunctionType(false);
cleaned = true; 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) if (!cleaned)
{ {
@ -288,7 +294,8 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
{ {
solAssert( solAssert(
_sourceType.category() == m_dataType->category(), _sourceType.category() == m_dataType->category(),
"Wrong type conversation for assignment."); "Wrong type conversation for assignment."
);
if (m_dataType->category() == Type::Category::Array) if (m_dataType->category() == Type::Category::Array)
{ {
m_context << Instruction::POP; // remove byte offset 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."); solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
for (auto const& member: structType.members(nullptr)) 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; TypePointer const& memberType = member.type;
if (memberType->category() == Type::Category::Mapping) if (!memberType->canLiveOutsideStorage())
continue; continue;
TypePointer sourceMemberType = sourceType.memberType(member.name); TypePointer sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage) 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 YulUtilFunctions::leftAlignFunction(Type const& _type)
{ {
string functionName = string("leftAlign_") + _type.identifier(); 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, // Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding! // the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name();
if (m_evmVersion.hasBitwiseShifting()) return m_functionCollector->createFunction(functionName, [&]() {
{ return
return m_functionCollector->createFunction(functionName, [&]() { Whiskers(R"(
return function <functionName>(value) -> newValue {
Whiskers(R"( newValue :=
function <functionName>(value) -> newValue { <?hasShifts>
newValue := shr(<numBits>, value) shr(<numBits>, value)
} <!hasShifts>
)") div(value, <multiplier>)
("functionName", functionName) </hasShifts>
("numBits", to_string(_numBits)) }
.render(); )")
}); ("functionName", functionName)
} ("hasShifts", m_evmVersion.hasBitwiseShifting())
else ("numBits", to_string(_numBits))
{ ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
return m_functionCollector->createFunction(functionName, [&]() { .render();
return });
Whiskers(R"( }
function <functionName>(value) -> newValue {
newValue := div(value, <multiplier>) string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
} {
)") solAssert(_numBytes <= 32, "");
("functionName", functionName) solAssert(_shiftBytes <= 32, "");
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) size_t numBits = _numBytes * 8;
.render(); 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() string YulUtilFunctions::roundUpFunction()
@ -257,74 +326,135 @@ string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits)
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, "");
string functionName = "checked_add_uint_" + to_string(_bits); string functionName = "checked_add_uint_" + to_string(_bits);
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
if (_bits < 256) return
return Whiskers(R"(
Whiskers(R"( function <functionName>(x, y) -> sum {
function <functionName>(x, y) -> sum { <?shortType>
let mask := <mask> let mask := <mask>
sum := add(and(x, mask), and(y, mask)) sum := add(and(x, mask), and(y, mask))
if and(sum, not(mask)) { revert(0, 0) } if and(sum, not(mask)) { revert(0, 0) }
} <!shortType>
)")
("functionName", functionName)
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
.render();
else
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
sum := add(x, y) sum := add(x, y)
if lt(sum, x) { revert(0, 0) } 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) ("functionName", functionName)
("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits))
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
.render(); .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 YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{ {
string functionName = "array_length_" + _type.identifier(); string functionName = "array_length_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
Whiskers w(R"( Whiskers w(R"(
function <functionName>(value) -> length { 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); w("functionName", functionName);
string body; w("dynamic", _type.isDynamicallySized());
if (!_type.isDynamicallySized()) if (!_type.isDynamicallySized())
body = "length := " + toCompactHexWithPrefix(_type.length()); w("length", toCompactHexWithPrefix(_type.length()));
else w("memory", _type.location() == DataLocation::Memory);
{ w("storage", _type.location() == DataLocation::Storage);
switch (_type.location()) w("byteArray", _type.isByteArray());
{ if (_type.isDynamicallySized())
case DataLocation::CallData: solAssert(
solAssert(false, "called regular array length function on calldata array"); _type.location() != DataLocation::CallData,
break; "called regular array length function on calldata array"
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);
return w.render(); return w.render();
}); });
} }
@ -338,20 +468,21 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
function <functionName>(length) -> size { function <functionName>(length) -> size {
// Make sure we can allocate memory without overflow // Make sure we can allocate memory without overflow
if gt(length, 0xffffffffffffffff) { revert(0, 0) } if gt(length, 0xffffffffffffffff) { revert(0, 0) }
size := <allocationSize> <?byteArray>
<addLengthSlot> // 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); w("functionName", functionName);
if (_type.isByteArray()) w("byteArray", _type.isByteArray());
// Round up w("dynamic", _type.isDynamicallySized());
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", "");
return w.render(); return w.render();
}); });
} }
@ -360,64 +491,30 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
{ {
string functionName = "array_dataslot_" + _type.identifier(); string functionName = "array_dataslot_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
switch (_type.location()) // 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
case DataLocation::Memory: // points to the data area.
if (_type.isDynamicallySized()) // This might change, if calldata arrays are stored in a single
return Whiskers(R"( // stack slot at some point.
function <functionName>(memPtr) -> dataPtr { return Whiskers(R"(
dataPtr := add(memPtr, 0x20) function <functionName>(ptr) -> data {
} data := ptr
)") <?dynamic>
("functionName", functionName) <?memory>
.render(); data := add(ptr, 0x20)
else </memory>
return Whiskers(R"( <?storage>
function <functionName>(memPtr) -> dataPtr { mstore(0, ptr)
dataPtr := memPtr data := keccak256(0, 0x20)
} </storage>
)") </dynamic>
("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();
} }
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, ""); solAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "array_nextElement_" + _type.identifier(); string functionName = "array_nextElement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { 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: solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
return Whiskers(R"( solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), "");
function <functionName>(memPtr) -> nextPtr { solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, "");
nextPtr := add(memPtr, 0x20) Whiskers templ(R"(
} function <functionName>(slot <key>) -> dataSlot {
)") mstore(0, <convertedKey>)
("functionName", functionName) mstore(0x20, slot)
.render(); dataSlot := keccak256(0, 0x40)
case DataLocation::Storage: }
return Whiskers(R"( )");
function <functionName>(slot) -> nextSlot { templ("functionName", functionName);
nextSlot := add(slot, 1) templ("key", _keyType.sizeOnStack() == 1 ? ", key" : "");
} if (_keyType.sizeOnStack() == 0)
)") templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()");
("functionName", functionName) else
.render(); templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)");
case DataLocation::CallData: return templ.render();
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, "");
} }
}); });
} }
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 YulUtilFunctions::allocationFunction()
{ {
string functionName = "allocateMemory"; string functionName = "allocateMemory";
@ -482,6 +710,9 @@ string YulUtilFunctions::allocationFunction()
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{ {
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to);
string functionName = string functionName =
"convert_" + "convert_" +
_from.identifier() + _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 YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
{ {
string result; string result;
@ -799,3 +1086,166 @@ string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_
} }
return result; 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 <memory>
#include <string> #include <string>
#include <vector>
namespace dev namespace dev
{ {
@ -34,6 +35,8 @@ namespace solidity
class Type; class Type;
class ArrayType; class ArrayType;
class MappingType;
class IntegerType;
/** /**
* Component that can generate various useful Yul functions. * Component that can generate various useful Yul functions.
@ -62,6 +65,10 @@ public:
/// Pads with zeros and might write more than exactly length. /// Pads with zeros and might write more than exactly length.
std::string copyToMemoryFunction(bool _fromCalldata); 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 /// @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. /// left-aligns it, usually for use in non-padded encoding.
std::string leftAlignFunction(Type const& _type); std::string leftAlignFunction(Type const& _type);
@ -69,12 +76,28 @@ public:
std::string shiftLeftFunction(size_t _numBits); std::string shiftLeftFunction(size_t _numBits);
std::string shiftRightFunction(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 /// @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. /// of 32 or the input if it is a multiple of 32.
std::string roundUpFunction(); std::string roundUpFunction();
std::string overflowCheckedUIntAddFunction(size_t _bits); 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); std::string arrayLengthFunction(ArrayType const& _type);
/// @returns the name of a function that computes the number of bytes required /// @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). /// 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. /// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type); 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. /// @returns the name of a function that allocates memory.
/// Modifies the "free memory pointer" /// Modifies the "free memory pointer"
/// Arguments: size /// Arguments: size
@ -115,13 +171,32 @@ public:
/// This is used for data decoded from external sources. /// This is used for data decoded from external sources.
std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); 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 /// @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, /// 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. /// 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. /// If @a _startSuffix == @a _endSuffix, the empty string is returned.
static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix); 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: 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; langutil::EVMVersion m_evmVersion;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector; 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()); return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
} }
string IRGenerationContext::variableName(VariableDeclaration const& _varDecl) string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl)
{ {
solAssert( solAssert(
m_localVariables.count(&_varDecl), m_localVariables.count(&_varDecl),
@ -48,6 +48,15 @@ string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
return m_localVariables[&_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) string IRGenerationContext::functionName(FunctionDefinition const& _function)
{ {
// @TODO previously, we had to distinguish creation context and runtime context, // @TODO previously, we had to distinguish creation context and runtime context,
@ -85,18 +94,27 @@ string IRGenerationContext::newYulVariable()
string IRGenerationContext::variable(Expression const& _expression) string IRGenerationContext::variable(Expression const& _expression)
{ {
unsigned size = _expression.annotation().type->sizeOnStack(); unsigned size = _expression.annotation().type->sizeOnStack();
solUnimplementedAssert(size == 1, ""); string var = "expr_" + to_string(_expression.id());
return "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) 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); string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions->createFunction(funName, [&]() { return m_functions->createFunction(funName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(fun <comma> <in>) -> <out> { function <functionName>(fun <comma> <in>) <arrow> <out> {
switch fun switch fun
<#cases> <#cases>
case <funID> case <funID>
@ -111,6 +129,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
templ("comma", _in > 0 ? "," : ""); templ("comma", _in > 0 ? "," : "");
YulUtilFunctions utils(m_evmVersion, m_functions); YulUtilFunctions utils(m_evmVersion, m_functions);
templ("in", utils.suffixedVariableNameList("in_", 0, _in)); templ("in", utils.suffixedVariableNameList("in_", 0, _in));
templ("arrow", _out > 0 ? "->" : "");
templ("out", utils.suffixedVariableNameList("out_", 0, _out)); templ("out", utils.suffixedVariableNameList("out_", 0, _out));
vector<map<string, string>> functions; vector<map<string, string>> functions;
for (auto const& contract: m_inheritanceHierarchy) for (auto const& contract: m_inheritanceHierarchy)
@ -120,11 +139,21 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
function->parameters().size() == _in && function->parameters().size() == _in &&
function->returnParameters().size() == _out 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> { functions.emplace_back(map<string, string> {
{ "funID", to_string(function->id()) }, { "funID", to_string(function->id()) },
{ "name", functionName(*function)} { "name", functionName(*function)}
}); });
}
templ("cases", move(functions)); templ("cases", move(functions));
return templ.render(); return templ.render();
}); });
} }
YulUtilFunctions IRGenerationContext::utils()
{
return YulUtilFunctions(m_evmVersion, m_functions);
}

View File

@ -26,6 +26,8 @@
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libdevcore/Common.h>
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -39,6 +41,7 @@ class ContractDefinition;
class VariableDeclaration; class VariableDeclaration;
class FunctionDefinition; class FunctionDefinition;
class Expression; class Expression;
class YulUtilFunctions;
/** /**
* Class that contains contextual information during IR generation. * Class that contains contextual information during IR generation.
@ -62,7 +65,16 @@ public:
std::string addLocalVariable(VariableDeclaration const& _varDecl); 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); std::string functionName(FunctionDefinition const& _function);
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration); FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
std::string virtualFunctionName(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 /// @returns the variable (or comma-separated list of variables) that contain
/// the value of the given expression. /// the value of the given expression.
std::string variable(Expression const& _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); 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: private:
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
OptimiserSettings m_optimiserSettings; OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy; std::vector<ContractDefinition const*> m_inheritanceHierarchy;
std::map<VariableDeclaration const*, std::string> m_localVariables; 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; std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
size_t m_varCounter = 0; 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) string IRGenerator::generate(ContractDefinition const& _contract)
{ {
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
Whiskers t(R"( Whiskers t(R"(
object "<CreationObject>" { object "<CreationObject>" {
code { code {
@ -89,18 +91,27 @@ string IRGenerator::generate(ContractDefinition const& _contract)
} }
)"); )");
resetContext(); resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("CreationObject", creationObjectName(_contract)); t("CreationObject", creationObjectName(_contract));
t("memoryInit", memoryInit()); t("memoryInit", memoryInit());
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : ""); t("constructor", constructorCode(_contract));
t("deploy", deployCode(_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()); t("functions", m_context.functionCollector()->requestedFunctions());
resetContext(); resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("RuntimeObject", runtimeObjectName(_contract)); t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_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()); t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
return t.render(); return t.render();
} }
@ -116,7 +127,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
{ {
string functionName = m_context.functionName(_function); string functionName = m_context.functionName(_function);
return m_context.functionCollector()->createFunction(functionName, [&]() { 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); t("functionName", functionName);
string params; string params;
for (auto const& varDecl: _function.parameters()) 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; // TODO initialize state variables in base to derived order.
if (!_constructor.isPayable()) // TODO base constructors
out = callValueCheck(); // 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 {};
return out;
} }
string IRGenerator::deployCode(ContractDefinition const& _contract) string IRGenerator::deployCode(ContractDefinition const& _contract)
@ -242,7 +266,7 @@ string IRGenerator::memoryInit()
.render(); .render();
} }
void IRGenerator::resetContext() void IRGenerator::resetContext(ContractDefinition const& _contract)
{ {
solAssert( solAssert(
m_context.functionCollector()->requestedFunctions().empty(), m_context.functionCollector()->requestedFunctions().empty(),
@ -250,4 +274,8 @@ void IRGenerator::resetContext()
); );
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); 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. /// Generates code for and returns the name of the function.
std::string generateFunction(FunctionDefinition const& _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 deployCode(ContractDefinition const& _contract);
std::string callValueCheck(); std::string callValueCheck();
@ -68,7 +68,7 @@ private:
std::string memoryInit(); std::string memoryInit();
void resetContext(); void resetContext(ContractDefinition const& _contract);
langutil::EVMVersion const m_evmVersion; langutil::EVMVersion const m_evmVersion;
OptimiserSettings const m_optimiserSettings; OptimiserSettings const m_optimiserSettings;

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@
#pragma once #pragma once
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/ir/IRLValue.h>
namespace dev namespace dev
{ {
@ -42,25 +43,70 @@ public:
m_utils(_utils) 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(Assignment const& _assignment) override;
bool visit(Return const& _return) override; bool visit(TupleExpression const& _tuple) override;
void endVisit(BinaryOperation const& _binOp) override; bool visit(IfStatement const& _ifStatement) override;
bool visit(FunctionCall const& _funCall) 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(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; bool visit(Literal const& _literal) override;
private: 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, /// @returns a Yul expression representing the current value of @a _expression,
/// converted to type @a _to if it does not yet have that type. /// converted to type @a _to if it does not yet have that type.
std::string expressionAsType(Expression const& _expression, Type const& _to); 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; std::ostringstream m_code;
IRGenerationContext& m_context; IRGenerationContext& m_context;
YulUtilFunctions& m_utils; 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 std;
using namespace dev; using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::smt; using namespace dev::solidity::smt;
EncodingContext::EncodingContext(SolverInterface& _solver): EncodingContext::EncodingContext(SolverInterface& _solver):
m_solver(_solver), m_solver(_solver),
m_thisAddress(make_unique<SymbolicAddressVariable>("this", m_solver)) m_thisAddress(make_unique<SymbolicAddressVariable>("this", m_solver))
{ {
auto sort = make_shared<smt::ArraySort>( auto sort = make_shared<ArraySort>(
make_shared<smt::Sort>(smt::Kind::Int), make_shared<Sort>(Kind::Int),
make_shared<smt::Sort>(smt::Kind::Int) make_shared<Sort>(Kind::Int)
); );
m_balances = make_unique<SymbolicVariable>(sort, "balances", m_solver); m_balances = make_unique<SymbolicVariable>(sort, "balances", m_solver);
} }
void EncodingContext::reset() void EncodingContext::reset()
{ {
resetAllVariables();
m_expressions.clear();
m_globalContext.clear();
m_thisAddress->increaseIndex(); m_thisAddress->increaseIndex();
m_balances->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(); return m_thisAddress->currentValue();
} }
smt::Expression EncodingContext::balance() Expression EncodingContext::balance()
{ {
return balance(m_thisAddress->currentValue()); 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(); unsigned indexBefore = m_balances->index();
addBalance(_from, 0 - _value); addBalance(_from, 0 - _value);
@ -65,7 +202,7 @@ void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::
solAssert(indexAfter > indexBefore, ""); solAssert(indexAfter > indexBefore, "");
m_balances->increaseIndex(); m_balances->increaseIndex();
/// Do not apply the transfer operation if _from == _to. /// Do not apply the transfer operation if _from == _to.
auto newBalances = smt::Expression::ite( auto newBalances = Expression::ite(
move(_from) == move(_to), move(_from) == move(_to),
m_balances->valueAtIndex(indexBefore), m_balances->valueAtIndex(indexBefore),
m_balances->valueAtIndex(indexAfter) 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); 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(), m_balances->currentValue(),
_address, _address,
balance(_address) + move(_value) balance(_address) + move(_value)

View File

@ -20,6 +20,9 @@
#include <libsolidity/formal/SolverInterface.h> #include <libsolidity/formal/SolverInterface.h>
#include <libsolidity/formal/SymbolicVariables.h> #include <libsolidity/formal/SymbolicVariables.h>
#include <unordered_map>
#include <set>
namespace dev namespace dev
{ {
namespace solidity namespace solidity
@ -38,22 +41,94 @@ public:
/// Resets the entire context. /// Resets the entire context.
void reset(); void reset();
/// Value of `this` address. /// Methods related to variables.
smt::Expression thisAddress(); //@{
/// @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`. /// @returns the symbolic balance of address `this`.
smt::Expression balance(); Expression balance();
/// @returns the symbolic balance of an address. /// @returns the symbolic balance of an address.
smt::Expression balance(smt::Expression _address); Expression balance(Expression _address);
/// Transfer _value from _from to _to. /// 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: private:
/// Adds _value to _account's balance. /// Adds _value to _account's balance.
void addBalance(smt::Expression _account, smt::Expression _value); void addBalance(Expression _account, Expression _value);
SolverInterface& m_solver; 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. /// Symbolic `this` address.
std::unique_ptr<SymbolicAddressVariable> m_thisAddress; 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. /// the constructor.
std::vector<std::string> unhandledQueries() { return m_interface->unhandledQueries(); } 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. /// otherwise nullptr.
static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall); static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall);
/// @returns the leftmost identifier in a multi-d IndexAccess.
static Expression const* leftmostBase(IndexAccess const& _indexAccess);
private: private:
// TODO: Check that we do not have concurrent reads and writes to a variable, // TODO: Check that we do not have concurrent reads and writes to a variable,
@ -115,14 +117,18 @@ private:
void inlineFunctionCall(FunctionCall const& _funCall); void inlineFunctionCall(FunctionCall const& _funCall);
/// Creates an uninterpreted function call. /// Creates an uninterpreted function call.
void abstractFunctionCall(FunctionCall const& _funCall); 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); void visitFunctionIdentifier(Identifier const& _identifier);
/// Encodes a modifier or function body according to the modifier /// Encodes a modifier or function body according to the modifier
/// visit depth. /// visit depth.
void visitFunctionOrModifier(); void visitFunctionOrModifier();
/// Defines a new global variable or function.
void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); 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 /// Handles the side effects of assignment
/// to variable of some SMT array type /// to variable of some SMT array type
/// while aliasing is not supported. /// while aliasing is not supported.
@ -135,7 +141,18 @@ private:
smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type); smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
void assignment(VariableDeclaration const& _variable, Expression const& _value, langutil::SourceLocation const& _location); 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); 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. /// Maps a variable to an SSA index.
using VariableIndices = std::unordered_map<VariableDeclaration const*, int>; using VariableIndices = std::unordered_map<VariableDeclaration const*, int>;
@ -162,6 +179,8 @@ private:
std::string const& _description std::string const& _description
); );
using CallStackEntry = std::pair<CallableDeclaration const*, ASTNode const*>;
struct OverflowTarget struct OverflowTarget
{ {
enum class Type { Underflow, Overflow, All } type; enum class Type { Underflow, Overflow, All } type;
@ -169,9 +188,9 @@ private:
smt::Expression value; smt::Expression value;
smt::Expression path; smt::Expression path;
langutil::SourceLocation const& location; 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), type(_type),
intType(_intType), intType(_intType),
value(_value), value(_value),
@ -198,11 +217,8 @@ private:
void initializeLocalVariables(FunctionDefinition const& _function); void initializeLocalVariables(FunctionDefinition const& _function);
void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smt::Expression> const& _callArgs); void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smt::Expression> const& _callArgs);
void resetVariable(VariableDeclaration const& _variable);
void resetStateVariables(); void resetStateVariables();
void resetStorageReferences(); 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. /// @returns the type without storage pointer information if it has it.
TypePointer typeWithoutPointer(TypePointer const& _type); TypePointer typeWithoutPointer(TypePointer const& _type);
@ -211,41 +227,21 @@ private:
/// using the branch condition as guard. /// using the branch condition as guard.
void mergeVariables(std::set<VariableDeclaration const*> const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse); 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. /// Tries to create an uninitialized variable and returns true on success.
/// This fails if the type is not supported.
bool createVariable(VariableDeclaration const& _varDecl); 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 /// @returns an expression denoting the value of the variable declared in @a _decl
/// at the current point. /// at the current point.
smt::Expression currentValue(VariableDeclaration const& _decl); smt::Expression currentValue(VariableDeclaration const& _decl);
/// @returns an expression denoting the value of the variable declared in @a _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. /// at the given index. Does not ensure that this index exists.
smt::Expression valueAtIndex(VariableDeclaration const& _decl, int _index); 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. /// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
smt::Expression expr(Expression const& _e); smt::Expression expr(Expression const& _e);
/// Creates the expression (value can be arbitrary) /// Creates the expression (value can be arbitrary)
void createExpr(Expression const& _e); void createExpr(Expression const& _e);
/// Checks if expression was created
bool knownExpr(Expression const& _e) const;
/// Creates the expression and sets its value. /// Creates the expression and sets its value.
void defineExpr(Expression const& _e, smt::Expression _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 /// Adds a new path condition
void pushPathCondition(smt::Expression const& _e); void pushPathCondition(smt::Expression const& _e);
/// Remove the last path condition /// Remove the last path condition
@ -255,17 +251,14 @@ private:
/// Returns the current callstack. Used for models. /// Returns the current callstack. Used for models.
langutil::SecondarySourceLocation currentCallStack(); langutil::SecondarySourceLocation currentCallStack();
/// Copies and pops the last called node. /// Copies and pops the last called node.
ASTNode const* popCallStack(); CallStackEntry popCallStack();
/// Adds @param _node to the callstack. /// Adds (_definition, _node) to the callstack.
void pushCallStack(ASTNode const* _node); void pushCallStack(CallStackEntry _entry);
/// Conjoin the current path conditions with the given parameter and add to the solver /// Conjoin the current path conditions with the given parameter and add to the solver
void addPathConjoinedExpression(smt::Expression const& _e); void addPathConjoinedExpression(smt::Expression const& _e);
/// Add to the solver: the given expression implied by the current path conditions /// Add to the solver: the given expression implied by the current path conditions
void addPathImpliedExpression(smt::Expression const& _e); void addPathImpliedExpression(smt::Expression const& _e);
/// Removes local variables from the context.
void removeLocalVariables();
/// Copy the SSA indices of m_variables. /// Copy the SSA indices of m_variables.
VariableIndices copyVariableIndices(); VariableIndices copyVariableIndices();
/// Resets the variable indices. /// Resets the variable indices.
@ -274,18 +267,16 @@ private:
/// @returns variables that are touched in _node's subtree. /// @returns variables that are touched in _node's subtree.
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node); std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node);
std::shared_ptr<smt::SolverInterface> m_interface; /// @returns the VariableDeclaration referenced by an Identifier or nullptr.
VariableUsage m_variableUsage; VariableDeclaration const* identifierToVariable(Expression const& _expr);
std::unique_ptr<smt::SolverInterface> m_interface;
smt::VariableUsage m_variableUsage;
bool m_loopExecutionHappened = false; bool m_loopExecutionHappened = false;
bool m_arrayAssignmentHappened = false; bool m_arrayAssignmentHappened = false;
bool m_externalFunctionCallHappened = false; bool m_externalFunctionCallHappened = false;
// True if the "No SMT solver available" warning was already created. // True if the "No SMT solver available" warning was already created.
bool m_noSolverWarning = false; 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. /// Stores the instances of an Uninterpreted Function applied to arguments.
/// These may be direct application of UFs or Array index access. /// These may be direct application of UFs or Array index access.
@ -301,10 +292,8 @@ private:
langutil::ErrorList m_smtErrors; langutil::ErrorList m_smtErrors;
std::shared_ptr<langutil::Scanner> m_scanner; std::shared_ptr<langutil::Scanner> m_scanner;
/// Stores the current path of function calls. /// Stores the current function/modifier call/invocation path.
std::vector<FunctionDefinition const*> m_functionPath; std::vector<CallStackEntry> m_callStack;
/// Stores the current call/invocation path.
std::vector<ASTNode const*> m_callStack;
/// Returns true if the current function was not visited by /// Returns true if the current function was not visited by
/// a function call. /// a function call.
bool isRootFunction(); bool isRootFunction();

View File

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

View File

@ -59,7 +59,7 @@ public:
private: private:
static bool solverAnswered(CheckResult result); 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> #include <libsolidity/formal/SSAVariable.h>
using namespace std; using namespace std;
using namespace dev::solidity; using namespace dev::solidity::smt;
SSAVariable::SSAVariable() SSAVariable::SSAVariable()
{ {
@ -28,6 +28,5 @@ SSAVariable::SSAVariable()
void SSAVariable::resetIndex() void SSAVariable::resetIndex()
{ {
m_currentIndex = 0; m_currentIndex = 0;
m_nextFreeIndex.reset (new unsigned); m_nextFreeIndex = make_unique<unsigned>(1);
*m_nextFreeIndex = 1;
} }

View File

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

View File

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

View File

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

View File

@ -23,17 +23,17 @@
using namespace std; using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity; using namespace dev::solidity::smt;
SymbolicVariable::SymbolicVariable( SymbolicVariable::SymbolicVariable(
TypePointer _type, solidity::TypePointer _type,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
m_type(move(_type)), m_type(move(_type)),
m_uniqueName(move(_uniqueName)), m_uniqueName(move(_uniqueName)),
m_interface(_interface), m_interface(_interface),
m_ssa(make_shared<SSAVariable>()) m_ssa(make_unique<SSAVariable>())
{ {
solAssert(m_type, ""); solAssert(m_type, "");
m_sort = smtSort(*m_type); m_sort = smtSort(*m_type);
@ -41,19 +41,19 @@ SymbolicVariable::SymbolicVariable(
} }
SymbolicVariable::SymbolicVariable( SymbolicVariable::SymbolicVariable(
smt::SortPointer _sort, SortPointer _sort,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
m_sort(move(_sort)), m_sort(move(_sort)),
m_uniqueName(move(_uniqueName)), m_uniqueName(move(_uniqueName)),
m_interface(_interface), m_interface(_interface),
m_ssa(make_shared<SSAVariable>()) m_ssa(make_unique<SSAVariable>())
{ {
solAssert(m_sort, ""); solAssert(m_sort, "");
} }
smt::Expression SymbolicVariable::currentValue() const Expression SymbolicVariable::currentValue() const
{ {
return valueAtIndex(m_ssa->index()); return valueAtIndex(m_ssa->index());
} }
@ -63,7 +63,7 @@ string SymbolicVariable::currentName() const
return uniqueSymbol(m_ssa->index()); 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); return m_interface.newVariable(uniqueSymbol(_index), m_sort);
} }
@ -73,26 +73,26 @@ string SymbolicVariable::uniqueSymbol(unsigned _index) const
return m_uniqueName + "_" + to_string(_index); return m_uniqueName + "_" + to_string(_index);
} }
smt::Expression SymbolicVariable::increaseIndex() Expression SymbolicVariable::increaseIndex()
{ {
++(*m_ssa); ++(*m_ssa);
return currentValue(); return currentValue();
} }
SymbolicBoolVariable::SymbolicBoolVariable( SymbolicBoolVariable::SymbolicBoolVariable(
TypePointer _type, solidity::TypePointer _type,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicVariable(move(_type), move(_uniqueName), _interface) SymbolicVariable(move(_type), move(_uniqueName), _interface)
{ {
solAssert(m_type->category() == Type::Category::Bool, ""); solAssert(m_type->category() == solidity::Type::Category::Bool, "");
} }
SymbolicIntVariable::SymbolicIntVariable( SymbolicIntVariable::SymbolicIntVariable(
TypePointer _type, solidity::TypePointer _type,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicVariable(move(_type), move(_uniqueName), _interface) SymbolicVariable(move(_type), move(_uniqueName), _interface)
{ {
@ -101,7 +101,7 @@ SymbolicIntVariable::SymbolicIntVariable(
SymbolicAddressVariable::SymbolicAddressVariable( SymbolicAddressVariable::SymbolicAddressVariable(
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicIntVariable(TypeProvider::uint(160), move(_uniqueName), _interface) SymbolicIntVariable(TypeProvider::uint(160), move(_uniqueName), _interface)
{ {
@ -110,21 +110,21 @@ SymbolicAddressVariable::SymbolicAddressVariable(
SymbolicFixedBytesVariable::SymbolicFixedBytesVariable( SymbolicFixedBytesVariable::SymbolicFixedBytesVariable(
unsigned _numBytes, unsigned _numBytes,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), move(_uniqueName), _interface) SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), move(_uniqueName), _interface)
{ {
} }
SymbolicFunctionVariable::SymbolicFunctionVariable( SymbolicFunctionVariable::SymbolicFunctionVariable(
TypePointer _type, solidity::TypePointer _type,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicVariable(move(_type), move(_uniqueName), _interface), SymbolicVariable(move(_type), move(_uniqueName), _interface),
m_declaration(m_interface.newVariable(currentName(), m_sort)) 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() void SymbolicFunctionVariable::resetDeclaration()
@ -132,22 +132,22 @@ void SymbolicFunctionVariable::resetDeclaration()
m_declaration = m_interface.newVariable(currentName(), m_sort); m_declaration = m_interface.newVariable(currentName(), m_sort);
} }
smt::Expression SymbolicFunctionVariable::increaseIndex() Expression SymbolicFunctionVariable::increaseIndex()
{ {
++(*m_ssa); ++(*m_ssa);
resetDeclaration(); resetDeclaration();
return currentValue(); return currentValue();
} }
smt::Expression SymbolicFunctionVariable::operator()(vector<smt::Expression> _arguments) const Expression SymbolicFunctionVariable::operator()(vector<Expression> _arguments) const
{ {
return m_declaration(_arguments); return m_declaration(_arguments);
} }
SymbolicMappingVariable::SymbolicMappingVariable( SymbolicMappingVariable::SymbolicMappingVariable(
TypePointer _type, solidity::TypePointer _type,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicVariable(move(_type), move(_uniqueName), _interface) SymbolicVariable(move(_type), move(_uniqueName), _interface)
{ {
@ -155,9 +155,9 @@ SymbolicMappingVariable::SymbolicMappingVariable(
} }
SymbolicArrayVariable::SymbolicArrayVariable( SymbolicArrayVariable::SymbolicArrayVariable(
TypePointer _type, solidity::TypePointer _type,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicVariable(move(_type), move(_uniqueName), _interface) SymbolicVariable(move(_type), move(_uniqueName), _interface)
{ {
@ -165,11 +165,29 @@ SymbolicArrayVariable::SymbolicArrayVariable(
} }
SymbolicEnumVariable::SymbolicEnumVariable( SymbolicEnumVariable::SymbolicEnumVariable(
TypePointer _type, solidity::TypePointer _type,
string _uniqueName, string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
): ):
SymbolicVariable(move(_type), move(_uniqueName), _interface) SymbolicVariable(move(_type), move(_uniqueName), _interface)
{ {
solAssert(isEnum(m_type->category()), ""); 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 solidity
{ {
namespace smt
{
class Type; class Type;
@ -36,23 +38,23 @@ class SymbolicVariable
{ {
public: public:
SymbolicVariable( SymbolicVariable(
TypePointer _type, solidity::TypePointer _type,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
SymbolicVariable( SymbolicVariable(
smt::SortPointer _sort, SortPointer _sort,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
virtual ~SymbolicVariable() = default; virtual ~SymbolicVariable() = default;
smt::Expression currentValue() const; Expression currentValue() const;
std::string currentName() const; std::string currentName() const;
virtual smt::Expression valueAtIndex(int _index) const; virtual Expression valueAtIndex(int _index) const;
virtual smt::Expression increaseIndex(); virtual Expression increaseIndex();
virtual smt::Expression operator()(std::vector<smt::Expression> /*_arguments*/) const virtual Expression operator()(std::vector<Expression> /*_arguments*/) const
{ {
solAssert(false, "Function application to non-function."); solAssert(false, "Function application to non-function.");
} }
@ -60,18 +62,18 @@ public:
unsigned index() const { return m_ssa->index(); } unsigned index() const { return m_ssa->index(); }
unsigned& index() { 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: protected:
std::string uniqueSymbol(unsigned _index) const; std::string uniqueSymbol(unsigned _index) const;
/// SMT sort. /// SMT sort.
smt::SortPointer m_sort; SortPointer m_sort;
/// Solidity type, used for size and range in number types. /// Solidity type, used for size and range in number types.
TypePointer m_type; solidity::TypePointer m_type;
std::string m_uniqueName; std::string m_uniqueName;
smt::SolverInterface& m_interface; SolverInterface& m_interface;
std::shared_ptr<SSAVariable> m_ssa; std::unique_ptr<SSAVariable> m_ssa;
}; };
/** /**
@ -81,9 +83,9 @@ class SymbolicBoolVariable: public SymbolicVariable
{ {
public: public:
SymbolicBoolVariable( SymbolicBoolVariable(
TypePointer _type, solidity::TypePointer _type,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
}; };
@ -94,9 +96,9 @@ class SymbolicIntVariable: public SymbolicVariable
{ {
public: public:
SymbolicIntVariable( SymbolicIntVariable(
TypePointer _type, solidity::TypePointer _type,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
}; };
@ -108,7 +110,7 @@ class SymbolicAddressVariable: public SymbolicIntVariable
public: public:
SymbolicAddressVariable( SymbolicAddressVariable(
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
}; };
@ -121,7 +123,7 @@ public:
SymbolicFixedBytesVariable( SymbolicFixedBytesVariable(
unsigned _numBytes, unsigned _numBytes,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
}; };
@ -132,20 +134,20 @@ class SymbolicFunctionVariable: public SymbolicVariable
{ {
public: public:
SymbolicFunctionVariable( SymbolicFunctionVariable(
TypePointer _type, solidity::TypePointer _type,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
smt::Expression increaseIndex(); Expression increaseIndex();
smt::Expression operator()(std::vector<smt::Expression> _arguments) const; Expression operator()(std::vector<Expression> _arguments) const;
private: private:
/// Creates a new function declaration. /// Creates a new function declaration.
void resetDeclaration(); void resetDeclaration();
/// Stores the current function declaration. /// Stores the current function declaration.
smt::Expression m_declaration; Expression m_declaration;
}; };
/** /**
@ -155,9 +157,9 @@ class SymbolicMappingVariable: public SymbolicVariable
{ {
public: public:
SymbolicMappingVariable( SymbolicMappingVariable(
TypePointer _type, solidity::TypePointer _type,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
}; };
@ -168,9 +170,9 @@ class SymbolicArrayVariable: public SymbolicVariable
{ {
public: public:
SymbolicArrayVariable( SymbolicArrayVariable(
TypePointer _type, solidity::TypePointer _type,
std::string _uniqueName, std::string _uniqueName,
smt::SolverInterface& _interface SolverInterface& _interface
); );
}; };
@ -181,11 +183,35 @@ class SymbolicEnumVariable: public SymbolicVariable
{ {
public: public:
SymbolicEnumVariable( SymbolicEnumVariable(
TypePointer _type, solidity::TypePointer _type,
std::string _uniqueName, 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 std;
using namespace dev; using namespace dev;
using namespace dev::solidity; 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) void VariableUsage::endVisit(Identifier const& _identifier)
{ {
Declaration const* declaration = _identifier.annotation().referencedDeclaration; if (_identifier.annotation().lValueRequested)
solAssert(declaration, ""); checkIdentifier(_identifier);
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) }
if (_identifier.annotation().lValueRequested)
m_touchedVariables.insert(varDecl); 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) void VariableUsage::endVisit(FunctionCall const& _funCall)
{ {
if (auto const& funDef = SMTChecker::inlinedFunctionCallToDefinition(_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); funDef->accept(*this);
} }
bool VariableUsage::visit(FunctionDefinition const& _function) bool VariableUsage::visit(FunctionDefinition const& _function)
{ {
m_functionPath.push_back(&_function); m_callStack.push_back(&_function);
return true; return true;
} }
void VariableUsage::endVisit(FunctionDefinition const&) void VariableUsage::endVisit(FunctionDefinition const&)
{ {
solAssert(!m_functionPath.empty(), ""); solAssert(!m_callStack.empty(), "");
m_functionPath.pop_back(); m_callStack.pop_back();
} }
void VariableUsage::endVisit(ModifierInvocation const& _modifierInv) void VariableUsage::endVisit(ModifierInvocation const& _modifierInv)
@ -62,18 +82,23 @@ void VariableUsage::endVisit(ModifierInvocation const& _modifierInv)
void VariableUsage::endVisit(PlaceholderStatement const&) void VariableUsage::endVisit(PlaceholderStatement const&)
{ {
solAssert(!m_functionPath.empty(), ""); solAssert(!m_callStack.empty(), "");
FunctionDefinition const* function = m_functionPath.back(); FunctionDefinition const* funDef = nullptr;
solAssert(function, ""); for (auto it = m_callStack.rbegin(); it != m_callStack.rend() && !funDef; ++it)
if (function->isImplemented()) funDef = dynamic_cast<FunctionDefinition const*>(*it);
function->body().accept(*this); 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(); Declaration const* declaration = _identifier.annotation().referencedDeclaration;
m_functionPath.clear(); solAssert(declaration, "");
m_functionPath += _outerCallstack; if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
_node.accept(*this); {
return m_touchedVariables; 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 solidity
{ {
namespace smt
{
/** /**
* This class computes information about which variables are modified in a certain subtree. * This class computes information about which variables are modified in a certain subtree.
@ -34,19 +36,25 @@ class VariableUsage: private ASTConstVisitor
{ {
public: public:
/// @param _outerCallstack the current callstack in the callers context. /// @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: private:
void endVisit(Identifier const& _node) override; void endVisit(Identifier const& _node) override;
void endVisit(IndexAccess const& _node) override;
void endVisit(FunctionCall const& _node) override; void endVisit(FunctionCall const& _node) override;
bool visit(FunctionDefinition const& _node) override; bool visit(FunctionDefinition const& _node) override;
void endVisit(FunctionDefinition const& _node) override; void endVisit(FunctionDefinition const& _node) override;
void endVisit(ModifierInvocation const& _node) override; void endVisit(ModifierInvocation const& _node) override;
void endVisit(PlaceholderStatement 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::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 <libevmasm/Exceptions.h>
#include <libdevcore/SwarmHash.h> #include <libdevcore/SwarmHash.h>
#include <libdevcore/IpfsHash.h>
#include <libdevcore/JSON.h> #include <libdevcore/JSON.h>
#include <json/json.h> #include <json/json.h>
@ -216,7 +217,7 @@ bool CompilerStack::parse()
string const& path = sourcesToParse[i]; string const& path = sourcesToParse[i];
Source& source = m_sources[path]; Source& source = m_sources[path];
source.scanner->reset(); 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) if (!source.ast)
solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error.");
else else
@ -249,7 +250,7 @@ bool CompilerStack::analyze()
bool noErrors = true; bool noErrors = true;
try { try {
SyntaxChecker syntaxChecker(m_errorReporter); SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser);
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
if (!syntaxChecker.checkSyntax(*source->ast)) if (!syntaxChecker.checkSyntax(*source->ast))
noErrors = false; noErrors = false;
@ -260,7 +261,7 @@ bool CompilerStack::analyze()
noErrors = false; noErrors = false;
m_globalContext = make_shared<GlobalContext>(); 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) for (Source const* source: m_sourceOrder)
if (!resolver.registerDeclarations(*source->ast)) if (!resolver.registerDeclarations(*source->ast))
return false; return false;
@ -278,11 +279,8 @@ bool CompilerStack::analyze()
for (ASTPointer<ASTNode> const& node: source->ast->nodes()) for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) 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 // 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 // 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 // already causes a double-declaration error elsewhere, so we do not report
@ -397,7 +395,8 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con
return return
m_requestedContractNames.empty() || m_requestedContractNames.empty() ||
m_requestedContractNames.count(_contract.fullyQualifiedName()) || m_requestedContractNames.count(_contract.fullyQualifiedName()) ||
m_requestedContractNames.count(_contract.name()); m_requestedContractNames.count(_contract.name()) ||
m_requestedContractNames.count(":" + _contract.name());
} }
bool CompilerStack::compile() bool CompilerStack::compile()
@ -771,6 +770,13 @@ h256 const& CompilerStack::Source::swarmHash() const
return swarmHashCached; 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) 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"] = Json::arrayValue;
meta["sources"][s.first]["urls"].append("bzzr://" + toHex(s.second.swarmHash().asBytes())); 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()); encoder.pushBytes("bzzr0", dev::swarmHash(_metadata).asBytes());
if (_experimentalMode) if (_experimentalMode)
encoder.pushBool("experimental", true); encoder.pushBool("experimental", true);
if (m_release)
encoder.pushBytes("solc", VersionCompactBytes);
else
encoder.pushString("solc", VersionStringStrict);
return encoder.serialise(); return encoder.serialise();
} }

View File

@ -25,6 +25,7 @@
#include <libsolidity/interface/ReadFile.h> #include <libsolidity/interface/ReadFile.h>
#include <libsolidity/interface/OptimiserSettings.h> #include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/Version.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
@ -131,6 +132,14 @@ public:
/// Must be set before parsing. /// Must be set before parsing.
void setOptimiserSettings(OptimiserSettings _settings); 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. /// Set the EVM version used before running compile.
/// When called without an argument it will revert to the default version. /// When called without an argument it will revert to the default version.
/// Must be set before parsing. /// 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 /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
Json::Value gasEstimates(std::string const& _contractName) const; 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: private:
/// The state per source unit. Filled gradually during parsing. /// The state per source unit. Filled gradually during parsing.
struct Source struct Source
@ -269,9 +280,11 @@ private:
std::shared_ptr<SourceUnit> ast; std::shared_ptr<SourceUnit> ast;
h256 mutable keccak256HashCached; h256 mutable keccak256HashCached;
h256 mutable swarmHashCached; h256 mutable swarmHashCached;
std::string mutable ipfsUrlCached;
void reset() { *this = Source(); } void reset() { *this = Source(); }
h256 const& keccak256() const; h256 const& keccak256() const;
h256 const& swarmHash() const; h256 const& swarmHash() const;
std::string const& ipfsUrl() const;
}; };
/// The state per contract. Filled gradually during compilation. /// The state per contract. Filled gradually during compilation.
@ -333,7 +346,7 @@ private:
std::string createMetadata(Contract const& _contract) const; std::string createMetadata(Contract const& _contract) const;
/// @returns the metadata CBOR for the given serialised metadata JSON. /// @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. /// @returns the computer source mapping string.
std::string computeSourceMapping(eth::AssemblyItems const& _items) const; std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
@ -381,7 +394,9 @@ private:
langutil::ErrorList m_errorList; langutil::ErrorList m_errorList;
langutil::ErrorReporter m_errorReporter; langutil::ErrorReporter m_errorReporter;
bool m_metadataLiteralSources = false; bool m_metadataLiteralSources = false;
bool m_parserErrorRecovery = false;
State m_stackState = Empty; 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 *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); ret.outputSelection = std::move(outputSelection);
return std::move(ret); return { std::move(ret) };
} }
Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings) 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 Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
{ {
YulStringRepository::reset();
try try
{ {
auto parsed = parseInput(_input); auto parsed = parseInput(_input);

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