mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6853 from ethereum/develop
Merge develop inte release for 0.5.9
This commit is contained in:
commit
e560f70d8e
@ -30,7 +30,9 @@ defaults:
|
||||
command: scripts/tests.sh --junit_report test_results
|
||||
- run_regressions: &run_regressions
|
||||
name: Regression tests
|
||||
command: scripts/regressions.py -o test_results
|
||||
command: |
|
||||
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
|
||||
scripts/regressions.py -o test_results
|
||||
- solc_artifact: &solc_artifact
|
||||
path: build/solc/solc
|
||||
destination: solc
|
||||
@ -43,12 +45,14 @@ defaults:
|
||||
- ossfuzz_artifacts: &ossfuzz_artifacts
|
||||
root: build
|
||||
paths:
|
||||
- test/tools/ossfuzz/solc_opt_ossfuzz
|
||||
- test/tools/ossfuzz/solc_noopt_ossfuzz
|
||||
- test/tools/ossfuzz/const_opt_ossfuzz
|
||||
- test/tools/ossfuzz/solc_noopt_ossfuzz
|
||||
- test/tools/ossfuzz/solc_opt_ossfuzz
|
||||
- test/tools/ossfuzz/strictasm_assembly_ossfuzz
|
||||
- test/tools/ossfuzz/strictasm_diff_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_ossfuzz
|
||||
- test/tools/ossfuzz/strictasm_opt_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_ossfuzz
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
@ -134,6 +138,24 @@ jobs:
|
||||
command: |
|
||||
test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js
|
||||
|
||||
test_emscripten_external_colony:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
name: Install test dependencies
|
||||
command: |
|
||||
sudo apt-get -qy install lsof
|
||||
- run:
|
||||
name: External ColonyNetworks tests
|
||||
command: |
|
||||
test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js
|
||||
|
||||
build_x86_linux:
|
||||
docker:
|
||||
- image: buildpack-deps:bionic
|
||||
@ -337,6 +359,7 @@ jobs:
|
||||
ulimit -a
|
||||
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
|
||||
ulimit -s 16384
|
||||
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
|
||||
build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test
|
||||
- run:
|
||||
name: Run commandline tests with ASAN
|
||||
@ -344,6 +367,7 @@ jobs:
|
||||
ulimit -a
|
||||
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
|
||||
ulimit -s 16384
|
||||
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
|
||||
test/cmdlineTests.sh
|
||||
- store_test_results:
|
||||
path: test_results/
|
||||
@ -416,11 +440,11 @@ jobs:
|
||||
|
||||
build_x86_linux_ossfuzz:
|
||||
docker:
|
||||
- image: buildpack-deps:cosmic
|
||||
- image: buildpack-deps:disco
|
||||
environment:
|
||||
TERM: xterm
|
||||
CC: /usr/bin/clang-7
|
||||
CXX: /usr/bin/clang++-7
|
||||
CC: /usr/bin/clang-8
|
||||
CXX: /usr/bin/clang++-8
|
||||
CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
|
||||
steps:
|
||||
- checkout
|
||||
@ -428,16 +452,18 @@ jobs:
|
||||
name: Install build dependencies
|
||||
command: |
|
||||
apt-get -qq update
|
||||
apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\*
|
||||
apt-get -qy install wget clang-8 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\*
|
||||
./scripts/install_lpm.sh
|
||||
./scripts/install_libfuzzer.sh
|
||||
# Install evmone and dependencies (intx and ethash)
|
||||
./scripts/install_evmone.sh
|
||||
- run: *setup_prerelease_commit_hash
|
||||
- run: *run_build_ossfuzz
|
||||
- persist_to_workspace: *ossfuzz_artifacts
|
||||
|
||||
test_x86_ossfuzz_regression:
|
||||
docker:
|
||||
- image: buildpack-deps:cosmic
|
||||
- image: buildpack-deps:disco
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
@ -448,13 +474,11 @@ jobs:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
apt-get -qq update
|
||||
apt-get -qy install libcvc4-dev llvm-7-dev
|
||||
apt-get -qy install libcvc4-dev llvm-8-dev
|
||||
./scripts/download_ossfuzz_corpus.sh
|
||||
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1
|
||||
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1
|
||||
- run: mkdir -p test_results
|
||||
- run: *run_regressions
|
||||
- store_test_results:
|
||||
path: test_results/
|
||||
- store_artifacts:
|
||||
path: test_results/
|
||||
destination: test_results/
|
||||
@ -471,14 +495,6 @@ workflows:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- test_emscripten_external_zeppelin:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- test_emscripten_external_gnosis:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- build_x86_linux: *build_on_tags
|
||||
- build_x86_linux_cxx17: *build_on_tags
|
||||
- build_x86_clang7_asan: *build_on_tags
|
||||
@ -521,8 +537,13 @@ workflows:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- test_emscripten_external_colony:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- build_x86_linux_ossfuzz: *build_on_tags
|
||||
- test_x86_ossfuzz_regression:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_x86_linux_ossfuzz
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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.
|
||||
Provide as much information as necessary to reproduce the bug.
|
||||
|
||||
```
|
||||
```solidity
|
||||
// Some *minimal* Solidity source code to reproduce the bug.
|
||||
// ...
|
||||
```
|
||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.5.8")
|
||||
set(PROJECT_VERSION "0.5.9")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
|
||||
|
||||
option(LLL "Build LLL" OFF)
|
||||
|
36
Changelog.md
36
Changelog.md
@ -1,3 +1,38 @@
|
||||
### 0.5.9 (2019-05-28)
|
||||
|
||||
Language Features:
|
||||
* Inline Assembly: Revert change introduced in 0.5.7: The ``callvalue()`` instruction does not require ``payable`` anymore.
|
||||
* Static Analyzer: Disallow libraries calling themselves externally.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Assembler: Encode the compiler version in the deployed bytecode.
|
||||
* Code Generator: Fix handling of structs of dynamic size as constructor parameters.
|
||||
* Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch.
|
||||
* Inline Assembly: Disallow the combination of ``msize()`` and the Yul optimizer.
|
||||
* Metadata: Add IPFS hashes of source files.
|
||||
* Optimizer: Add rule to simplify SHL/SHR combinations.
|
||||
* Optimizer: Add rules for multiplication and division by left-shifted one.
|
||||
* SMTChecker: Support inherited state variables.
|
||||
* SMTChecker: Support tuples and function calls with multiple return values.
|
||||
* SMTChecker: Support ``delete``.
|
||||
* SMTChecker: Inline external function calls to ``this``.
|
||||
* Yul Optimizer: Simplify single-run ``for`` loops to ``if`` statements.
|
||||
* Yul Optimizer: Optimize representation of numbers.
|
||||
* Yul Optimizer: Do not inline recursive functions.
|
||||
* Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used.
|
||||
|
||||
Bugfixes:
|
||||
* Code Generator: Explicitly turn uninitialized internal function pointers into invalid functions when loaded from storage.
|
||||
* Code Generator: Fix assertion failure when assigning structs containing array of mapping.
|
||||
* Compiler Internals: Reset the Yul string repository before each compilation, freeing up memory.
|
||||
* SMTChecker: Fix bad cast in base constructor modifier.
|
||||
* SMTChecker: Fix internal error when visiting state variable inherited from base class.
|
||||
* SMTChecker: Fix internal error in fixed point operations.
|
||||
* SMTChecker: Fix internal error in assignment to unsupported type.
|
||||
* SMTChecker: Fix internal error in branching when inlining function calls that modify local variables.
|
||||
|
||||
|
||||
### 0.5.8 (2019-04-30)
|
||||
|
||||
Important Bugfixes:
|
||||
@ -33,6 +68,7 @@ Bugfixes:
|
||||
* SMTChecker: SSA control-flow did not take into account state variables that were modified inside inlined functions that were called inside branches.
|
||||
* Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation.
|
||||
* Type System: Allow direct call to base class functions that have overloads.
|
||||
* Type System: Warn about shadowing builtin variables if user variables are named ``this`` or ``super``.
|
||||
* Yul: Properly register functions and disallow shadowing between function variables and variables in the outside scope.
|
||||
|
||||
|
||||
|
@ -37,6 +37,9 @@ function(create_build_info NAME)
|
||||
-DETH_BUILD_COMPILER="${ETH_BUILD_COMPILER}"
|
||||
-DETH_BUILD_PLATFORM="${ETH_BUILD_PLATFORM}"
|
||||
-DPROJECT_VERSION="${PROJECT_VERSION}"
|
||||
-DPROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}"
|
||||
-DPROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}"
|
||||
-DPROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}"
|
||||
-P "${ETH_SCRIPTS_DIR}/buildinfo.cmake"
|
||||
)
|
||||
include_directories("${PROJECT_BINARY_DIR}/include")
|
||||
|
@ -144,7 +144,13 @@ else ()
|
||||
endif ()
|
||||
|
||||
if (SANITIZE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=${SANITIZE}")
|
||||
# Perform case-insensitive string compare
|
||||
string(TOLOWER "${SANITIZE}" san)
|
||||
# -fno-omit-frame-pointer gives more informative stack trace in case of an error
|
||||
# -fsanitize-address-use-after-scope throws an error when a variable is used beyond its scope
|
||||
if (san STREQUAL "address")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Code coverage support.
|
||||
|
@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#define ETH_PROJECT_VERSION "@PROJECT_VERSION@"
|
||||
#define ETH_PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define ETH_PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define ETH_PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
#define SOL_COMMIT_HASH "@SOL_COMMIT_HASH@"
|
||||
#define ETH_BUILD_TYPE "@ETH_BUILD_TYPE@"
|
||||
#define ETH_BUILD_OS "@ETH_BUILD_OS@"
|
||||
|
@ -67,7 +67,7 @@ jsoncpp:
|
||||
license you like.
|
||||
|
||||
scanner/token:
|
||||
The liblangutil/{CharStream,Scanner,Token}.{h,cpp} files are dervied from
|
||||
The liblangutil/{CharStream,Scanner,Token}.{h,cpp} files are derived from
|
||||
code originating from the V8 project licensed under the following terms:
|
||||
|
||||
Copyright 2006-2012, the V8 project authors. All rights reserved.
|
||||
|
@ -214,6 +214,7 @@ Given the contract:
|
||||
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
|
||||
contract Foo {
|
||||
function bar(bytes3[2] memory) public pure {}
|
||||
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
|
||||
@ -485,6 +486,7 @@ For example,
|
||||
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
|
||||
contract Test {
|
||||
constructor() public { b = hex"12345678901234567890123456789012"; }
|
||||
event Event(uint indexed a, bytes32 b);
|
||||
@ -533,6 +535,7 @@ As an example, the code
|
||||
pragma solidity >=0.4.19 <0.7.0;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
|
||||
contract Test {
|
||||
struct S { uint a; uint[] b; T[] c; }
|
||||
struct T { uint x; uint y; }
|
||||
|
@ -104,27 +104,28 @@ efficient code, for example:
|
||||
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
|
||||
library VectorSum {
|
||||
// This function is less efficient because the optimizer currently fails to
|
||||
// remove the bounds checks in array access.
|
||||
function sumSolidity(uint[] memory _data) public pure returns (uint o_sum) {
|
||||
function sumSolidity(uint[] memory _data) public pure returns (uint sum) {
|
||||
for (uint i = 0; i < _data.length; ++i)
|
||||
o_sum += _data[i];
|
||||
sum += _data[i];
|
||||
}
|
||||
|
||||
// We know that we only access the array in bounds, so we can avoid the check.
|
||||
// 0x20 needs to be added to an array because the first slot contains the
|
||||
// array length.
|
||||
function sumAsm(uint[] memory _data) public pure returns (uint o_sum) {
|
||||
function sumAsm(uint[] memory _data) public pure returns (uint sum) {
|
||||
for (uint i = 0; i < _data.length; ++i) {
|
||||
assembly {
|
||||
o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
|
||||
sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same as above, but accomplish the entire code within inline assembly.
|
||||
function sumPureAsm(uint[] memory _data) public pure returns (uint o_sum) {
|
||||
function sumPureAsm(uint[] memory _data) public pure returns (uint sum) {
|
||||
assembly {
|
||||
// Load the length (first 32 bytes)
|
||||
let len := mload(_data)
|
||||
@ -143,7 +144,7 @@ efficient code, for example:
|
||||
lt(data, end)
|
||||
{ data := add(data, 0x20) }
|
||||
{
|
||||
o_sum := add(o_sum, mload(data))
|
||||
sum := add(sum, mload(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -693,6 +694,7 @@ We consider the runtime bytecode of the following Solidity program::
|
||||
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
|
||||
contract C {
|
||||
function f(uint x) public pure returns (uint y) {
|
||||
y = 1;
|
||||
|
@ -1,4 +1,15 @@
|
||||
[
|
||||
{
|
||||
"name": "DynamicConstructorArgumentsClippedABIV2",
|
||||
"summary": "A contract's constructor that takes structs or arrays that contain dynamically-sized arrays reverts or decodes to invalid data.",
|
||||
"description": "During construction of a contract, constructor parameters are copied from the code section to memory for decoding. The amount of bytes to copy was calculated incorrectly in case all parameters are statically-sized but contain dynamically-sized arrays as struct members or inner arrays. Such types are only available if ABIEncoderV2 is activated.",
|
||||
"introduced": "0.4.16",
|
||||
"fixed": "0.5.9",
|
||||
"severity": "very low",
|
||||
"conditions": {
|
||||
"ABIEncoderV2": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "UninitializedFunctionPointerInConstructor",
|
||||
"summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.",
|
||||
|
@ -452,6 +452,7 @@
|
||||
},
|
||||
"0.4.16": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
@ -462,6 +463,7 @@
|
||||
},
|
||||
"0.4.17": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
@ -473,6 +475,7 @@
|
||||
},
|
||||
"0.4.18": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
@ -483,6 +486,7 @@
|
||||
},
|
||||
"0.4.19": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
@ -510,6 +514,7 @@
|
||||
},
|
||||
"0.4.20": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
@ -521,6 +526,7 @@
|
||||
},
|
||||
"0.4.21": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
@ -532,6 +538,7 @@
|
||||
},
|
||||
"0.4.22": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
@ -543,6 +550,7 @@
|
||||
},
|
||||
"0.4.23": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
@ -553,6 +561,7 @@
|
||||
},
|
||||
"0.4.24": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
@ -563,6 +572,7 @@
|
||||
},
|
||||
"0.4.25": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x"
|
||||
@ -570,7 +580,9 @@
|
||||
"released": "2018-09-12"
|
||||
},
|
||||
"0.4.26": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2"
|
||||
],
|
||||
"released": "2019-04-29"
|
||||
},
|
||||
"0.4.3": {
|
||||
@ -677,6 +689,7 @@
|
||||
},
|
||||
"0.5.0": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
@ -685,6 +698,7 @@
|
||||
},
|
||||
"0.5.1": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
@ -693,6 +707,7 @@
|
||||
},
|
||||
"0.5.2": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
@ -701,6 +716,7 @@
|
||||
},
|
||||
"0.5.3": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
@ -709,6 +725,7 @@
|
||||
},
|
||||
"0.5.4": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
@ -717,6 +734,7 @@
|
||||
},
|
||||
"0.5.5": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage",
|
||||
@ -727,6 +745,7 @@
|
||||
},
|
||||
"0.5.6": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage",
|
||||
@ -736,13 +755,20 @@
|
||||
},
|
||||
"0.5.7": {
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries"
|
||||
],
|
||||
"released": "2019-03-26"
|
||||
},
|
||||
"0.5.8": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"DynamicConstructorArgumentsClippedABIV2"
|
||||
],
|
||||
"released": "2019-04-30"
|
||||
},
|
||||
"0.5.9": {
|
||||
"bugs": [],
|
||||
"released": "2019-05-28"
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ This means that cyclic creation dependencies are impossible.
|
||||
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
|
||||
contract OwnedToken {
|
||||
// `TokenCreator` is a contract type that is defined below.
|
||||
// It is fine to reference it as long as it is not used
|
||||
@ -86,6 +87,7 @@ This means that cyclic creation dependencies are impossible.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract TokenCreator {
|
||||
function createToken(bytes32 name)
|
||||
public
|
||||
|
@ -59,7 +59,9 @@ logs that match a topic with a certain address value:
|
||||
|
||||
The hash of the signature of the event is one of the topics, except if you
|
||||
declared the event with the ``anonymous`` specifier. This means that it is
|
||||
not possible to filter for specific anonymous events by name.
|
||||
not possible to filter for specific anonymous events by name, you can
|
||||
only filter by the contract address. The advantage of anonymous events
|
||||
is that they are cheaper to deploy and call.
|
||||
|
||||
::
|
||||
|
||||
|
@ -12,7 +12,9 @@ is called, except when the contract name is explicitly given or the
|
||||
|
||||
When a contract inherits from other contracts, only a single
|
||||
contract is created on the blockchain, and the code from all the base contracts
|
||||
is compiled into the created contract.
|
||||
is compiled into the created contract. This means that all internal calls
|
||||
to functions of base contracts also just use internal function calls
|
||||
(``super.f(..)`` will use JUMP and not a message call).
|
||||
|
||||
The general inheritance system is very similar to
|
||||
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_,
|
||||
@ -25,21 +27,24 @@ Details are given in the following example.
|
||||
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract owned {
|
||||
|
||||
contract Owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
address payable owner;
|
||||
}
|
||||
|
||||
|
||||
// Use `is` to derive from another contract. Derived
|
||||
// contracts can access all non-private members including
|
||||
// internal functions and state variables. These cannot be
|
||||
// accessed externally via `this`, though.
|
||||
contract mortal is owned {
|
||||
contract Mortal is Owned {
|
||||
function kill() public {
|
||||
if (msg.sender == owner) selfdestruct(owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// These abstract contracts are only provided to make the
|
||||
// interface known to the compiler. Note the function
|
||||
// without body. If a contract does not implement all
|
||||
@ -48,15 +53,17 @@ Details are given in the following example.
|
||||
function lookup(uint id) public returns (address adr);
|
||||
}
|
||||
|
||||
|
||||
contract NameReg {
|
||||
function register(bytes32 name) public;
|
||||
function unregister() public;
|
||||
}
|
||||
|
||||
|
||||
// Multiple inheritance is possible. Note that `owned` is
|
||||
// also a base class of `mortal`, yet there is only a single
|
||||
// instance of `owned` (as for virtual inheritance in C++).
|
||||
contract named is owned, mortal {
|
||||
contract Named is Owned, Mortal {
|
||||
constructor(bytes32 name) public {
|
||||
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
|
||||
NameReg(config.lookup(1)).register(name);
|
||||
@ -73,15 +80,16 @@ Details are given in the following example.
|
||||
NameReg(config.lookup(1)).unregister();
|
||||
// It is still possible to call a specific
|
||||
// overridden function.
|
||||
mortal.kill();
|
||||
Mortal.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If a constructor takes an argument, it needs to be
|
||||
// provided in the header (or modifier-invocation-style at
|
||||
// the constructor of the derived contract (see below)).
|
||||
contract PriceFeed is owned, mortal, named("GoldFeed") {
|
||||
contract PriceFeed is Owned, Mortal, Named("GoldFeed") {
|
||||
function updateInfo(uint newInfo) public {
|
||||
if (msg.sender == owner) info = newInfo;
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ more advanced example to implement a set).
|
||||
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
|
||||
library Set {
|
||||
// We define a new struct datatype that will be used to
|
||||
// hold its data in the calling contract.
|
||||
@ -89,6 +90,7 @@ more advanced example to implement a set).
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract C {
|
||||
Set.Data knownValues;
|
||||
|
||||
|
@ -43,7 +43,7 @@ For state variables, ``external`` is not possible.
|
||||
.. note::
|
||||
Everything that is inside a contract is visible to
|
||||
all observers external to the blockchain. Making something ``private``
|
||||
only prevents other contracts from accessing and modifying
|
||||
only prevents other contracts from reading or modifying
|
||||
the information, but it will still be visible to the
|
||||
whole world outside of the blockchain.
|
||||
|
||||
|
@ -96,7 +96,7 @@ The option ``--no-smt`` disables the tests that require ``libz3`` and
|
||||
``--no-ipc`` disables those that require ``aleth``.
|
||||
|
||||
If you want to run the ipc tests (that test the semantics of the generated code),
|
||||
you need to install `aleth <https://github.com/ethereum/aleth/releases/download/v1.6.0-rc.1/aleth-1.6.0-rc.1-linux-x86_64.tar.gz>`_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``.
|
||||
you need to install `aleth <https://github.com/ethereum/aleth/releases/download/v1.6.0/aleth-1.6.0-linux-x86_64.tar.gz>`_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``.
|
||||
|
||||
To run the actual tests, use: ``./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc``.
|
||||
|
||||
|
@ -4,10 +4,18 @@
|
||||
Modular Contracts
|
||||
*****************
|
||||
|
||||
A modular approach to building your contracts helps you prevent overflow risks
|
||||
and unexpected tokens. In the example below, the contract uses the ``move`` method
|
||||
A modular approach to building your contracts helps you reduce the complexity
|
||||
and improve the readability which will help to identify bugs and vulnerabilities
|
||||
during development and code review.
|
||||
If you specify and control the behaviour or each module in isolation, the
|
||||
interactions you have to consider are only those between the module specifications
|
||||
and not every other moving part of the contract.
|
||||
In the example below, the contract uses the ``move`` method
|
||||
of the ``Balances`` :ref:`library <libraries>` to check that balances sent between
|
||||
addresses match what you expect.
|
||||
addresses match what you expect. In this way, the ``Balances`` library
|
||||
provides an isolated component that properly tracks balances of accounts.
|
||||
It is easy to verify that the ``Balances`` library never produces negative balances or overflows
|
||||
and the sum of all balances is an invariant across the lifetime of the contract.
|
||||
|
||||
::
|
||||
|
||||
|
@ -20,10 +20,11 @@ Remix
|
||||
|
||||
*We recommend Remix for small contracts and for quickly learning Solidity.*
|
||||
|
||||
`Access Remix online <https://remix.ethereum.org/>`_, you don't need to install anything.
|
||||
`Access Remix online <https://remix.ethereum.org/>`_, you do not need to install anything.
|
||||
If you want to use it without connection to the Internet, go to
|
||||
https://github.com/ethereum/remix-live/tree/gh-pages and download the ``.zip`` file as
|
||||
explained on that page.
|
||||
explained on that page. Remix is also a convenient option for testing nightly builds
|
||||
without installing multiple Solidity versions.
|
||||
|
||||
Further options on this page detail installing commandline Solidity compiler software
|
||||
on your computer. Choose a commandline compiler if you are working on a larger contract
|
||||
@ -60,17 +61,36 @@ Please refer to the solc-js repository for instructions.
|
||||
Docker
|
||||
======
|
||||
|
||||
We provide up to date docker builds for the compiler. The ``stable``
|
||||
repository contains released versions while the ``nightly``
|
||||
repository contains potentially unstable changes in the develop branch.
|
||||
Docker images of Solidity builds are available using the ``solc`` image from the ``ethereum`` organisation.
|
||||
Use the ``stable`` tag for the latest released version, and ``nightly`` for potentially unstable changes in the develop branch.
|
||||
|
||||
The Docker image runs the compiler executable, so you can pass all compiler arguments to it.
|
||||
For example, the command below pulls the stable version of the ``solc`` image (if you do not have it already),
|
||||
and runs it in a new container, passing the ``--help`` argument.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run ethereum/solc:stable --version
|
||||
docker run ethereum/solc:stable --help
|
||||
|
||||
Currently, the docker image only contains the compiler executable,
|
||||
so you have to do some additional work to link in the source and
|
||||
output directories.
|
||||
You can also specify release build versions in the tag, for example, for the 0.5.4 release.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run ethereum/solc:0.5.4 --help
|
||||
|
||||
To use the Docker image to compile Solidity files on the host machine mount a
|
||||
local folder for input and output, and specify the contract to compile. For example.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -v /local/path:/sources ethereum/solc:stable -o /sources/output --abi --bin /sources/Contract.sol
|
||||
|
||||
You can also use the standard JSON interface (which is recommended when using the compiler with tooling).
|
||||
When using this interface it is not necessary to mount any directories.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run ethereum/solc:stable --standard-json < input.json > output.json
|
||||
|
||||
Binary Packages
|
||||
===============
|
||||
|
@ -31,10 +31,9 @@ Storage Example
|
||||
}
|
||||
}
|
||||
|
||||
The first line simply tells that the source code is written for
|
||||
Solidity version 0.4.0 or anything newer that does not break functionality
|
||||
(up to, but not including, version 0.7.0). This is to ensure that the
|
||||
contract is not compilable with a new (breaking) compiler version, where it could behave differently.
|
||||
The first line tells you that the source code is written for
|
||||
Solidity version 0.4.0, or a newer version of the language up to, but not including version 0.7.0.
|
||||
This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently.
|
||||
:ref:`Pragmas<pragma>` are common instructions for compilers about how to treat the
|
||||
source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_).
|
||||
|
||||
@ -42,9 +41,9 @@ A contract in the sense of Solidity is a collection of code (its *functions*) an
|
||||
data (its *state*) that resides at a specific address on the Ethereum
|
||||
blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of
|
||||
type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot
|
||||
in a database that can be queried and altered by calling functions of the
|
||||
in a database that you can query and alter by calling functions of the
|
||||
code that manages the database. In the case of Ethereum, this is always the owning
|
||||
contract. And in this case, the functions ``set`` and ``get`` can be used to modify
|
||||
contract. In this case, the functions ``set`` and ``get`` can be used to modify
|
||||
or retrieve the value of the variable.
|
||||
|
||||
To access a state variable, you do not need the prefix ``this.`` as is common in
|
||||
@ -53,9 +52,9 @@ other languages.
|
||||
This contract does not do much yet apart from (due to the infrastructure
|
||||
built by Ethereum) allowing anyone to store a single number that is accessible by
|
||||
anyone in the world without a (feasible) way to prevent you from publishing
|
||||
this number. Of course, anyone could just call ``set`` again with a different value
|
||||
and overwrite your number, but the number will still be stored in the history
|
||||
of the blockchain. Later, we will see how you can impose access restrictions
|
||||
this number. Anyone could call ``set`` again with a different value
|
||||
and overwrite your number, but the number is still stored in the history
|
||||
of the blockchain. Later, you will see how you can impose access restrictions
|
||||
so that only you can alter the number.
|
||||
|
||||
.. note::
|
||||
@ -64,7 +63,7 @@ so that only you can alter the number.
|
||||
|
||||
.. warning::
|
||||
Be careful with using Unicode text, as similar looking (or even identical) characters can
|
||||
have different code points and as such will be encoded as a different byte array.
|
||||
have different code points and as such are encoded as a different byte array.
|
||||
|
||||
.. index:: ! subcurrency
|
||||
|
||||
|
@ -124,30 +124,35 @@ Encoding of the Metadata Hash in the Bytecode
|
||||
=============================================
|
||||
|
||||
Because we might support other ways to retrieve the metadata file in the future,
|
||||
the mapping ``{"bzzr0": <Swarm hash>}`` is stored
|
||||
the mapping ``{"bzzr0": <Swarm hash>, "solc": <compiler version>}`` is stored
|
||||
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might
|
||||
contain more keys (see below) and the beginning of that
|
||||
encoding is not easy to find, its length is added in a two-byte big-endian
|
||||
encoding. The current version of the Solidity compiler usually adds the following
|
||||
to the end of the deployed bytecode::
|
||||
|
||||
0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
|
||||
0xa2
|
||||
0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash>
|
||||
0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding>
|
||||
0x00 0x32
|
||||
|
||||
So in order to retrieve the data, the end of the deployed bytecode can be checked
|
||||
to match that pattern and use the Swarm hash to retrieve the file.
|
||||
|
||||
Whereas release builds of solc use a 3 byte encoding of the version as shown
|
||||
above (one byte each for major, minor and patch version number), prerelease builds
|
||||
will instead use a complete version string including commit hash and build date.
|
||||
|
||||
.. note::
|
||||
The CBOR mapping can also contain other keys, so it is better to fully
|
||||
decode the data instead of relying on it starting with ``0xa165``.
|
||||
decode the data instead of relying on it starting with ``0xa265``.
|
||||
For example, if any experimental features that affect code generation
|
||||
are used, the mapping will also contain ``"experimental": true``.
|
||||
Furthermore, we are planning to add the compiler version to the mapping
|
||||
to ease source-verification and scanning for bugs.
|
||||
|
||||
.. note::
|
||||
The compiler currently uses the "swarm version 0" hash of the metadata,
|
||||
but this might change in the future, so do not rely on this sequence
|
||||
to start with ``0xa1 0x65 'b' 'z' 'z' 'r' '0'``. We might also
|
||||
to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '0'``. We might also
|
||||
add additional data to this CBOR structure, so the
|
||||
best option is to use a proper CBOR parser.
|
||||
|
||||
|
@ -20,7 +20,8 @@ to take too much care, but if you manage your bank account using that web servic
|
||||
you should be more careful.
|
||||
|
||||
This section will list some pitfalls and general security recommendations but
|
||||
can, of course, never be complete. Also, keep in mind that even if your
|
||||
can, of course, never be complete.
|
||||
Also, keep in mind that even if your
|
||||
smart contract code is bug-free, the compiler or the platform itself might
|
||||
have a bug. A list of some publicly known security-relevant bugs of the compiler
|
||||
can be found in the
|
||||
@ -31,6 +32,10 @@ Solidity compiler.
|
||||
As always, with open source documentation, please help us extend this section
|
||||
(especially, some examples would not hurt)!
|
||||
|
||||
NOTE: In addition to the list below, you can find more security recommendations and best practices
|
||||
`in Guy Lando's knowledge list <https://github.com/guylando/KnowledgeLists/blob/master/EthereumSmartContracts.md>`_ and
|
||||
`the Consensys GitHub repo <https://consensys.github.io/smart-contract-best-practices/>`_.
|
||||
|
||||
********
|
||||
Pitfalls
|
||||
********
|
||||
|
@ -26,11 +26,11 @@ doing, an explicit type conversion is sometimes possible. Note that this may
|
||||
give you some unexpected behaviour and allows you to bypass some security
|
||||
features of the compiler, so be sure to test that the
|
||||
result is what you want! Take the following example where you are converting
|
||||
a negative ``int8`` to a ``uint``:
|
||||
a negative ``int`` to a ``uint``:
|
||||
|
||||
::
|
||||
|
||||
int8 y = -3;
|
||||
int y = -3;
|
||||
uint x = uint(y);
|
||||
|
||||
At the end of this code snippet, ``x`` will have the value ``0xfffff..fd`` (64 hex
|
||||
|
@ -7,6 +7,8 @@ If ``a`` is an LValue (i.e. a variable or something that can be assigned to), th
|
||||
|
||||
``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change.
|
||||
|
||||
.. _delete:
|
||||
|
||||
delete
|
||||
------
|
||||
|
||||
|
@ -217,13 +217,13 @@ Array Members
|
||||
For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array.
|
||||
Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion.
|
||||
Increasing the length adds new zero-initialised elements to the array.
|
||||
Reducing the length performs an implicit :ref:``delete`` on each of the
|
||||
Reducing the length performs an implicit :ref:`delete<delete>` on each of the
|
||||
removed elements. If you try to resize a non-dynamic array that isn't in
|
||||
storage, you receive a ``Value must be an lvalue`` error.
|
||||
**push**:
|
||||
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length.
|
||||
**pop**:
|
||||
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:``delete`` on the removed element.
|
||||
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:`delete<delete>` on the removed element.
|
||||
|
||||
.. warning::
|
||||
If you use ``.length--`` on an empty array, it causes an underflow and
|
||||
@ -234,7 +234,7 @@ Array Members
|
||||
storage is assumed to be zero-initialised, while decreasing
|
||||
the length has at least linear cost (but in most cases worse than linear),
|
||||
because it includes explicitly clearing the removed
|
||||
elements similar to calling :ref:``delete`` on them.
|
||||
elements similar to calling :ref:`delete<delete>` on them.
|
||||
|
||||
.. note::
|
||||
It is not yet possible to use arrays of arrays in external functions
|
||||
@ -371,7 +371,8 @@ shown in the following example:
|
||||
campaignID = numCampaigns++; // campaignID is return variable
|
||||
// Creates new struct in memory and copies it to storage.
|
||||
// We leave out the mapping type, because it is not valid in memory.
|
||||
// If structs are copied (even from storage to storage), mapping types
|
||||
// If structs are copied (even from storage to storage),
|
||||
// types that are not valid outside of storage (ex. mappings and array of mappings)
|
||||
// are always omitted, because they cannot be enumerated.
|
||||
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ Mathematical and Cryptographic Functions
|
||||
the ecrecover function remained unchanged.
|
||||
|
||||
This is usually not a problem unless you require signatures to be unique or
|
||||
use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/docs/cryptography_ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue.
|
||||
use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/v2.3.0/api/cryptography#ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue.
|
||||
|
||||
.. note::
|
||||
|
||||
|
43
docs/yul.rst
43
docs/yul.rst
@ -47,20 +47,21 @@ if the backend changes. For a list of mandatory built-in functions, see the sect
|
||||
|
||||
The following example program assumes that the EVM opcodes ``mul``, ``div``
|
||||
and ``mod`` are available either natively or as functions and computes exponentiation.
|
||||
As per the warning above, the following code is untyped and can be compiled using ``solc --strict-assembly``.
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
function power(base:u256, exponent:u256) -> result:u256
|
||||
function power(base, exponent) -> result
|
||||
{
|
||||
switch exponent
|
||||
case 0:u256 { result := 1:u256 }
|
||||
case 1:u256 { result := base }
|
||||
case 0 { result := 1 }
|
||||
case 1 { result := base }
|
||||
default
|
||||
{
|
||||
result := power(mul(base, base), div(exponent, 2:u256))
|
||||
switch mod(exponent, 2:u256)
|
||||
case 1:u256 { result := mul(base, result) }
|
||||
result := power(mul(base, base), div(exponent, 2))
|
||||
switch mod(exponent, 2)
|
||||
case 1 { result := mul(base, result) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,10 +73,10 @@ and ``add`` to be available.
|
||||
.. code::
|
||||
|
||||
{
|
||||
function power(base:u256, exponent:u256) -> result:u256
|
||||
function power(base, exponent) -> result
|
||||
{
|
||||
result := 1:u256
|
||||
for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) }
|
||||
result := 1
|
||||
for { let i := 0 } lt(i, exponent) { i := add(i, 1) }
|
||||
{
|
||||
result := mul(result, base)
|
||||
}
|
||||
@ -593,15 +594,21 @@ An example Yul Object is shown below:
|
||||
// are in scope without nested access.
|
||||
object "Contract1" {
|
||||
code {
|
||||
function allocate(size) -> ptr {
|
||||
ptr := mload(0x40)
|
||||
if iszero(ptr) { ptr := 0x60 }
|
||||
mstore(0x40, add(ptr, size))
|
||||
}
|
||||
|
||||
// first create "runtime.Contract2"
|
||||
let size = datasize("runtime.Contract2")
|
||||
let offset = allocate(size)
|
||||
let size := datasize("runtime.Contract2")
|
||||
let offset := allocate(size)
|
||||
// This will turn into a memory->memory copy for eWASM and
|
||||
// a codecopy for EVM
|
||||
datacopy(offset, dataoffset("runtime.Contract2"), size)
|
||||
// constructor parameter is a single number 0x1234
|
||||
mstore(add(offset, size), 0x1234)
|
||||
create(offset, add(size, 32))
|
||||
pop(create(offset, add(size, 32), 0))
|
||||
|
||||
// now return the runtime object (this is
|
||||
// constructor code)
|
||||
@ -617,16 +624,22 @@ An example Yul Object is shown below:
|
||||
|
||||
object "runtime" {
|
||||
code {
|
||||
function allocate(size) -> ptr {
|
||||
ptr := mload(0x40)
|
||||
if iszero(ptr) { ptr := 0x60 }
|
||||
mstore(0x40, add(ptr, size))
|
||||
}
|
||||
|
||||
// runtime code
|
||||
|
||||
let size = datasize("Contract2")
|
||||
let offset = allocate(size)
|
||||
let size := datasize("Contract2")
|
||||
let offset := allocate(size)
|
||||
// This will turn into a memory->memory copy for eWASM and
|
||||
// a codecopy for EVM
|
||||
datacopy(offset, dataoffset("Contract2"), size)
|
||||
// constructor parameter is a single number 0x1234
|
||||
mstore(add(offset, size), 0x1234)
|
||||
create(offset, add(size, 32))
|
||||
pop(create(offset, add(size, 32), 0))
|
||||
}
|
||||
|
||||
// Embedded object. Use case is that the outside is a factory contract,
|
||||
|
@ -12,10 +12,13 @@ set(sources
|
||||
FixedHash.h
|
||||
IndentedWriter.cpp
|
||||
IndentedWriter.h
|
||||
IpfsHash.cpp
|
||||
IpfsHash.h
|
||||
JSON.cpp
|
||||
JSON.h
|
||||
Keccak256.cpp
|
||||
Keccak256.h
|
||||
picosha2.h
|
||||
Result.h
|
||||
StringUtils.cpp
|
||||
StringUtils.h
|
||||
|
@ -91,6 +91,20 @@ inline u256 s2u(s256 _u)
|
||||
return u256(c_end + _u);
|
||||
}
|
||||
|
||||
inline u256 exp256(u256 _base, u256 _exponent)
|
||||
{
|
||||
using boost::multiprecision::limb_type;
|
||||
u256 result = 1;
|
||||
while (_exponent)
|
||||
{
|
||||
if (boost::multiprecision::bit_test(_exponent, 0))
|
||||
result *= _base;
|
||||
_base *= _base;
|
||||
_exponent >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
|
@ -111,7 +111,7 @@ enum class HexCase
|
||||
/// @example toHex("A\x69") == "4169"
|
||||
std::string toHex(bytes const& _data, HexPrefix _prefix = HexPrefix::DontAdd, HexCase _case = HexCase::Lower);
|
||||
|
||||
/// Converts a (printable) ASCII hex character into the correspnding integer value.
|
||||
/// Converts a (printable) ASCII hex character into the corresponding integer value.
|
||||
/// @example fromHex('A') == 10 && fromHex('f') == 15 && fromHex('5') == 5
|
||||
int fromHex(char _i, WhenError _throw);
|
||||
|
||||
@ -188,12 +188,6 @@ inline bytes toCompactBigEndian(uint8_t _val, unsigned _min = 0)
|
||||
return (_min || _val) ? bytes{ _val } : bytes{};
|
||||
}
|
||||
|
||||
/// Workarounds shift left bug in boost <1.65.1.
|
||||
template <class S> S bigintShiftLeftWorkaround(S const& _a, unsigned _b)
|
||||
{
|
||||
return (S)(bigint(_a) << _b);
|
||||
}
|
||||
|
||||
/// Convenience function for conversion of a u256 to hex
|
||||
inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd)
|
||||
{
|
||||
@ -352,4 +346,27 @@ inline std::string findAnyOf(std::string const& _haystack, std::vector<std::stri
|
||||
return needle;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template<typename T>
|
||||
void variadicEmplaceBack(std::vector<T>&) {}
|
||||
template<typename T, typename A, typename... Args>
|
||||
void variadicEmplaceBack(std::vector<T>& _vector, A&& _a, Args&&... _args)
|
||||
{
|
||||
_vector.emplace_back(std::forward<A>(_a));
|
||||
variadicEmplaceBack(_vector, std::forward<Args>(_args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
std::vector<T> make_vector(Args&&... _args)
|
||||
{
|
||||
std::vector<T> result;
|
||||
result.reserve(sizeof...(_args));
|
||||
detail::variadicEmplaceBack(result, std::forward<Args>(_args)...);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ private:
|
||||
DEV_SIMPLE_EXCEPTION(InvalidAddress);
|
||||
DEV_SIMPLE_EXCEPTION(BadHexCharacter);
|
||||
DEV_SIMPLE_EXCEPTION(FileError);
|
||||
DEV_SIMPLE_EXCEPTION(DataTooLong);
|
||||
|
||||
// error information to be added to exceptions
|
||||
using errinfo_invalidSymbol = boost::error_info<struct tag_invalidSymbol, char>;
|
||||
|
93
libdevcore/IpfsHash.cpp
Normal file
93
libdevcore/IpfsHash.cpp
Normal 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
37
libdevcore/IpfsHash.h
Normal 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);
|
||||
|
||||
}
|
@ -74,6 +74,22 @@ std::string joinHumanReadable
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Joins collection of strings just like joinHumanReadable, but prepends the separator
|
||||
/// unless the collection is empty.
|
||||
template<class T>
|
||||
std::string joinHumanReadablePrefixed
|
||||
(
|
||||
T const& _list,
|
||||
std::string const& _separator = ", ",
|
||||
std::string const& _lastSeparator = ""
|
||||
)
|
||||
{
|
||||
if (begin(_list) == end(_list))
|
||||
return {};
|
||||
else
|
||||
return _separator + joinHumanReadable(_list, _separator, _lastSeparator);
|
||||
}
|
||||
|
||||
/// Formats large numbers to be easily readable by humans.
|
||||
/// Returns decimal representation for smaller numbers; hex for large numbers.
|
||||
/// "Special" numbers, powers-of-two and powers-of-two minus 1, are returned in
|
||||
|
@ -30,64 +30,73 @@
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
|
||||
Whiskers::Whiskers(string const& _template):
|
||||
m_template(_template)
|
||||
Whiskers::Whiskers(string _template):
|
||||
m_template(move(_template))
|
||||
{
|
||||
}
|
||||
|
||||
Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value)
|
||||
Whiskers& Whiskers::operator()(string _parameter, string _value)
|
||||
{
|
||||
assertThrow(
|
||||
m_parameters.count(_parameter) == 0,
|
||||
WhiskersError,
|
||||
_parameter + " already set."
|
||||
);
|
||||
assertThrow(
|
||||
m_listParameters.count(_parameter) == 0,
|
||||
WhiskersError,
|
||||
_parameter + " already set as list parameter."
|
||||
);
|
||||
m_parameters[_parameter] = _value;
|
||||
|
||||
checkParameterUnknown(_parameter);
|
||||
m_parameters[move(_parameter)] = move(_value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Whiskers& Whiskers::operator ()(
|
||||
string const& _listParameter,
|
||||
vector<map<string, string>> const& _values
|
||||
Whiskers& Whiskers::operator()(string _parameter, bool _value)
|
||||
{
|
||||
checkParameterUnknown(_parameter);
|
||||
m_conditions[move(_parameter)] = _value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Whiskers& Whiskers::operator()(
|
||||
string _listParameter,
|
||||
vector<map<string, string>> _values
|
||||
)
|
||||
{
|
||||
assertThrow(
|
||||
m_listParameters.count(_listParameter) == 0,
|
||||
WhiskersError,
|
||||
_listParameter + " already set."
|
||||
);
|
||||
assertThrow(
|
||||
m_parameters.count(_listParameter) == 0,
|
||||
WhiskersError,
|
||||
_listParameter + " already set as value parameter."
|
||||
);
|
||||
m_listParameters[_listParameter] = _values;
|
||||
|
||||
checkParameterUnknown(_listParameter);
|
||||
m_listParameters[move(_listParameter)] = move(_values);
|
||||
return *this;
|
||||
}
|
||||
|
||||
string Whiskers::render() const
|
||||
{
|
||||
return replace(m_template, m_parameters, m_listParameters);
|
||||
return replace(m_template, m_parameters, m_conditions, m_listParameters);
|
||||
}
|
||||
|
||||
void Whiskers::checkParameterUnknown(string const& _parameter)
|
||||
{
|
||||
assertThrow(
|
||||
!m_parameters.count(_parameter),
|
||||
WhiskersError,
|
||||
_parameter + " already set as value parameter."
|
||||
);
|
||||
assertThrow(
|
||||
!m_conditions.count(_parameter),
|
||||
WhiskersError,
|
||||
_parameter + " already set as condition parameter."
|
||||
);
|
||||
assertThrow(
|
||||
!m_listParameters.count(_parameter),
|
||||
WhiskersError,
|
||||
_parameter + " already set as list parameter."
|
||||
);
|
||||
}
|
||||
|
||||
string Whiskers::replace(
|
||||
string const& _template,
|
||||
StringMap const& _parameters,
|
||||
map<string, bool> const& _conditions,
|
||||
map<string, vector<StringMap>> const& _listParameters
|
||||
)
|
||||
{
|
||||
using namespace boost;
|
||||
static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>");
|
||||
static regex listOrTag("<([^#/?!>]+)>|<#([^>]+)>(.*?)</\\2>|<\\?([^>]+)>(.*?)(<!\\4>(.*?))?</\\4>");
|
||||
return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string
|
||||
{
|
||||
string tagName(_match[1]);
|
||||
string listName(_match[2]);
|
||||
string conditionName(_match[4]);
|
||||
if (!tagName.empty())
|
||||
{
|
||||
assertThrow(
|
||||
@ -99,20 +108,32 @@ string Whiskers::replace(
|
||||
);
|
||||
return _parameters.at(tagName);
|
||||
}
|
||||
else
|
||||
else if (!listName.empty())
|
||||
{
|
||||
string listName(_match[2]);
|
||||
string templ(_match[3]);
|
||||
assertThrow(!listName.empty(), WhiskersError, "");
|
||||
assertThrow(
|
||||
_listParameters.count(listName),
|
||||
WhiskersError, "List parameter " + listName + " not set."
|
||||
);
|
||||
string replacement;
|
||||
for (auto const& parameters: _listParameters.at(listName))
|
||||
replacement += replace(templ, joinMaps(_parameters, parameters));
|
||||
replacement += replace(templ, joinMaps(_parameters, parameters), _conditions);
|
||||
return replacement;
|
||||
}
|
||||
else
|
||||
{
|
||||
assertThrow(!conditionName.empty(), WhiskersError, "");
|
||||
assertThrow(
|
||||
_conditions.count(conditionName),
|
||||
WhiskersError, "Condition parameter " + conditionName + " not set."
|
||||
);
|
||||
return replace(
|
||||
_conditions.at(conditionName) ? _match[5] : _match[7],
|
||||
_parameters,
|
||||
_conditions,
|
||||
_listParameters
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -34,45 +34,63 @@ namespace dev
|
||||
|
||||
DEV_SIMPLE_EXCEPTION(WhiskersError);
|
||||
|
||||
///
|
||||
/// Moustache-like templates.
|
||||
///
|
||||
/// Usage:
|
||||
/// std::vector<std::map<std::string, std::string>> listValues(2);
|
||||
/// listValues[0]["k"] = "key1";
|
||||
/// listValues[0]["v"] = "value1";
|
||||
/// listValues[1]["k"] = "key2";
|
||||
/// listValues[1]["v"] = "value2";
|
||||
/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>")
|
||||
/// ("p1", "HEAD")
|
||||
/// ("list", listValues)
|
||||
/// .render();
|
||||
///
|
||||
/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
|
||||
///
|
||||
/// Note that lists cannot themselves contain lists - this would be a future feature.
|
||||
/**
|
||||
* Moustache-like templates.
|
||||
*
|
||||
* Usage:
|
||||
* std::vector<std::map<std::string, std::string>> listValues(2);
|
||||
* listValues[0]["k"] = "key1";
|
||||
* listValues[0]["v"] = "value1";
|
||||
* listValues[1]["k"] = "key2";
|
||||
* listValues[1]["v"] = "value2";
|
||||
* auto s = Whiskers("<?c><p1><!c>y</c>\n<#list><k> -> <v>\n</list>")
|
||||
* ("p1", "HEAD")
|
||||
* ("c", true)
|
||||
* ("list", listValues)
|
||||
* .render();
|
||||
*
|
||||
* results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
|
||||
*
|
||||
* Note that lists cannot themselves contain lists - this would be a future feature.
|
||||
*
|
||||
* The elements are:
|
||||
* - Regular parameter: <name>
|
||||
* just replaced
|
||||
* - Condition parameter: <?name>...<!name>...</name>, where "<!name>" is optional
|
||||
* replaced (and recursively expanded) by the first part if the condition is true
|
||||
* and by the second (or empty string if missing) if the condition is false
|
||||
* - List parameter: <#list>...</list>
|
||||
* The part between the tags is repeated as often as values are provided
|
||||
* in the mapping. Each list element can have its own parameter -> value mapping.
|
||||
*/
|
||||
class Whiskers
|
||||
{
|
||||
public:
|
||||
using StringMap = std::map<std::string, std::string>;
|
||||
using StringListMap = std::map<std::string, std::vector<StringMap>>;
|
||||
|
||||
explicit Whiskers(std::string const& _template);
|
||||
explicit Whiskers(std::string _template);
|
||||
|
||||
/// Sets a single parameter, <paramName>.
|
||||
Whiskers& operator()(std::string const& _parameter, std::string const& _value);
|
||||
/// Sets a single regular parameter, <paramName>.
|
||||
Whiskers& operator()(std::string _parameter, std::string _value);
|
||||
Whiskers& operator()(std::string _parameter, char const* _value) { return (*this)(_parameter, std::string{_value}); }
|
||||
/// Sets a condition parameter, <?paramName>...<!paramName>...</paramName>
|
||||
Whiskers& operator()(std::string _parameter, bool _value);
|
||||
/// Sets a list parameter, <#listName> </listName>.
|
||||
Whiskers& operator()(
|
||||
std::string const& _listParameter,
|
||||
std::vector<StringMap> const& _values
|
||||
std::string _listParameter,
|
||||
std::vector<StringMap> _values
|
||||
);
|
||||
|
||||
std::string render() const;
|
||||
|
||||
private:
|
||||
void checkParameterUnknown(std::string const& _parameter);
|
||||
|
||||
static std::string replace(
|
||||
std::string const& _template,
|
||||
StringMap const& _parameters,
|
||||
std::map<std::string, bool> const& _conditions,
|
||||
StringListMap const& _listParameters = StringListMap()
|
||||
);
|
||||
|
||||
@ -81,6 +99,7 @@ private:
|
||||
|
||||
std::string m_template;
|
||||
StringMap m_parameters;
|
||||
std::map<std::string, bool> m_conditions;
|
||||
StringListMap m_listParameters;
|
||||
};
|
||||
|
||||
|
288
libdevcore/picosha2.h
Normal file
288
libdevcore/picosha2.h
Normal 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
|
@ -25,6 +25,8 @@ public:
|
||||
using mutable_value_type = typename std::conditional<std::is_const<_T>::value, typename std::remove_const<_T>::type, _T>::type;
|
||||
using string_type = typename std::conditional<std::is_const<_T>::value, std::string const, std::string>::type;
|
||||
using vector_type = typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const, std::vector<_T>>::type;
|
||||
using iterator = _T*;
|
||||
using const_iterator = _T const*;
|
||||
|
||||
static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data.");
|
||||
|
||||
|
@ -151,6 +151,7 @@ bigint CodeCopyMethod::gasNeeded() const
|
||||
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
|
||||
{
|
||||
bytes data = toBigEndian(m_value);
|
||||
assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
|
||||
AssemblyItems actualCopyRoutine = copyRoutine();
|
||||
actualCopyRoutine[4] = _assembly.newData(data);
|
||||
return actualCopyRoutine;
|
||||
@ -159,15 +160,25 @@ AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
|
||||
AssemblyItems const& CodeCopyMethod::copyRoutine()
|
||||
{
|
||||
AssemblyItems static copyRoutine{
|
||||
// constant to be reused 3+ times
|
||||
u256(0),
|
||||
|
||||
// back up memory
|
||||
// mload(0)
|
||||
Instruction::DUP1,
|
||||
Instruction::MLOAD, // back up memory
|
||||
Instruction::MLOAD,
|
||||
|
||||
// codecopy(0, <offset>, 32)
|
||||
u256(32),
|
||||
AssemblyItem(PushData, u256(1) << 16), // has to be replaced
|
||||
AssemblyItem(PushData, u256(1) << 16), // replaced above in actualCopyRoutine[4]
|
||||
Instruction::DUP4,
|
||||
Instruction::CODECOPY,
|
||||
|
||||
// mload(0)
|
||||
Instruction::DUP2,
|
||||
Instruction::MLOAD,
|
||||
|
||||
// restore original memory
|
||||
Instruction::SWAP2,
|
||||
Instruction::MSTORE
|
||||
};
|
||||
@ -211,11 +222,16 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
|
||||
if (lowerPart != 0)
|
||||
newRoutine += findRepresentation(u256(abs(lowerPart)));
|
||||
if (m_params.evmVersion.hasBitwiseShifting())
|
||||
newRoutine += AssemblyItems{u256(1), u256(bits), Instruction::SHL};
|
||||
{
|
||||
newRoutine += findRepresentation(upperPart);
|
||||
newRoutine += AssemblyItems{u256(bits), Instruction::SHL};
|
||||
}
|
||||
else
|
||||
{
|
||||
newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
|
||||
if (upperPart != 1)
|
||||
newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
|
||||
}
|
||||
if (lowerPart > 0)
|
||||
newRoutine += AssemblyItems{Instruction::ADD};
|
||||
else if (lowerPart < 0)
|
||||
|
@ -47,6 +47,13 @@ template <class S> S modWorkaround(S const& _a, S const& _b)
|
||||
return (S)(bigint(_a) % bigint(_b));
|
||||
}
|
||||
|
||||
// This works around a bug fixed with Boost 1.64.
|
||||
// https://www.boost.org/doc/libs/1_68_0/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64
|
||||
inline u256 shlWorkaround(u256 const& _x, unsigned _amount)
|
||||
{
|
||||
return u256((bigint(_x) << _amount) & u256(-1));
|
||||
}
|
||||
|
||||
// simplificationRuleList below was split up into parts to prevent
|
||||
// stack overflows in the JavaScript optimizer for emscripten builds
|
||||
// that affected certain browser versions.
|
||||
@ -93,7 +100,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1(
|
||||
{{Instruction::SHL, {A, B}}, [=]{
|
||||
if (A.d() > 255)
|
||||
return u256(0);
|
||||
return bigintShiftLeftWorkaround(B.d(), unsigned(A.d()));
|
||||
return shlWorkaround(B.d(), unsigned(A.d()));
|
||||
}, false},
|
||||
{{Instruction::SHR, {A, B}}, [=]{
|
||||
if (A.d() > 255)
|
||||
@ -365,6 +372,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
}
|
||||
}
|
||||
|
||||
// Combine two SHL by constant
|
||||
rules.push_back({
|
||||
// SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X)
|
||||
{Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}},
|
||||
@ -378,6 +386,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
false
|
||||
});
|
||||
|
||||
// Combine two SHR by constant
|
||||
rules.push_back({
|
||||
// SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X)
|
||||
{Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}},
|
||||
@ -391,6 +400,93 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
false
|
||||
});
|
||||
|
||||
// Combine SHL-SHR by constant
|
||||
rules.push_back({
|
||||
// SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask)
|
||||
{Instruction::SHR, {{B}, {Instruction::SHL, {{A}, {X}}}}},
|
||||
[=]() -> Pattern {
|
||||
u256 mask = shlWorkaround(u256(-1), unsigned(A.d())) >> unsigned(B.d());
|
||||
|
||||
if (A.d() > B.d())
|
||||
return {Instruction::AND, {{Instruction::SHL, {A.d() - B.d(), X}}, mask}};
|
||||
else if (B.d() > A.d())
|
||||
return {Instruction::AND, {{Instruction::SHR, {B.d() - A.d(), X}}, mask}};
|
||||
else
|
||||
return {Instruction::AND, {X, mask}};
|
||||
},
|
||||
false,
|
||||
[=] { return A.d() < 256 && B.d() < 256; }
|
||||
});
|
||||
|
||||
// Combine SHR-SHL by constant
|
||||
rules.push_back({
|
||||
// SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask)
|
||||
{Instruction::SHL, {{B}, {Instruction::SHR, {{A}, {X}}}}},
|
||||
[=]() -> Pattern {
|
||||
u256 mask = shlWorkaround(u256(-1) >> unsigned(A.d()), unsigned(B.d()));
|
||||
|
||||
if (A.d() > B.d())
|
||||
return {Instruction::AND, {{Instruction::SHR, {A.d() - B.d(), X}}, mask}};
|
||||
else if (B.d() > A.d())
|
||||
return {Instruction::AND, {{Instruction::SHL, {B.d() - A.d(), X}}, mask}};
|
||||
else
|
||||
return {Instruction::AND, {X, mask}};
|
||||
},
|
||||
false,
|
||||
[=] { return A.d() < 256 && B.d() < 256; }
|
||||
});
|
||||
|
||||
// Move AND with constant across SHL and SHR by constant
|
||||
for (auto shiftOp: {Instruction::SHL, Instruction::SHR})
|
||||
{
|
||||
auto replacement = [=]() -> Pattern {
|
||||
u256 mask =
|
||||
shiftOp == Instruction::SHL ?
|
||||
shlWorkaround(A.d(), unsigned(B.d())) :
|
||||
A.d() >> unsigned(B.d());
|
||||
return {Instruction::AND, {{shiftOp, {B.d(), X}}, std::move(mask)}};
|
||||
};
|
||||
rules.push_back({
|
||||
// SH[L/R](B, AND(X, A)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
|
||||
{shiftOp, {{B}, {Instruction::AND, {{X}, {A}}}}},
|
||||
replacement,
|
||||
false,
|
||||
[=] { return B.d() < 256; }
|
||||
});
|
||||
rules.push_back({
|
||||
// SH[L/R](B, AND(A, X)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
|
||||
{shiftOp, {{B}, {Instruction::AND, {{A}, {X}}}}},
|
||||
replacement,
|
||||
false,
|
||||
[=] { return B.d() < 256; }
|
||||
});
|
||||
}
|
||||
|
||||
rules.push_back({
|
||||
// MUL(X, SHL(Y, 1)) -> SHL(Y, X)
|
||||
{Instruction::MUL, {X, {Instruction::SHL, {Y, u256(1)}}}},
|
||||
[=]() -> Pattern {
|
||||
return {Instruction::SHL, {Y, X}};
|
||||
},
|
||||
false
|
||||
});
|
||||
rules.push_back({
|
||||
// MUL(SHL(X, 1), Y) -> SHL(X, Y)
|
||||
{Instruction::MUL, {{Instruction::SHL, {X, u256(1)}}, Y}},
|
||||
[=]() -> Pattern {
|
||||
return {Instruction::SHL, {X, Y}};
|
||||
},
|
||||
false
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
// DIV(X, SHL(Y, 1)) -> SHR(Y, X)
|
||||
{Instruction::DIV, {X, {Instruction::SHL, {Y, u256(1)}}}},
|
||||
[=]() -> Pattern {
|
||||
return {Instruction::SHR, {Y, X}};
|
||||
},
|
||||
false
|
||||
});
|
||||
|
||||
std::function<bool()> feasibilityFunction = [=]() {
|
||||
if (B.d() > 256)
|
||||
|
@ -136,7 +136,13 @@ bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item)
|
||||
{
|
||||
if (_item.type() != Operation)
|
||||
return false;
|
||||
switch (_item.instruction())
|
||||
else
|
||||
return terminatesControlFlow(_item.instruction());
|
||||
}
|
||||
|
||||
bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
|
||||
{
|
||||
switch (_instruction)
|
||||
{
|
||||
case Instruction::RETURN:
|
||||
case Instruction::SELFDESTRUCT:
|
||||
@ -202,6 +208,22 @@ bool SemanticInformation::movable(Instruction _instruction)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SemanticInformation::sideEffectFree(Instruction _instruction)
|
||||
{
|
||||
// These are not really functional.
|
||||
assertThrow(!isDupInstruction(_instruction) && !isSwapInstruction(_instruction), AssemblyException, "");
|
||||
|
||||
return !instructionInfo(_instruction).sideEffects;
|
||||
}
|
||||
|
||||
bool SemanticInformation::sideEffectFreeIfNoMSize(Instruction _instruction)
|
||||
{
|
||||
if (_instruction == Instruction::KECCAK256 || _instruction == Instruction::MLOAD)
|
||||
return true;
|
||||
else
|
||||
return sideEffectFree(_instruction);
|
||||
}
|
||||
|
||||
bool SemanticInformation::invalidatesMemory(Instruction _instruction)
|
||||
{
|
||||
switch (_instruction)
|
||||
|
@ -48,6 +48,7 @@ struct SemanticInformation
|
||||
static bool isJumpInstruction(AssemblyItem const& _item);
|
||||
static bool altersControlFlow(AssemblyItem const& _item);
|
||||
static bool terminatesControlFlow(AssemblyItem const& _item);
|
||||
static bool terminatesControlFlow(Instruction _instruction);
|
||||
/// @returns false if the value put on the stack by _item depends on anything else than
|
||||
/// the information in the current block header, memory, storage or stack.
|
||||
static bool isDeterministic(AssemblyItem const& _item);
|
||||
@ -55,6 +56,16 @@ struct SemanticInformation
|
||||
/// without altering the semantics. This means it cannot depend on storage or memory,
|
||||
/// cannot have any side-effects, but it can depend on a call-constant state of the blockchain.
|
||||
static bool movable(Instruction _instruction);
|
||||
/// @returns true if the instruction can be removed without changing the semantics.
|
||||
/// This does not mean that it has to be deterministic or retrieve information from
|
||||
/// somewhere else than purely the values of its arguments.
|
||||
static bool sideEffectFree(Instruction _instruction);
|
||||
/// @returns true if the instruction can be removed without changing the semantics.
|
||||
/// This does not mean that it has to be deterministic or retrieve information from
|
||||
/// somewhere else than purely the values of its arguments.
|
||||
/// If true, the instruction is still allowed to influence the value returned by the
|
||||
/// msize instruction.
|
||||
static bool sideEffectFreeIfNoMSize(Instruction _instruction);
|
||||
/// @returns true if the given instruction modifies memory.
|
||||
static bool invalidatesMemory(Instruction _instruction);
|
||||
/// @returns true if the given instruction modifies storage (even indirectly).
|
||||
|
@ -6,6 +6,7 @@ set(sources
|
||||
ErrorReporter.cpp
|
||||
ErrorReporter.h
|
||||
EVMVersion.h
|
||||
EVMVersion.cpp
|
||||
Exceptions.cpp
|
||||
Exceptions.h
|
||||
ParserBase.cpp
|
||||
|
@ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount)
|
||||
return get();
|
||||
}
|
||||
|
||||
char CharStream::setPosition(size_t _location)
|
||||
{
|
||||
solAssert(_location <= m_source.size(), "Attempting to set position past end of source.");
|
||||
m_position = _location;
|
||||
return get();
|
||||
}
|
||||
|
||||
string CharStream::lineAtPosition(int _position) const
|
||||
{
|
||||
// if _position points to \n, it returns the line before the \n
|
||||
@ -106,5 +113,3 @@ tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
|
||||
}
|
||||
return tuple<int, int>(lineNumber, searchPosition - lineStart);
|
||||
}
|
||||
|
||||
|
||||
|
@ -76,7 +76,13 @@ public:
|
||||
|
||||
char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; }
|
||||
char advanceAndGet(size_t _chars = 1);
|
||||
/// Sets scanner position to @ _amount characters backwards in source text.
|
||||
/// @returns The character of the current location after update is returned.
|
||||
char rollback(size_t _amount);
|
||||
/// Sets scanner position to @ _location if it refers a valid offset in m_source.
|
||||
/// If not, nothing is done.
|
||||
/// @returns The character of the current location after update is returned.
|
||||
char setPosition(size_t _location);
|
||||
|
||||
void reset() { m_position = 0; }
|
||||
|
||||
|
47
liblangutil/EVMVersion.cpp
Normal file
47
liblangutil/EVMVersion.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/operators.hpp>
|
||||
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
|
||||
@ -78,6 +81,8 @@ public:
|
||||
bool hasCreate2() const { return *this >= constantinople(); }
|
||||
bool hasExtCodeHash() const { return *this >= constantinople(); }
|
||||
|
||||
bool hasOpcode(dev::eth::Instruction _opcode) const;
|
||||
|
||||
/// Whether we have to retain the costs for the call opcode itself (false),
|
||||
/// or whether we can just forward easily all remaining gas (true).
|
||||
bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); }
|
||||
@ -90,5 +95,4 @@ private:
|
||||
Version m_version = Version::Petersburg;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
@ -86,6 +86,11 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se
|
||||
m_errorList.push_back(err);
|
||||
}
|
||||
|
||||
bool ErrorReporter::hasExcessiveErrors() const
|
||||
{
|
||||
return m_errorCount > c_maxErrorsAllowed;
|
||||
}
|
||||
|
||||
bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
|
||||
{
|
||||
if (_type == Error::Type::Warning)
|
||||
|
@ -118,6 +118,9 @@ public:
|
||||
return m_errorCount > 0;
|
||||
}
|
||||
|
||||
// @returns true if the maximum error count has been reached.
|
||||
bool hasExcessiveErrors() const;
|
||||
|
||||
private:
|
||||
void error(
|
||||
Error::Type _type,
|
||||
@ -149,4 +152,3 @@ private:
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const
|
||||
return m_scanner->peekNextToken();
|
||||
}
|
||||
|
||||
std::string ParserBase::currentLiteral() const
|
||||
string ParserBase::currentLiteral() const
|
||||
{
|
||||
return m_scanner->currentLiteral();
|
||||
}
|
||||
@ -57,30 +57,79 @@ Token ParserBase::advance()
|
||||
return m_scanner->next();
|
||||
}
|
||||
|
||||
string ParserBase::tokenName(Token _token)
|
||||
{
|
||||
if (_token == Token::Identifier)
|
||||
return "identifier";
|
||||
else if (_token == Token::EOS)
|
||||
return "end of source";
|
||||
else if (TokenTraits::isReservedKeyword(_token))
|
||||
return "reserved keyword '" + TokenTraits::friendlyName(_token) + "'";
|
||||
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
|
||||
{
|
||||
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
|
||||
return "'" + elemTypeName.toString() + "'";
|
||||
}
|
||||
else
|
||||
return "'" + TokenTraits::friendlyName(_token) + "'";
|
||||
}
|
||||
|
||||
void ParserBase::expectToken(Token _value, bool _advance)
|
||||
{
|
||||
Token tok = m_scanner->currentToken();
|
||||
if (tok != _value)
|
||||
{
|
||||
auto tokenName = [this](Token _token)
|
||||
string const expectedToken = ParserBase::tokenName(_value);
|
||||
if (m_parserErrorRecovery)
|
||||
parserError("Expected " + expectedToken + " but got " + tokenName(tok));
|
||||
else
|
||||
fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok));
|
||||
// Do not advance so that recovery can sync or make use of the current token.
|
||||
// This is especially useful if the expected token
|
||||
// is the only one that is missing and is at the end of a construct.
|
||||
// "{ ... ; }" is such an example.
|
||||
// ^
|
||||
_advance = false;
|
||||
}
|
||||
if (_advance)
|
||||
m_scanner->next();
|
||||
}
|
||||
|
||||
void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance)
|
||||
{
|
||||
Token tok = m_scanner->currentToken();
|
||||
if (tok != _value)
|
||||
{
|
||||
if (_token == Token::Identifier)
|
||||
return string("identifier");
|
||||
else if (_token == Token::EOS)
|
||||
return string("end of source");
|
||||
else if (TokenTraits::isReservedKeyword(_token))
|
||||
return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'";
|
||||
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
|
||||
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)
|
||||
{
|
||||
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
|
||||
return string("'") + elemTypeName.toString() + "'";
|
||||
// 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
|
||||
return string("'") + TokenTraits::friendlyName(_token) + "'";
|
||||
};
|
||||
|
||||
fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok));
|
||||
{
|
||||
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();
|
||||
}
|
||||
@ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth()
|
||||
m_recursionDepth--;
|
||||
}
|
||||
|
||||
void ParserBase::parserWarning(string const& _description)
|
||||
{
|
||||
m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description);
|
||||
}
|
||||
|
||||
void ParserBase::parserError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
m_errorReporter.parserError(_location, _description);
|
||||
}
|
||||
|
||||
void ParserBase::parserError(string const& _description)
|
||||
{
|
||||
m_errorReporter.parserError(SourceLocation{position(), endPosition(), source()}, _description);
|
||||
parserError(SourceLocation{position(), endPosition(), source()}, _description);
|
||||
}
|
||||
|
||||
void ParserBase::fatalParserError(string const& _description)
|
||||
{
|
||||
m_errorReporter.fatalParserError(SourceLocation{position(), endPosition(), source()}, _description);
|
||||
fatalParserError(SourceLocation{position(), endPosition(), source()}, _description);
|
||||
}
|
||||
|
||||
void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
m_errorReporter.fatalParserError(_location, _description);
|
||||
}
|
||||
|
@ -36,7 +36,14 @@ class Scanner;
|
||||
class ParserBase
|
||||
{
|
||||
public:
|
||||
explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {}
|
||||
/// Set @a _parserErrorRecovery to true for additional error
|
||||
/// recovery. This is experimental and intended for use
|
||||
/// by front-end tools that need partial AST information even
|
||||
/// when errors occur.
|
||||
explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter)
|
||||
{
|
||||
m_parserErrorRecovery = _parserErrorRecovery;
|
||||
}
|
||||
|
||||
std::shared_ptr<CharStream> source() const { return m_scanner->charStream(); }
|
||||
|
||||
@ -62,10 +69,17 @@ protected:
|
||||
|
||||
///@{
|
||||
///@name Helper functions
|
||||
/// If current token value is not _value, throw exception otherwise advance token.
|
||||
/// If current token value is not @a _value, throw exception otherwise advance token
|
||||
// @a if _advance is true and error recovery is in effect.
|
||||
void expectToken(Token _value, bool _advance = true);
|
||||
|
||||
/// Like expectToken but if there is an error ignores tokens until
|
||||
/// the expected token or EOS is seen. If EOS is encountered, back up to the error point,
|
||||
/// and throw an exception so that a higher grammar rule has an opportunity to recover.
|
||||
void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true);
|
||||
Token currentToken() const;
|
||||
Token peekNextToken() const;
|
||||
std::string tokenName(Token _token);
|
||||
std::string currentLiteral() const;
|
||||
Token advance();
|
||||
///@}
|
||||
@ -77,16 +91,26 @@ protected:
|
||||
/// Creates a @ref ParserError and annotates it with the current position and the
|
||||
/// given @a _description.
|
||||
void parserError(std::string const& _description);
|
||||
void parserError(SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
/// Creates a @ref ParserWarning and annotates it with the current position and the
|
||||
/// given @a _description.
|
||||
void parserWarning(std::string const& _description);
|
||||
|
||||
/// Creates a @ref ParserError and annotates it with the current position and the
|
||||
/// given @a _description. Throws the FatalError.
|
||||
void fatalParserError(std::string const& _description);
|
||||
void fatalParserError(SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
std::shared_ptr<Scanner> m_scanner;
|
||||
/// The reference to the list of errors and warning to add errors/warnings during parsing
|
||||
ErrorReporter& m_errorReporter;
|
||||
/// Current recursion depth during parsing.
|
||||
size_t m_recursionDepth = 0;
|
||||
/// True if we are in parser error recovery. Usually this means we are scanning for
|
||||
/// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages.
|
||||
bool m_inParserRecovery = false;
|
||||
bool m_parserErrorRecovery = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -156,6 +156,13 @@ void Scanner::reset()
|
||||
next();
|
||||
}
|
||||
|
||||
void Scanner::setPosition(size_t _offset)
|
||||
{
|
||||
m_char = m_source->setPosition(_offset);
|
||||
scanToken();
|
||||
next();
|
||||
}
|
||||
|
||||
void Scanner::supportPeriodInIdentifier(bool _value)
|
||||
{
|
||||
m_supportPeriodInIdentifier = _value;
|
||||
|
@ -110,6 +110,9 @@ public:
|
||||
/// @returns the next token and advances input
|
||||
Token next();
|
||||
|
||||
/// Set scanner to a specific offset. This is used in error recovery.
|
||||
void setPosition(size_t _offset);
|
||||
|
||||
///@{
|
||||
///@name Information about the current token
|
||||
|
||||
|
@ -21,10 +21,11 @@
|
||||
*/
|
||||
|
||||
#include <libsolc/libsolc.h>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <libdevcore/JSON.h>
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
#include <libyul/YulString.h>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <libdevcore/JSON.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -100,6 +101,9 @@ extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _
|
||||
}
|
||||
extern void solidity_free() noexcept
|
||||
{
|
||||
// This is called right before each compilation, but not at the end, so additional memory
|
||||
// can be freed here.
|
||||
yul::YulStringRepository::reset();
|
||||
s_outputBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ set(sources
|
||||
codegen/ir/IRGeneratorForStatements.h
|
||||
codegen/ir/IRGenerationContext.cpp
|
||||
codegen/ir/IRGenerationContext.h
|
||||
codegen/ir/IRLValue.cpp
|
||||
codegen/ir/IRLValue.h
|
||||
formal/EncodingContext.cpp
|
||||
formal/EncodingContext.h
|
||||
formal/SMTChecker.cpp
|
||||
|
@ -37,16 +37,17 @@ namespace solidity
|
||||
{
|
||||
|
||||
NameAndTypeResolver::NameAndTypeResolver(
|
||||
vector<Declaration const*> const& _globals,
|
||||
GlobalContext& _globalContext,
|
||||
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
|
||||
ErrorReporter& _errorReporter
|
||||
) :
|
||||
m_scopes(_scopes),
|
||||
m_errorReporter(_errorReporter)
|
||||
m_errorReporter(_errorReporter),
|
||||
m_globalContext(_globalContext)
|
||||
{
|
||||
if (!m_scopes[nullptr])
|
||||
m_scopes[nullptr].reset(new DeclarationContainer());
|
||||
for (Declaration const* declaration: _globals)
|
||||
for (Declaration const* declaration: _globalContext.declarations())
|
||||
{
|
||||
solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");
|
||||
}
|
||||
@ -57,7 +58,7 @@ bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode
|
||||
// The helper registers all declarations in m_scopes as a side-effect of its construction.
|
||||
try
|
||||
{
|
||||
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope);
|
||||
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, m_globalContext, _currentScope);
|
||||
}
|
||||
catch (langutil::FatalError const&)
|
||||
{
|
||||
@ -272,6 +273,10 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
|
||||
setScope(contract->scope());
|
||||
solAssert(!!m_currentScope, "");
|
||||
|
||||
m_globalContext.setCurrentContract(*contract);
|
||||
updateDeclaration(*m_globalContext.currentSuper());
|
||||
updateDeclaration(*m_globalContext.currentThis());
|
||||
|
||||
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
|
||||
if (!resolveNamesAndTypes(*baseContract, true))
|
||||
success = false;
|
||||
@ -452,11 +457,13 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper(
|
||||
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
|
||||
ASTNode& _astRoot,
|
||||
ErrorReporter& _errorReporter,
|
||||
GlobalContext& _globalContext,
|
||||
ASTNode const* _currentScope
|
||||
):
|
||||
m_scopes(_scopes),
|
||||
m_currentScope(_currentScope),
|
||||
m_errorReporter(_errorReporter)
|
||||
m_errorReporter(_errorReporter),
|
||||
m_globalContext(_globalContext)
|
||||
{
|
||||
_astRoot.accept(*this);
|
||||
solAssert(m_currentScope == _currentScope, "Scopes not correctly closed.");
|
||||
@ -560,6 +567,10 @@ bool DeclarationRegistrationHelper::visit(ImportDirective& _import)
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
|
||||
{
|
||||
m_globalContext.setCurrentContract(_contract);
|
||||
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, false, true);
|
||||
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true);
|
||||
|
||||
registerDeclaration(_contract, true);
|
||||
_contract.annotation().canonicalName = currentCanonicalName();
|
||||
return true;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/analysis/DeclarationContainer.h>
|
||||
#include <libsolidity/analysis/GlobalContext.h>
|
||||
#include <libsolidity/analysis/ReferencesResolver.h>
|
||||
#include <libsolidity/ast/ASTAnnotations.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
@ -53,7 +54,7 @@ public:
|
||||
/// @param _scopes mapping of scopes to be used (usually default constructed), these
|
||||
/// are filled during the lifetime of this object.
|
||||
NameAndTypeResolver(
|
||||
std::vector<Declaration const*> const& _globals,
|
||||
GlobalContext& _globalContext,
|
||||
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
|
||||
langutil::ErrorReporter& _errorReporter
|
||||
);
|
||||
@ -131,6 +132,7 @@ private:
|
||||
|
||||
DeclarationContainer* m_currentScope = nullptr;
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
GlobalContext& m_globalContext;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -148,6 +150,7 @@ public:
|
||||
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
|
||||
ASTNode& _astRoot,
|
||||
langutil::ErrorReporter& _errorReporter,
|
||||
GlobalContext& _globalContext,
|
||||
ASTNode const* _currentScope = nullptr
|
||||
);
|
||||
|
||||
@ -200,6 +203,7 @@ private:
|
||||
ASTNode const* m_currentScope = nullptr;
|
||||
VariableScope* m_currentFunction = nullptr;
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
GlobalContext& m_globalContext;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -309,6 +309,19 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
|
||||
"Arithmetic modulo zero."
|
||||
);
|
||||
}
|
||||
if (
|
||||
m_currentContract->isLibrary() &&
|
||||
functionType->kind() == FunctionType::Kind::DelegateCall &&
|
||||
functionType->declaration().scope() == m_currentContract
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
_functionCall.location(),
|
||||
SecondarySourceLocation().append(
|
||||
"The function declaration is here:",
|
||||
functionType->declaration().scope()->location()
|
||||
),
|
||||
"Libraries cannot call their own functions externally."
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -21,6 +21,9 @@
|
||||
#include <libsolidity/ast/ExperimentalFeatures.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/AsmData.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/SemVerHandler.h>
|
||||
|
||||
@ -254,6 +257,23 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
if (!m_useYulOptimizer)
|
||||
return false;
|
||||
|
||||
if (yul::SideEffectsCollector(
|
||||
_inlineAssembly.dialect(),
|
||||
_inlineAssembly.operations()
|
||||
).containsMSize())
|
||||
m_errorReporter.syntaxError(
|
||||
_inlineAssembly.location(),
|
||||
"The msize instruction cannot be used when the Yul optimizer is activated because "
|
||||
"it can change its semantics. Either disable the Yul optimizer or do not use the instruction."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SyntaxChecker::visit(PlaceholderStatement const&)
|
||||
{
|
||||
m_placeholderFound = true;
|
||||
|
@ -39,12 +39,16 @@ namespace solidity
|
||||
* - whether a modifier contains at least one '_'
|
||||
* - issues deprecation warnings for unary '+'
|
||||
* - issues deprecation warning for throw
|
||||
* - whether the msize instruction is used and the Yul optimizer is enabled at the same time.
|
||||
*/
|
||||
class SyntaxChecker: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
/// @param _errorReporter provides the error logging functionality.
|
||||
SyntaxChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
|
||||
SyntaxChecker(langutil::ErrorReporter& _errorReporter, bool _useYulOptimizer):
|
||||
m_errorReporter(_errorReporter),
|
||||
m_useYulOptimizer(_useYulOptimizer)
|
||||
{}
|
||||
|
||||
bool checkSyntax(ASTNode const& _astRoot);
|
||||
|
||||
@ -75,6 +79,8 @@ private:
|
||||
|
||||
bool visit(UnaryOperation const& _operation) override;
|
||||
|
||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||
|
||||
bool visit(PlaceholderStatement const& _placeholderStatement) override;
|
||||
|
||||
bool visit(ContractDefinition const& _contract) override;
|
||||
@ -88,6 +94,8 @@ private:
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
|
||||
bool m_useYulOptimizer = false;
|
||||
|
||||
/// Flag that indicates whether a function modifier actually contains '_'.
|
||||
bool m_placeholderFound = false;
|
||||
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
@ -707,7 +706,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
*_inlineAssembly.annotation().analysisInfo,
|
||||
m_errorReporter,
|
||||
Error::Type::SyntaxError,
|
||||
yul::EVMDialect::looseAssemblyForEVM(m_evmVersion),
|
||||
_inlineAssembly.dialect(),
|
||||
identifierAccess
|
||||
);
|
||||
if (!analyzer.analyze(_inlineAssembly.operations()))
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <libsolidity/analysis/ViewPureChecker.h>
|
||||
#include <libsolidity/ast/ExperimentalFeatures.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
#include <functional>
|
||||
@ -33,7 +34,11 @@ namespace
|
||||
class AssemblyViewPureChecker: public boost::static_visitor<void>
|
||||
{
|
||||
public:
|
||||
explicit AssemblyViewPureChecker(std::function<void(StateMutability, SourceLocation const&)> _reportMutability):
|
||||
explicit AssemblyViewPureChecker(
|
||||
yul::Dialect const& _dialect,
|
||||
std::function<void(StateMutability, SourceLocation const&)> _reportMutability
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_reportMutability(_reportMutability) {}
|
||||
|
||||
void operator()(yul::Label const&) { }
|
||||
@ -69,6 +74,11 @@ public:
|
||||
}
|
||||
void operator()(yul::FunctionCall const& _funCall)
|
||||
{
|
||||
if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect))
|
||||
if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name))
|
||||
if (fun->instruction)
|
||||
checkInstruction(_funCall.location, *fun->instruction);
|
||||
|
||||
for (auto const& arg: _funCall.arguments)
|
||||
boost::apply_visitor(*this, arg);
|
||||
}
|
||||
@ -107,16 +117,16 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
|
||||
void checkInstruction(SourceLocation _location, dev::eth::Instruction _instruction)
|
||||
{
|
||||
if (eth::SemanticInformation::invalidInViewFunctions(_instruction))
|
||||
m_reportMutability(StateMutability::NonPayable, _location);
|
||||
else if (_instruction == dev::eth::Instruction::CALLVALUE)
|
||||
m_reportMutability(StateMutability::Payable, _location);
|
||||
else if (eth::SemanticInformation::invalidInPureFunctions(_instruction))
|
||||
m_reportMutability(StateMutability::View, _location);
|
||||
}
|
||||
|
||||
yul::Dialect const& m_dialect;
|
||||
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
|
||||
};
|
||||
|
||||
}
|
||||
@ -223,6 +233,7 @@ void ViewPureChecker::endVisit(Identifier const& _identifier)
|
||||
void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
AssemblyViewPureChecker{
|
||||
_inlineAssembly.dialect(),
|
||||
[=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); }
|
||||
}(_inlineAssembly.operations());
|
||||
}
|
||||
|
@ -400,6 +400,21 @@ SourceUnit const& Scopable::sourceUnit() const
|
||||
return dynamic_cast<SourceUnit const&>(*s);
|
||||
}
|
||||
|
||||
CallableDeclaration const* Scopable::functionOrModifierDefinition() const
|
||||
{
|
||||
ASTNode const* s = scope();
|
||||
solAssert(s, "");
|
||||
while (dynamic_cast<Scopable const*>(s))
|
||||
{
|
||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(s))
|
||||
return funDef;
|
||||
if (auto modDef = dynamic_cast<ModifierDefinition const*>(s))
|
||||
return modDef;
|
||||
s = dynamic_cast<Scopable const*>(s)->scope();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
string Scopable::sourceUnitName() const
|
||||
{
|
||||
return sourceUnit().annotation().path;
|
||||
|
@ -43,6 +43,7 @@ namespace yul
|
||||
{
|
||||
// Forward-declaration to <yul/AsmData.h>
|
||||
struct Block;
|
||||
struct Dialect;
|
||||
}
|
||||
|
||||
namespace dev
|
||||
@ -161,6 +162,9 @@ public:
|
||||
/// @returns the source unit this scopable is present in.
|
||||
SourceUnit const& sourceUnit() const;
|
||||
|
||||
/// @returns the function or modifier definition this scopable is present in or nullptr.
|
||||
CallableDeclaration const* functionOrModifierDefinition() const;
|
||||
|
||||
/// @returns the source name this scopable is present in.
|
||||
/// Can be combined with annotation().canonicalName (if present) to form a globally unique name.
|
||||
std::string sourceUnitName() const;
|
||||
@ -1046,17 +1050,20 @@ public:
|
||||
InlineAssembly(
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
yul::Dialect const& _dialect,
|
||||
std::shared_ptr<yul::Block> const& _operations
|
||||
):
|
||||
Statement(_location, _docString), m_operations(_operations) {}
|
||||
Statement(_location, _docString), m_dialect(_dialect), m_operations(_operations) {}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
yul::Dialect const& dialect() const { return m_dialect; }
|
||||
yul::Block const& operations() const { return *m_operations; }
|
||||
|
||||
InlineAssemblyAnnotation& annotation() const override;
|
||||
|
||||
private:
|
||||
yul::Dialect const& m_dialect;
|
||||
std::shared_ptr<yul::Block> m_operations;
|
||||
};
|
||||
|
||||
|
@ -35,8 +35,9 @@
|
||||
|
||||
namespace yul
|
||||
{
|
||||
struct AsmAnalysisInfo;
|
||||
struct Identifier;
|
||||
struct AsmAnalysisInfo;
|
||||
struct Identifier;
|
||||
struct Dialect;
|
||||
}
|
||||
|
||||
namespace dev
|
||||
|
@ -38,6 +38,7 @@ class SourceUnit;
|
||||
class PragmaDirective;
|
||||
class ImportDirective;
|
||||
class Declaration;
|
||||
class CallableDeclaration;
|
||||
class ContractDefinition;
|
||||
class InheritanceSpecifier;
|
||||
class UsingForDirective;
|
||||
|
@ -2068,7 +2068,8 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
||||
{
|
||||
TypePointer type = variable->annotation().type;
|
||||
solAssert(type, "");
|
||||
// Skip all mapping members if we are not in storage.
|
||||
// If we are not in storage, skip all members that cannot live outside of storage,
|
||||
// ex. mappings and array of mappings
|
||||
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
|
||||
continue;
|
||||
members.emplace_back(
|
||||
|
@ -255,39 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||
return suffix;
|
||||
}
|
||||
|
||||
string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
||||
return createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
|
||||
unsigned storageBytes = _type.storageBytes();
|
||||
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
|
||||
if (type->isSigned() && storageBytes != 32)
|
||||
{
|
||||
templ("body", "cleaned := signextend(" + to_string(storageBytes - 1) + ", value)");
|
||||
return templ.render();
|
||||
}
|
||||
|
||||
if (storageBytes == 32)
|
||||
templ("body", "cleaned := value");
|
||||
else if (_type.leftAligned())
|
||||
templ("body", "cleaned := " + m_utils.shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
|
||||
else
|
||||
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunction(
|
||||
Type const& _from,
|
||||
Type const& _to,
|
||||
@ -609,7 +576,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
if (_from.baseType()->isValueType())
|
||||
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
|
||||
templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
|
||||
else
|
||||
templ("arrayElementAccess", "srcPtr");
|
||||
break;
|
||||
@ -806,7 +773,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
items[i]["inRange"] = "1";
|
||||
else
|
||||
items[i]["inRange"] = "0";
|
||||
items[i]["extractFromSlot"] = extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
|
||||
items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
|
||||
}
|
||||
templ("items", items);
|
||||
return templ.render();
|
||||
@ -893,7 +860,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
|
||||
members.back()["retrieveValue"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1400,51 +1367,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
string functionName =
|
||||
"read_from_storage_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
solAssert(_type.sizeOnStack() == 1, "");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> value {
|
||||
value := <extract>(sload(slot))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("extract", extractFromStorageValue(_type, _offset, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName =
|
||||
"extract_from_storage_value_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot_value) -> value {
|
||||
value := <cleanupStorage>(<shr>(slot_value))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr", m_utils.shiftRightFunction(_offset * 8))
|
||||
("cleanupStorage", cleanupFromStorageFunction(_type, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
{
|
||||
solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), "");
|
||||
|
@ -128,15 +128,6 @@ private:
|
||||
std::string toFunctionNameSuffix() const;
|
||||
};
|
||||
|
||||
/// Performs cleanup after reading from a potentially compressed storage slot.
|
||||
/// The function does not perform any validation, it just masks or sign-extends
|
||||
/// higher order bytes or left-aligns (in case of bytesNN).
|
||||
/// The storage cleanup expects the value to be right-aligned with potentially
|
||||
/// dirty higher order bytes.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of the ABI encoding function with the given type
|
||||
/// and queues the generation of the function to the requested functions.
|
||||
/// @param _fromStack if false, the input value was just loaded from storage
|
||||
@ -231,19 +222,6 @@ private:
|
||||
/// Part of @a abiDecodingFunction for array types.
|
||||
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
|
||||
|
||||
/// @returns a function that reads a value type from storage.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns a function that extracts a value type from storage slot that has been
|
||||
/// retrieved already.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of a function that retrieves an element from calldata.
|
||||
std::string calldataAccessFunction(Type const& _type);
|
||||
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <libyul/backends/evm/AsmCodeGen.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/optimiser/Suite.h>
|
||||
#include <libyul/optimiser/Metrics.h>
|
||||
#include <libyul/YulString.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
@ -382,7 +383,8 @@ void CompilerContext::appendInlineAssembly(
|
||||
ErrorList errors;
|
||||
ErrorReporter errorReporter(errors);
|
||||
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--"));
|
||||
auto parserResult = yul::Parser(errorReporter, yul::EVMDialect::strictAssemblyForEVM(m_evmVersion)).parse(scanner, false);
|
||||
yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion);
|
||||
auto parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false);
|
||||
#ifdef SOL_OUTPUT_ASM
|
||||
cout << yul::AsmPrinter()(*parserResult) << endl;
|
||||
#endif
|
||||
@ -409,7 +411,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
analysisInfo,
|
||||
errorReporter,
|
||||
boost::none,
|
||||
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
|
||||
dialect,
|
||||
identifierAccess.resolve
|
||||
).analyze(*parserResult);
|
||||
if (!parserResult || !errorReporter.errors().empty() || !analyzerResult)
|
||||
@ -419,8 +421,10 @@ void CompilerContext::appendInlineAssembly(
|
||||
// so we essentially only optimize the ABI functions.
|
||||
if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
|
||||
{
|
||||
bool const isCreation = m_runtimeContext != nullptr;
|
||||
yul::OptimiserSuite::run(
|
||||
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
|
||||
dialect,
|
||||
yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment),
|
||||
*parserResult,
|
||||
analysisInfo,
|
||||
_optimiserSettings.optimizeStackAllocation,
|
||||
@ -431,7 +435,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
analysisInfo,
|
||||
errorReporter,
|
||||
boost::none,
|
||||
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
|
||||
dialect,
|
||||
identifierAccess.resolve
|
||||
).analyze(*parserResult))
|
||||
reportError("Optimizer introduced error into inline assembly.");
|
||||
|
@ -228,34 +228,21 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
|
||||
// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
|
||||
if (!_constructor.parameters().empty())
|
||||
{
|
||||
unsigned argumentSize = 0;
|
||||
for (ASTPointer<VariableDeclaration> const& var: _constructor.parameters())
|
||||
if (var->annotation().type->isDynamicallySized())
|
||||
{
|
||||
argumentSize = 0;
|
||||
break;
|
||||
}
|
||||
else
|
||||
argumentSize += var->annotation().type->calldataEncodedSize();
|
||||
|
||||
CompilerUtils(m_context).fetchFreeMemoryPointer();
|
||||
if (argumentSize == 0)
|
||||
{
|
||||
// argument size is dynamic, use CODESIZE to determine it
|
||||
m_context.appendProgramSize(); // program itself
|
||||
// CODESIZE is program plus manually added arguments
|
||||
// CODESIZE returns the actual size of the code,
|
||||
// which is the size of the generated code (``programSize``)
|
||||
// plus the constructor arguments added to the transaction payload.
|
||||
m_context.appendProgramSize();
|
||||
m_context << Instruction::CODESIZE << Instruction::SUB;
|
||||
}
|
||||
else
|
||||
m_context << u256(argumentSize);
|
||||
// stack: <memptr> <argument size>
|
||||
m_context << Instruction::DUP1;
|
||||
m_context.appendProgramSize();
|
||||
m_context << Instruction::DUP4 << Instruction::CODECOPY;
|
||||
m_context << Instruction::DUP2 << Instruction::ADD;
|
||||
m_context << Instruction::DUP1;
|
||||
// stack: <memptr> <argument size>
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2 << Instruction::ADD;
|
||||
// stack: <memptr> <argument size> <mem end>
|
||||
CompilerUtils(m_context).storeFreeMemoryPointer();
|
||||
// stack: <memptr>
|
||||
// stack: <memptr> <argument size>
|
||||
CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true);
|
||||
}
|
||||
_constructor.accept(*this);
|
||||
|
@ -206,6 +206,12 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
|
||||
CompilerUtils(m_context).splitExternalFunctionType(false);
|
||||
cleaned = true;
|
||||
}
|
||||
else if (fun->kind() == FunctionType::Kind::Internal)
|
||||
{
|
||||
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
||||
CompilerUtils(m_context).pushZeroValue(*fun);
|
||||
m_context << Instruction::MUL << Instruction::OR;
|
||||
}
|
||||
}
|
||||
if (!cleaned)
|
||||
{
|
||||
@ -288,7 +294,8 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
||||
{
|
||||
solAssert(
|
||||
_sourceType.category() == m_dataType->category(),
|
||||
"Wrong type conversation for assignment.");
|
||||
"Wrong type conversation for assignment."
|
||||
);
|
||||
if (m_dataType->category() == Type::Category::Array)
|
||||
{
|
||||
m_context << Instruction::POP; // remove byte offset
|
||||
@ -313,9 +320,9 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
||||
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
|
||||
for (auto const& member: structType.members(nullptr))
|
||||
{
|
||||
// assign each member that is not a mapping
|
||||
// assign each member that can live outside of storage
|
||||
TypePointer const& memberType = member.type;
|
||||
if (memberType->category() == Type::Category::Mapping)
|
||||
if (!memberType->canLiveOutsideStorage())
|
||||
continue;
|
||||
TypePointer sourceMemberType = sourceType.memberType(member.name);
|
||||
if (sourceType.location() == DataLocation::Storage)
|
||||
|
@ -104,6 +104,61 @@ string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType)
|
||||
{
|
||||
string functionName =
|
||||
string(_assert ? "assert_helper" : "require_helper") +
|
||||
(_messageType ? ("_" + _messageType->identifier()) : "");
|
||||
|
||||
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (!_messageType)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(condition) {
|
||||
if iszero(condition) { <invalidOrRevert> }
|
||||
}
|
||||
)")
|
||||
("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
|
||||
int const hashHeaderSize = 4;
|
||||
int const byteSize = 8;
|
||||
u256 const errorHash =
|
||||
u256(FixedHash<hashHeaderSize>::Arith(
|
||||
FixedHash<hashHeaderSize>(dev::keccak256("Error(string)"))
|
||||
)) << (256 - hashHeaderSize * byteSize);
|
||||
|
||||
string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector)
|
||||
.tupleEncoder(
|
||||
{_messageType},
|
||||
{TypeProvider::stringMemory()}
|
||||
);
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(condition <messageVars>) {
|
||||
if iszero(condition) {
|
||||
let fmp := mload(<freeMemPointer>)
|
||||
mstore(fmp, <errorHash>)
|
||||
let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>) <messageVars>)
|
||||
revert(fmp, sub(end, fmp))
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("errorHash", formatNumber(errorHash))
|
||||
("abiEncodeFunc", encodeFunc)
|
||||
("hashHeaderSize", to_string(hashHeaderSize))
|
||||
("messageVars",
|
||||
(_messageType->sizeOnStack() > 0 ? ", " : "") +
|
||||
suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack())
|
||||
)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("leftAlign_") + _type.identifier();
|
||||
@ -206,35 +261,49 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
|
||||
// Note that if this is extended with signed shifts,
|
||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||
|
||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shr(<numBits>, value)
|
||||
newValue :=
|
||||
<?hasShifts>
|
||||
shr(<numBits>, value)
|
||||
<!hasShifts>
|
||||
div(value, <multiplier>)
|
||||
</hasShifts>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("hasShifts", m_evmVersion.hasBitwiseShifting())
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := div(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
|
||||
{
|
||||
solAssert(_numBytes <= 32, "");
|
||||
solAssert(_shiftBytes <= 32, "");
|
||||
size_t numBits = _numBytes * 8;
|
||||
size_t shiftBits = _shiftBytes * 8;
|
||||
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value, toInsert) -> result {
|
||||
let mask := <mask>
|
||||
toInsert := <shl>(toInsert)
|
||||
value := and(value, not(mask))
|
||||
result := or(value, and(toInsert, mask))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits))
|
||||
("shl", shiftLeftFunction(shiftBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::roundUpFunction()
|
||||
@ -257,24 +326,90 @@ string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits)
|
||||
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, "");
|
||||
string functionName = "checked_add_uint_" + to_string(_bits);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_bits < 256)
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> sum {
|
||||
<?shortType>
|
||||
let mask := <mask>
|
||||
sum := add(and(x, mask), and(y, mask))
|
||||
if and(sum, not(mask)) { revert(0, 0) }
|
||||
<!shortType>
|
||||
sum := add(x, y)
|
||||
if lt(sum, x) { revert(0, 0) }
|
||||
</shortType>
|
||||
}
|
||||
)")
|
||||
("shortType", _bits < 256)
|
||||
("functionName", functionName)
|
||||
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
|
||||
.render();
|
||||
else
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::overflowCheckedUIntMulFunction(size_t _bits)
|
||||
{
|
||||
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, "");
|
||||
string functionName = "checked_mul_uint_" + to_string(_bits);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
// - The current overflow check *before* the multiplication could
|
||||
// be replaced by the following check *after* the multiplication:
|
||||
// if and(iszero(iszero(x)), iszero(eq(div(product, x), y))) { revert(0, 0) }
|
||||
// - The case the x equals 0 could be treated separately and directly return zero.
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> product {
|
||||
if and(iszero(iszero(x)), lt(div(<mask>, x), y)) { revert(0, 0) }
|
||||
<?shortType>
|
||||
product := mulmod(x, y, <powerOfTwo>)
|
||||
<!shortType>
|
||||
product := mul(x, y)
|
||||
</shortType>
|
||||
}
|
||||
)")
|
||||
("shortType", _bits < 256)
|
||||
("functionName", functionName)
|
||||
("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits))
|
||||
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
||||
{
|
||||
unsigned bits = _type.numBits();
|
||||
solAssert(0 < bits && bits <= 256 && bits % 8 == 0, "");
|
||||
string functionName = "checked_div_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> sum {
|
||||
sum := add(x, y)
|
||||
if lt(sum, x) { revert(0, 0) }
|
||||
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)
|
||||
@ -288,43 +423,38 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(value) -> length {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
string body;
|
||||
if (!_type.isDynamicallySized())
|
||||
body = "length := " + toCompactHexWithPrefix(_type.length());
|
||||
else
|
||||
{
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
solAssert(false, "called regular array length function on calldata array");
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
body = "length := mload(value)";
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
if (_type.isByteArray())
|
||||
{
|
||||
<?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.
|
||||
body = R"(
|
||||
length := sload(value)
|
||||
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
|
||||
length := div(and(length, mask), 2)
|
||||
)";
|
||||
</byteArray>
|
||||
</storage>
|
||||
<!dynamic>
|
||||
length := <length>
|
||||
</dynamic>
|
||||
}
|
||||
else
|
||||
body = "length := sload(value)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
solAssert(!body.empty(), "");
|
||||
w("body", body);
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
w("dynamic", _type.isDynamicallySized());
|
||||
if (!_type.isDynamicallySized())
|
||||
w("length", toCompactHexWithPrefix(_type.length()));
|
||||
w("memory", _type.location() == DataLocation::Memory);
|
||||
w("storage", _type.location() == DataLocation::Storage);
|
||||
w("byteArray", _type.isByteArray());
|
||||
if (_type.isDynamicallySized())
|
||||
solAssert(
|
||||
_type.location() != DataLocation::CallData,
|
||||
"called regular array length function on calldata array"
|
||||
);
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
@ -338,20 +468,21 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
function <functionName>(length) -> size {
|
||||
// Make sure we can allocate memory without overflow
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
size := <allocationSize>
|
||||
<addLengthSlot>
|
||||
<?byteArray>
|
||||
// round up
|
||||
size := and(add(length, 0x1f), not(0x1f))
|
||||
<!byteArray>
|
||||
size := mul(length, 0x20)
|
||||
</byteArray>
|
||||
<?dynamic>
|
||||
// add length slot
|
||||
size := add(size, 0x20)
|
||||
</dynamic>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
if (_type.isByteArray())
|
||||
// Round up
|
||||
w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
|
||||
else
|
||||
w("allocationSize", "mul(length, 0x20)");
|
||||
if (_type.isDynamicallySized())
|
||||
w("addLengthSlot", "size := add(size, 0x20)");
|
||||
else
|
||||
w("addLengthSlot", "");
|
||||
w("byteArray", _type.isByteArray());
|
||||
w("dynamic", _type.isDynamicallySized());
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
@ -360,64 +491,30 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_dataslot_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::Memory:
|
||||
if (_type.isDynamicallySized())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := memPtr
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
case DataLocation::Storage:
|
||||
if (_type.isDynamicallySized())
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
mstore(0, slot)
|
||||
dataSlot := keccak256(0, 0x20)
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
dataSlot := slot
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
// Calldata arrays are stored as offset of the data area and length
|
||||
// on the stack, so the offset already points to the data area.
|
||||
// No special processing for calldata arrays, because they are stored as
|
||||
// offset of the data area and length on the stack, so the offset already
|
||||
// points to the data area.
|
||||
// This might change, if calldata arrays are stored in a single
|
||||
// stack slot at some point.
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
dataSlot := slot
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
default:
|
||||
solAssert(false, "");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(ptr) -> data {
|
||||
data := ptr
|
||||
<?dynamic>
|
||||
<?memory>
|
||||
data := add(ptr, 0x20)
|
||||
</memory>
|
||||
<?storage>
|
||||
mstore(0, ptr)
|
||||
data := keccak256(0, 0x20)
|
||||
</storage>
|
||||
</dynamic>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("dynamic", _type.isDynamicallySized())
|
||||
("memory", _type.location() == DataLocation::Memory)
|
||||
("storage", _type.location() == DataLocation::Storage)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
@ -428,36 +525,167 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
solAssert(_type.baseType()->storageBytes() > 16, "");
|
||||
string functionName = "array_nextElement_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::Memory:
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> nextPtr {
|
||||
nextPtr := add(memPtr, 0x20)
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(ptr) -> next {
|
||||
next := add(ptr, <advance>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
case DataLocation::Storage:
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> nextSlot {
|
||||
nextSlot := add(slot, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
case DataLocation::CallData:
|
||||
return Whiskers(R"(
|
||||
function <functionName>(calldataPtr) -> nextPtr {
|
||||
nextPtr := add(calldataPtr, <stride>)
|
||||
}
|
||||
)")
|
||||
("stride", toCompactHexWithPrefix(_type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize()))
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
default:
|
||||
)");
|
||||
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
|
||||
{
|
||||
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
|
||||
solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), "");
|
||||
solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, "");
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot <key>) -> dataSlot {
|
||||
mstore(0, <convertedKey>)
|
||||
mstore(0x20, slot)
|
||||
dataSlot := keccak256(0, 0x40)
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("key", _keyType.sizeOnStack() == 1 ? ", key" : "");
|
||||
if (_keyType.sizeOnStack() == 0)
|
||||
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()");
|
||||
else
|
||||
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)");
|
||||
return templ.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
string functionName =
|
||||
"read_from_storage_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
"_" +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
solAssert(_type.sizeOnStack() == 1, "");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> value {
|
||||
value := <extract>(sload(slot))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("extract", extractFromStorageValue(_type, _offset, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName =
|
||||
"extract_from_storage_value_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot_value) -> value {
|
||||
value := <cleanupStorage>(<shr>(slot_value))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr", shiftRightFunction(_offset * 8))
|
||||
("cleanupStorage", cleanupFromStorageFunction(_type, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
cleaned := <cleaned>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
|
||||
unsigned storageBytes = _type.storageBytes();
|
||||
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
|
||||
if (type->isSigned() && storageBytes != 32)
|
||||
{
|
||||
templ("cleaned", "signextend(" + to_string(storageBytes - 1) + ", value)");
|
||||
return templ.render();
|
||||
}
|
||||
|
||||
if (storageBytes == 32)
|
||||
templ("cleaned", "value");
|
||||
else if (_type.leftAligned())
|
||||
templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
|
||||
else
|
||||
templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::prepareStoreFunction(Type const& _type)
|
||||
{
|
||||
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
|
||||
|
||||
string functionName = "prepare_store_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
ret := <actualPrepare>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
if (_type.category() == Type::Category::FixedBytes)
|
||||
templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)");
|
||||
else
|
||||
templ("actualPrepare", "value");
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
@ -482,6 +710,9 @@ string YulUtilFunctions::allocationFunction()
|
||||
|
||||
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
{
|
||||
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
||||
return conversionFunctionSpecial(_from, _to);
|
||||
|
||||
string functionName =
|
||||
"convert_" +
|
||||
_from.identifier() +
|
||||
@ -782,6 +1013,62 @@ string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFail
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::packedHashFunction(
|
||||
vector<Type const*> const& _givenTypes,
|
||||
vector<Type const*> const& _targetTypes
|
||||
)
|
||||
{
|
||||
string functionName = string("packed_hashed_");
|
||||
for (auto const& t: _givenTypes)
|
||||
functionName += t->identifier() + "_";
|
||||
functionName += "_to_";
|
||||
for (auto const& t: _targetTypes)
|
||||
functionName += t->identifier() + "_";
|
||||
size_t sizeOnStack = 0;
|
||||
for (Type const* t: _givenTypes)
|
||||
sizeOnStack += t->sizeOnStack();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(<variables>) -> hash {
|
||||
let pos := mload(<freeMemoryPointer>)
|
||||
let end := <packedEncode>(pos <comma> <variables>)
|
||||
hash := keccak256(pos, sub(end, pos))
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack));
|
||||
templ("comma", sizeOnStack > 0 ? "," : "");
|
||||
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
|
||||
templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::forwardingRevertFunction()
|
||||
{
|
||||
bool forward = m_evmVersion.supportsReturndata();
|
||||
string functionName = "revert_forward_" + to_string(forward);
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (forward)
|
||||
return Whiskers(R"(
|
||||
function <functionName>() {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
return Whiskers(R"(
|
||||
function <functionName>() {
|
||||
revert(0, 0)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
|
||||
{
|
||||
string result;
|
||||
@ -799,3 +1086,166 @@ string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
|
||||
string const functionName = "decrement_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
u256 minintval;
|
||||
|
||||
// Smallest admissible value to decrement
|
||||
if (type.isSigned())
|
||||
minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
|
||||
else
|
||||
minintval = 1;
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
if <lt>(value, <minval>) { revert(0,0) }
|
||||
ret := sub(value, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("minval", toCompactHexWithPrefix(minintval))
|
||||
("lt", type.isSigned() ? "slt" : "lt")
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
|
||||
string const functionName = "increment_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
u256 maxintval;
|
||||
|
||||
// Biggest admissible value to increment
|
||||
if (type.isSigned())
|
||||
maxintval = (u256(1) << (type.numBits() - 1)) - 2;
|
||||
else
|
||||
maxintval = (u256(1) << type.numBits()) - 2;
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
if <gt>(value, <maxval>) { revert(0,0) }
|
||||
ret := add(value, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("maxval", toCompactHexWithPrefix(maxintval))
|
||||
("gt", type.isSigned() ? "sgt" : "gt")
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
solAssert(type.isSigned(), "Expected signed type!");
|
||||
|
||||
string const functionName = "negate_" + _type.identifier();
|
||||
|
||||
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(_value) -> ret {
|
||||
if slt(_value, <minval>) { revert(0,0) }
|
||||
ret := sub(0, _value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("minval", toCompactHexWithPrefix(minintval))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::zeroValueFunction(Type const& _type)
|
||||
{
|
||||
solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!");
|
||||
solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented");
|
||||
|
||||
string const functionName = "zero_value_for_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>() -> ret {
|
||||
<body>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("body", "ret := 0x0")
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to)
|
||||
{
|
||||
string functionName =
|
||||
"convert_" +
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
solUnimplementedAssert(
|
||||
_from.category() == Type::Category::StringLiteral,
|
||||
"Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented."
|
||||
);
|
||||
string const& data = dynamic_cast<StringLiteralType const&>(_from).value();
|
||||
if (_to.category() == Type::Category::FixedBytes)
|
||||
{
|
||||
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_to).numBytes();
|
||||
solAssert(data.size() <= 32, "");
|
||||
Whiskers templ(R"(
|
||||
function <functionName>() -> converted {
|
||||
converted := <data>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("data", formatNumber(
|
||||
h256::Arith(h256(data, h256::AlignLeft)) &
|
||||
(~(u256(-1) >> (8 * numBytes)))
|
||||
));
|
||||
return templ.render();
|
||||
}
|
||||
else if (_to.category() == Type::Category::Array)
|
||||
{
|
||||
auto const& arrayType = dynamic_cast<ArrayType const&>(_to);
|
||||
solAssert(arrayType.isByteArray(), "");
|
||||
size_t words = (data.size() + 31) / 32;
|
||||
size_t storageSize = 32 + words * 32;
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>() -> converted {
|
||||
converted := <allocate>(<storageSize>)
|
||||
mstore(converted, <size>)
|
||||
<#word>
|
||||
mstore(add(converted, <offset>), <wordValue>)
|
||||
</word>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("allocate", allocationFunction());
|
||||
templ("storageSize", to_string(storageSize));
|
||||
templ("size", to_string(data.size()));
|
||||
vector<map<string, string>> wordParams(words);
|
||||
for (size_t i = 0; i < words; ++i)
|
||||
{
|
||||
wordParams[i]["offset"] = to_string(32 + i * 32);
|
||||
wordParams[i]["wordValue"] = "0x" + h256(data.substr(32 * i, 32), h256::AlignLeft).hex();
|
||||
}
|
||||
templ("word", wordParams);
|
||||
return templ.render();
|
||||
}
|
||||
else
|
||||
solAssert(
|
||||
false,
|
||||
"Invalid conversion from string literal to " + _to.toString() + " requested."
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
@ -34,6 +35,8 @@ namespace solidity
|
||||
|
||||
class Type;
|
||||
class ArrayType;
|
||||
class MappingType;
|
||||
class IntegerType;
|
||||
|
||||
/**
|
||||
* Component that can generate various useful Yul functions.
|
||||
@ -62,6 +65,10 @@ public:
|
||||
/// Pads with zeros and might write more than exactly length.
|
||||
std::string copyToMemoryFunction(bool _fromCalldata);
|
||||
|
||||
// @returns the name of a function that has the equivalent logic of an
|
||||
// `assert` or `require` call.
|
||||
std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr);
|
||||
|
||||
/// @returns the name of a function that takes a (cleaned) value of the given value type and
|
||||
/// left-aligns it, usually for use in non-padded encoding.
|
||||
std::string leftAlignFunction(Type const& _type);
|
||||
@ -69,12 +76,28 @@ public:
|
||||
std::string shiftLeftFunction(size_t _numBits);
|
||||
std::string shiftRightFunction(size_t _numBits);
|
||||
|
||||
/// @returns the name of a function f(value, toInsert) -> newValue which replaces the
|
||||
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
|
||||
/// byte) by the _numBytes least significant bytes of `toInsert`.
|
||||
std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes);
|
||||
|
||||
/// @returns the name of a function that rounds its input to the next multiple
|
||||
/// of 32 or the input if it is a multiple of 32.
|
||||
std::string roundUpFunction();
|
||||
|
||||
std::string overflowCheckedUIntAddFunction(size_t _bits);
|
||||
|
||||
std::string overflowCheckedUIntMulFunction(size_t _bits);
|
||||
|
||||
/// @returns name of function to perform division on integers.
|
||||
/// Checks for division by zero and the special case of
|
||||
/// signed division of the smallest number by -1.
|
||||
std::string overflowCheckedIntDivFunction(IntegerType const& _type);
|
||||
|
||||
/// @returns computes the difference between two values.
|
||||
/// Assumes the input to be in range for the type.
|
||||
std::string overflowCheckedUIntSubFunction();
|
||||
|
||||
std::string arrayLengthFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that computes the number of bytes required
|
||||
/// to store an array in memory given its length (internally encoded, not ABI encoded).
|
||||
@ -88,6 +111,39 @@ public:
|
||||
/// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot.
|
||||
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that performs index access for mappings.
|
||||
/// @param _mappingType the type of the mapping
|
||||
/// @param _keyType the type of the value provided
|
||||
std::string mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType);
|
||||
|
||||
/// @returns a function that reads a value type from storage.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns a function that extracts a value type from storage slot that has been
|
||||
/// retrieved already.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// Performs cleanup after reading from a potentially compressed storage slot.
|
||||
/// The function does not perform any validation, it just masks or sign-extends
|
||||
/// higher order bytes or left-aligns (in case of bytesNN).
|
||||
/// The storage cleanup expects the value to be right-aligned with potentially
|
||||
/// dirty higher order bytes.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of a function that prepares a value of the given type
|
||||
/// for being stored in storage. This usually includes cleanup and right-alignment
|
||||
/// to fit the number of bytes in storage.
|
||||
/// The resulting value might still have dirty higher order bits.
|
||||
std::string prepareStoreFunction(Type const& _type);
|
||||
|
||||
/// @returns the name of a function that allocates memory.
|
||||
/// Modifies the "free memory pointer"
|
||||
/// Arguments: size
|
||||
@ -115,13 +171,32 @@ public:
|
||||
/// This is used for data decoded from external sources.
|
||||
std::string validatorFunction(Type const& _type, bool _revertOnFailure = false);
|
||||
|
||||
std::string packedHashFunction(std::vector<Type const*> const& _givenTypes, std::vector<Type const*> const& _targetTypes);
|
||||
|
||||
/// @returns the name of a function that reverts and uses returndata (if available)
|
||||
/// as reason string.
|
||||
std::string forwardingRevertFunction();
|
||||
|
||||
/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
|
||||
/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
|
||||
/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.
|
||||
/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
|
||||
static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix);
|
||||
|
||||
|
||||
std::string incrementCheckedFunction(Type const& _type);
|
||||
std::string decrementCheckedFunction(Type const& _type);
|
||||
|
||||
std::string negateNumberCheckedFunction(Type const& _type);
|
||||
|
||||
/// @returns the name of a function that returns the zero value for the
|
||||
/// provided type
|
||||
std::string zeroValueFunction(Type const& _type);
|
||||
private:
|
||||
/// Special case of conversionFunction - handles everything that does not
|
||||
/// use exactly one variable to hold the value.
|
||||
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
};
|
||||
|
@ -39,7 +39,7 @@ string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl
|
||||
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
||||
}
|
||||
|
||||
string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
|
||||
string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(
|
||||
m_localVariables.count(&_varDecl),
|
||||
@ -48,6 +48,15 @@ string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
|
||||
return m_localVariables[&_varDecl];
|
||||
}
|
||||
|
||||
void IRGenerationContext::addStateVariable(
|
||||
VariableDeclaration const& _declaration,
|
||||
u256 _storageOffset,
|
||||
unsigned _byteOffset
|
||||
)
|
||||
{
|
||||
m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset);
|
||||
}
|
||||
|
||||
string IRGenerationContext::functionName(FunctionDefinition const& _function)
|
||||
{
|
||||
// @TODO previously, we had to distinguish creation context and runtime context,
|
||||
@ -85,18 +94,27 @@ string IRGenerationContext::newYulVariable()
|
||||
string IRGenerationContext::variable(Expression const& _expression)
|
||||
{
|
||||
unsigned size = _expression.annotation().type->sizeOnStack();
|
||||
solUnimplementedAssert(size == 1, "");
|
||||
return "expr_" + to_string(_expression.id());
|
||||
string var = "expr_" + to_string(_expression.id());
|
||||
if (size == 1)
|
||||
return var;
|
||||
else
|
||||
return YulUtilFunctions::suffixedVariableNameList(move(var) + "_", 1, 1 + size);
|
||||
}
|
||||
|
||||
string IRGenerationContext::variablePart(Expression const& _expression, size_t _part)
|
||||
{
|
||||
size_t numVars = _expression.annotation().type->sizeOnStack();
|
||||
solAssert(numVars > 1, "");
|
||||
solAssert(1 <= _part && _part <= numVars, "");
|
||||
return "expr_" + to_string(_expression.id()) + "_" + to_string(_part);
|
||||
}
|
||||
|
||||
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
{
|
||||
// TODO can we limit the generated functions to only those visited
|
||||
// in the expression context? What about creation / runtime context?
|
||||
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
||||
return m_functions->createFunction(funName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(fun <comma> <in>) -> <out> {
|
||||
function <functionName>(fun <comma> <in>) <arrow> <out> {
|
||||
switch fun
|
||||
<#cases>
|
||||
case <funID>
|
||||
@ -111,6 +129,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
templ("comma", _in > 0 ? "," : "");
|
||||
YulUtilFunctions utils(m_evmVersion, m_functions);
|
||||
templ("in", utils.suffixedVariableNameList("in_", 0, _in));
|
||||
templ("arrow", _out > 0 ? "->" : "");
|
||||
templ("out", utils.suffixedVariableNameList("out_", 0, _out));
|
||||
vector<map<string, string>> functions;
|
||||
for (auto const& contract: m_inheritanceHierarchy)
|
||||
@ -120,11 +139,21 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
function->parameters().size() == _in &&
|
||||
function->returnParameters().size() == _out
|
||||
)
|
||||
{
|
||||
// 0 is reserved for uninitialized function pointers
|
||||
solAssert(function->id() != 0, "Unexpected function ID: 0");
|
||||
|
||||
functions.emplace_back(map<string, string> {
|
||||
{ "funID", to_string(function->id()) },
|
||||
{ "name", functionName(*function)}
|
||||
});
|
||||
}
|
||||
templ("cases", move(functions));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
YulUtilFunctions IRGenerationContext::utils()
|
||||
{
|
||||
return YulUtilFunctions(m_evmVersion, m_functions);
|
||||
}
|
||||
|
@ -26,6 +26,8 @@
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -39,6 +41,7 @@ class ContractDefinition;
|
||||
class VariableDeclaration;
|
||||
class FunctionDefinition;
|
||||
class Expression;
|
||||
class YulUtilFunctions;
|
||||
|
||||
/**
|
||||
* Class that contains contextual information during IR generation.
|
||||
@ -62,7 +65,16 @@ public:
|
||||
|
||||
|
||||
std::string addLocalVariable(VariableDeclaration const& _varDecl);
|
||||
std::string variableName(VariableDeclaration const& _varDecl);
|
||||
bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); }
|
||||
std::string localVariableName(VariableDeclaration const& _varDecl);
|
||||
|
||||
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
|
||||
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
|
||||
std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const
|
||||
{
|
||||
return m_stateVariables.at(&_varDecl);
|
||||
}
|
||||
|
||||
std::string functionName(FunctionDefinition const& _function);
|
||||
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
|
||||
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
|
||||
@ -71,14 +83,24 @@ public:
|
||||
/// @returns the variable (or comma-separated list of variables) that contain
|
||||
/// the value of the given expression.
|
||||
std::string variable(Expression const& _expression);
|
||||
/// @returns the variable of a multi-variable expression. Variables are numbered
|
||||
/// starting from 1.
|
||||
std::string variablePart(Expression const& _expression, size_t _part);
|
||||
|
||||
std::string internalDispatch(size_t _in, size_t _out);
|
||||
|
||||
/// @returns a new copy of the utility function generator (but using the same function set).
|
||||
YulUtilFunctions utils();
|
||||
|
||||
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
OptimiserSettings m_optimiserSettings;
|
||||
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
|
||||
std::map<VariableDeclaration const*, std::string> m_localVariables;
|
||||
/// Storage offsets of state variables
|
||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
|
||||
size_t m_varCounter = 0;
|
||||
};
|
||||
|
@ -71,6 +71,8 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
|
||||
|
||||
string IRGenerator::generate(ContractDefinition const& _contract)
|
||||
{
|
||||
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
|
||||
|
||||
Whiskers t(R"(
|
||||
object "<CreationObject>" {
|
||||
code {
|
||||
@ -89,18 +91,27 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
||||
}
|
||||
)");
|
||||
|
||||
resetContext();
|
||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||
resetContext(_contract);
|
||||
|
||||
t("CreationObject", creationObjectName(_contract));
|
||||
t("memoryInit", memoryInit());
|
||||
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : "");
|
||||
t("constructor", constructorCode(_contract));
|
||||
t("deploy", deployCode(_contract));
|
||||
// We generate code for all functions and rely on the optimizer to remove them again
|
||||
// TODO it would probably be better to only generate functions when internalDispatch or
|
||||
// virtualFunctionName is called - same below.
|
||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||
for (auto const* fun: contract->definedFunctions())
|
||||
generateFunction(*fun);
|
||||
t("functions", m_context.functionCollector()->requestedFunctions());
|
||||
|
||||
resetContext();
|
||||
resetContext(_contract);
|
||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||
t("RuntimeObject", runtimeObjectName(_contract));
|
||||
t("dispatch", dispatchRoutine(_contract));
|
||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||
for (auto const* fun: contract->definedFunctions())
|
||||
generateFunction(*fun);
|
||||
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
|
||||
return t.render();
|
||||
}
|
||||
@ -116,7 +127,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
string functionName = m_context.functionName(_function);
|
||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
||||
Whiskers t("\nfunction <functionName>(<params>) <returns> {\n<body>\n}\n");
|
||||
Whiskers t(R"(
|
||||
function <functionName>(<params>) <returns> {
|
||||
for { let return_flag := 1 } return_flag {} {
|
||||
<body>
|
||||
break
|
||||
}
|
||||
}
|
||||
)");
|
||||
t("functionName", functionName);
|
||||
string params;
|
||||
for (auto const& varDecl: _function.parameters())
|
||||
@ -131,15 +149,21 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||
});
|
||||
}
|
||||
|
||||
string IRGenerator::constructorCode(FunctionDefinition const& _constructor)
|
||||
string IRGenerator::constructorCode(ContractDefinition const& _contract)
|
||||
{
|
||||
// TODO initialize state variables in base to derived order.
|
||||
// TODO base constructors
|
||||
// TODO callValueCheck if there is no constructor.
|
||||
if (FunctionDefinition const* constructor = _contract.constructor())
|
||||
{
|
||||
string out;
|
||||
if (!_constructor.isPayable())
|
||||
if (!constructor->isPayable())
|
||||
out = callValueCheck();
|
||||
solUnimplementedAssert(constructor->parameters().empty(), "");
|
||||
return move(out) + m_context.functionName(*constructor) + "()\n";
|
||||
}
|
||||
|
||||
solUnimplemented("Constructors are not yet implemented.");
|
||||
|
||||
return out;
|
||||
return {};
|
||||
}
|
||||
|
||||
string IRGenerator::deployCode(ContractDefinition const& _contract)
|
||||
@ -242,7 +266,7 @@ string IRGenerator::memoryInit()
|
||||
.render();
|
||||
}
|
||||
|
||||
void IRGenerator::resetContext()
|
||||
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(
|
||||
m_context.functionCollector()->requestedFunctions().empty(),
|
||||
@ -250,4 +274,8 @@ void IRGenerator::resetContext()
|
||||
);
|
||||
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings);
|
||||
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector());
|
||||
|
||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||
for (auto const& var: ContractType(_contract).stateVariables())
|
||||
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ private:
|
||||
/// Generates code for and returns the name of the function.
|
||||
std::string generateFunction(FunctionDefinition const& _function);
|
||||
|
||||
std::string constructorCode(FunctionDefinition const& _constructor);
|
||||
std::string constructorCode(ContractDefinition const& _contract);
|
||||
std::string deployCode(ContractDefinition const& _contract);
|
||||
std::string callValueCheck();
|
||||
|
||||
@ -68,7 +68,7 @@ private:
|
||||
|
||||
std::string memoryInit();
|
||||
|
||||
void resetContext();
|
||||
void resetContext(ContractDefinition const& _contract);
|
||||
|
||||
langutil::EVMVersion const m_evmVersion;
|
||||
OptimiserSettings const m_optimiserSettings;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/codegen/ir/IRLValue.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
@ -42,25 +43,70 @@ public:
|
||||
m_utils(_utils)
|
||||
{}
|
||||
|
||||
std::string code() const { return m_code.str(); }
|
||||
std::string code() const;
|
||||
|
||||
bool visit(VariableDeclarationStatement const& _variableDeclaration) override;
|
||||
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
|
||||
bool visit(Assignment const& _assignment) override;
|
||||
bool visit(Return const& _return) override;
|
||||
void endVisit(BinaryOperation const& _binOp) override;
|
||||
bool visit(FunctionCall const& _funCall) override;
|
||||
bool visit(TupleExpression const& _tuple) override;
|
||||
bool visit(IfStatement const& _ifStatement) override;
|
||||
bool visit(ForStatement const& _forStatement) override;
|
||||
bool visit(WhileStatement const& _whileStatement) override;
|
||||
bool visit(Continue const& _continueStatement) override;
|
||||
bool visit(Break const& _breakStatement) override;
|
||||
void endVisit(Return const& _return) override;
|
||||
void endVisit(UnaryOperation const& _unaryOperation) override;
|
||||
bool visit(BinaryOperation const& _binOp) override;
|
||||
void endVisit(FunctionCall const& _funCall) override;
|
||||
void endVisit(MemberAccess const& _memberAccess) override;
|
||||
bool visit(InlineAssembly const& _inlineAsm) override;
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
void endVisit(IndexAccess const& _indexAccess) override;
|
||||
void endVisit(Identifier const& _identifier) override;
|
||||
bool visit(Literal const& _literal) override;
|
||||
|
||||
private:
|
||||
/// Appends code to call an external function with the given arguments.
|
||||
/// All involved expressions have already been visited.
|
||||
void appendExternalFunctionCall(
|
||||
FunctionCall const& _functionCall,
|
||||
std::vector<ASTPointer<Expression const>> const& _arguments
|
||||
);
|
||||
|
||||
std::string fetchFreeMem() const;
|
||||
|
||||
/// @returns a Yul expression representing the current value of @a _expression,
|
||||
/// converted to type @a _to if it does not yet have that type.
|
||||
std::string expressionAsType(Expression const& _expression, Type const& _to);
|
||||
std::ostream& defineExpression(Expression const& _expression);
|
||||
/// Defines only one of many variables corresponding to an expression.
|
||||
/// We start counting at 1 instead of 0.
|
||||
std::ostream& defineExpressionPart(Expression const& _expression, size_t _part);
|
||||
|
||||
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
|
||||
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);
|
||||
|
||||
/// @returns code to perform the given binary operation in the given type on the two values.
|
||||
std::string binaryOperation(
|
||||
langutil::Token _op,
|
||||
Type const& _type,
|
||||
std::string const& _left,
|
||||
std::string const& _right
|
||||
);
|
||||
|
||||
void setLValue(Expression const& _expression, std::unique_ptr<IRLValue> _lvalue);
|
||||
void generateLoop(
|
||||
Statement const& _body,
|
||||
Expression const* _conditionExpression,
|
||||
Statement const* _initExpression = nullptr,
|
||||
ExpressionStatement const* _loopExpression = nullptr,
|
||||
bool _isDoWhile = false
|
||||
);
|
||||
|
||||
static Type const& type(Expression const& _expression);
|
||||
|
||||
std::ostringstream m_code;
|
||||
IRGenerationContext& m_context;
|
||||
YulUtilFunctions& m_utils;
|
||||
std::unique_ptr<IRLValue> m_currentLValue;
|
||||
};
|
||||
|
||||
}
|
||||
|
122
libsolidity/codegen/ir/IRLValue.cpp
Normal file
122
libsolidity/codegen/ir/IRLValue.cpp
Normal 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");
|
||||
}
|
100
libsolidity/codegen/ir/IRLValue.h
Normal file
100
libsolidity/codegen/ir/IRLValue.h
Normal 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;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -21,42 +21,179 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
using namespace dev::solidity::smt;
|
||||
|
||||
EncodingContext::EncodingContext(SolverInterface& _solver):
|
||||
m_solver(_solver),
|
||||
m_thisAddress(make_unique<SymbolicAddressVariable>("this", m_solver))
|
||||
{
|
||||
auto sort = make_shared<smt::ArraySort>(
|
||||
make_shared<smt::Sort>(smt::Kind::Int),
|
||||
make_shared<smt::Sort>(smt::Kind::Int)
|
||||
auto sort = make_shared<ArraySort>(
|
||||
make_shared<Sort>(Kind::Int),
|
||||
make_shared<Sort>(Kind::Int)
|
||||
);
|
||||
m_balances = make_unique<SymbolicVariable>(sort, "balances", m_solver);
|
||||
}
|
||||
|
||||
void EncodingContext::reset()
|
||||
{
|
||||
resetAllVariables();
|
||||
m_expressions.clear();
|
||||
m_globalContext.clear();
|
||||
m_thisAddress->increaseIndex();
|
||||
m_balances->increaseIndex();
|
||||
}
|
||||
|
||||
smt::Expression EncodingContext::thisAddress()
|
||||
/// Variables.
|
||||
|
||||
shared_ptr<SymbolicVariable> EncodingContext::variable(solidity::VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(knownVariable(_varDecl), "");
|
||||
return m_variables[&_varDecl];
|
||||
}
|
||||
|
||||
bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(!knownVariable(_varDecl), "");
|
||||
auto const& type = _varDecl.type();
|
||||
auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), m_solver);
|
||||
m_variables.emplace(&_varDecl, result.second);
|
||||
return result.first;
|
||||
}
|
||||
|
||||
bool EncodingContext::knownVariable(solidity::VariableDeclaration const& _varDecl)
|
||||
{
|
||||
return m_variables.count(&_varDecl);
|
||||
}
|
||||
|
||||
void EncodingContext::resetVariable(solidity::VariableDeclaration const& _variable)
|
||||
{
|
||||
newValue(_variable);
|
||||
setUnknownValue(_variable);
|
||||
}
|
||||
|
||||
void EncodingContext::resetVariables(set<solidity::VariableDeclaration const*> const& _variables)
|
||||
{
|
||||
for (auto const* decl: _variables)
|
||||
resetVariable(*decl);
|
||||
}
|
||||
|
||||
void EncodingContext::resetVariables(function<bool(solidity::VariableDeclaration const&)> const& _filter)
|
||||
{
|
||||
for_each(begin(m_variables), end(m_variables), [&](auto _variable)
|
||||
{
|
||||
if (_filter(*_variable.first))
|
||||
this->resetVariable(*_variable.first);
|
||||
});
|
||||
}
|
||||
|
||||
void EncodingContext::resetAllVariables()
|
||||
{
|
||||
resetVariables([&](solidity::VariableDeclaration const&) { return true; });
|
||||
}
|
||||
|
||||
Expression EncodingContext::newValue(solidity::VariableDeclaration const& _decl)
|
||||
{
|
||||
solAssert(knownVariable(_decl), "");
|
||||
return m_variables.at(&_decl)->increaseIndex();
|
||||
}
|
||||
|
||||
void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl)
|
||||
{
|
||||
solAssert(knownVariable(_decl), "");
|
||||
setZeroValue(*m_variables.at(&_decl));
|
||||
}
|
||||
|
||||
void EncodingContext::setZeroValue(SymbolicVariable& _variable)
|
||||
{
|
||||
setSymbolicZeroValue(_variable, m_solver);
|
||||
}
|
||||
|
||||
void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl)
|
||||
{
|
||||
solAssert(knownVariable(_decl), "");
|
||||
setUnknownValue(*m_variables.at(&_decl));
|
||||
}
|
||||
|
||||
void EncodingContext::setUnknownValue(SymbolicVariable& _variable)
|
||||
{
|
||||
setSymbolicUnknownValue(_variable, m_solver);
|
||||
}
|
||||
|
||||
/// Expressions
|
||||
|
||||
shared_ptr<SymbolicVariable> EncodingContext::expression(solidity::Expression const& _e)
|
||||
{
|
||||
if (!knownExpression(_e))
|
||||
createExpression(_e);
|
||||
return m_expressions.at(&_e);
|
||||
}
|
||||
|
||||
bool EncodingContext::createExpression(solidity::Expression const& _e, shared_ptr<SymbolicVariable> _symbVar)
|
||||
{
|
||||
solAssert(_e.annotation().type, "");
|
||||
if (knownExpression(_e))
|
||||
{
|
||||
expression(_e)->increaseIndex();
|
||||
return false;
|
||||
}
|
||||
else if (_symbVar)
|
||||
{
|
||||
m_expressions.emplace(&_e, _symbVar);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), m_solver);
|
||||
m_expressions.emplace(&_e, result.second);
|
||||
return result.first;
|
||||
}
|
||||
}
|
||||
|
||||
bool EncodingContext::knownExpression(solidity::Expression const& _e) const
|
||||
{
|
||||
return m_expressions.count(&_e);
|
||||
}
|
||||
|
||||
/// Global variables and functions.
|
||||
|
||||
shared_ptr<SymbolicVariable> EncodingContext::globalSymbol(string const& _name)
|
||||
{
|
||||
solAssert(knownGlobalSymbol(_name), "");
|
||||
return m_globalContext.at(_name);
|
||||
}
|
||||
|
||||
bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr)
|
||||
{
|
||||
solAssert(!knownGlobalSymbol(_name), "");
|
||||
auto result = newSymbolicVariable(*_expr.annotation().type, _name, m_solver);
|
||||
m_globalContext.emplace(_name, result.second);
|
||||
setUnknownValue(*result.second);
|
||||
return result.first;
|
||||
}
|
||||
|
||||
bool EncodingContext::knownGlobalSymbol(string const& _var) const
|
||||
{
|
||||
return m_globalContext.count(_var);
|
||||
}
|
||||
|
||||
// Blockchain
|
||||
|
||||
Expression EncodingContext::thisAddress()
|
||||
{
|
||||
return m_thisAddress->currentValue();
|
||||
}
|
||||
|
||||
smt::Expression EncodingContext::balance()
|
||||
Expression EncodingContext::balance()
|
||||
{
|
||||
return balance(m_thisAddress->currentValue());
|
||||
}
|
||||
|
||||
smt::Expression EncodingContext::balance(smt::Expression _address)
|
||||
Expression EncodingContext::balance(Expression _address)
|
||||
{
|
||||
return smt::Expression::select(m_balances->currentValue(), move(_address));
|
||||
return Expression::select(m_balances->currentValue(), move(_address));
|
||||
}
|
||||
|
||||
void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value)
|
||||
void EncodingContext::transfer(Expression _from, Expression _to, Expression _value)
|
||||
{
|
||||
unsigned indexBefore = m_balances->index();
|
||||
addBalance(_from, 0 - _value);
|
||||
@ -65,7 +202,7 @@ void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::
|
||||
solAssert(indexAfter > indexBefore, "");
|
||||
m_balances->increaseIndex();
|
||||
/// Do not apply the transfer operation if _from == _to.
|
||||
auto newBalances = smt::Expression::ite(
|
||||
auto newBalances = Expression::ite(
|
||||
move(_from) == move(_to),
|
||||
m_balances->valueAtIndex(indexBefore),
|
||||
m_balances->valueAtIndex(indexAfter)
|
||||
@ -73,9 +210,9 @@ void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::
|
||||
m_solver.addAssertion(m_balances->currentValue() == newBalances);
|
||||
}
|
||||
|
||||
void EncodingContext::addBalance(smt::Expression _address, smt::Expression _value)
|
||||
void EncodingContext::addBalance(Expression _address, Expression _value)
|
||||
{
|
||||
auto newBalances = smt::Expression::store(
|
||||
auto newBalances = Expression::store(
|
||||
m_balances->currentValue(),
|
||||
_address,
|
||||
balance(_address) + move(_value)
|
||||
|
@ -20,6 +20,9 @@
|
||||
#include <libsolidity/formal/SolverInterface.h>
|
||||
#include <libsolidity/formal/SymbolicVariables.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
@ -38,22 +41,94 @@ public:
|
||||
/// Resets the entire context.
|
||||
void reset();
|
||||
|
||||
/// Value of `this` address.
|
||||
smt::Expression thisAddress();
|
||||
/// Methods related to variables.
|
||||
//@{
|
||||
/// @returns the symbolic representation of a program variable.
|
||||
std::shared_ptr<SymbolicVariable> variable(solidity::VariableDeclaration const& _varDecl);
|
||||
/// @returns all symbolic variables.
|
||||
std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> const& variables() const { return m_variables; }
|
||||
|
||||
/// Creates a symbolic variable and
|
||||
/// @returns true if a variable's type is not supported and is therefore abstract.
|
||||
bool createVariable(solidity::VariableDeclaration const& _varDecl);
|
||||
/// @returns true if variable was created.
|
||||
bool knownVariable(solidity::VariableDeclaration const& _varDecl);
|
||||
|
||||
/// Resets a specific variable.
|
||||
void resetVariable(solidity::VariableDeclaration const& _variable);
|
||||
/// Resets a set of variables.
|
||||
void resetVariables(std::set<solidity::VariableDeclaration const*> const& _variables);
|
||||
/// Resets variables according to a predicate.
|
||||
void resetVariables(std::function<bool(solidity::VariableDeclaration const&)> const& _filter);
|
||||
///Resets all variables.
|
||||
void resetAllVariables();
|
||||
|
||||
/// Allocates a new index for the declaration, updates the current
|
||||
/// index to this value and returns the expression.
|
||||
Expression newValue(solidity::VariableDeclaration const& _decl);
|
||||
/// Sets the value of the declaration to zero.
|
||||
void setZeroValue(solidity::VariableDeclaration const& _decl);
|
||||
void setZeroValue(SymbolicVariable& _variable);
|
||||
/// Resets the variable to an unknown value (in its range).
|
||||
void setUnknownValue(solidity::VariableDeclaration const& decl);
|
||||
void setUnknownValue(SymbolicVariable& _variable);
|
||||
//@}
|
||||
|
||||
/// Methods related to expressions.
|
||||
////@{
|
||||
/// @returns the symbolic representation of an AST node expression.
|
||||
std::shared_ptr<SymbolicVariable> expression(solidity::Expression const& _e);
|
||||
/// @returns all symbolic expressions.
|
||||
std::unordered_map<solidity::Expression const*, std::shared_ptr<SymbolicVariable>> const& expressions() const { return m_expressions; }
|
||||
|
||||
/// Creates the expression (value can be arbitrary).
|
||||
/// @returns true if type is not supported.
|
||||
bool createExpression(solidity::Expression const& _e, std::shared_ptr<SymbolicVariable> _symbExpr = nullptr);
|
||||
/// Checks if expression was created.
|
||||
bool knownExpression(solidity::Expression const& _e) const;
|
||||
//@}
|
||||
|
||||
/// Methods related to global variables and functions.
|
||||
//@{
|
||||
/// Global variables and functions.
|
||||
std::shared_ptr<SymbolicVariable> globalSymbol(std::string const& _name);
|
||||
/// @returns all symbolic variables.
|
||||
std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> const& globalSymbols() const { return m_globalContext; }
|
||||
/// Defines a new global variable or function
|
||||
/// and @returns true if type was abstracted.
|
||||
bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr);
|
||||
/// Checks if special variable or function was seen.
|
||||
bool knownGlobalSymbol(std::string const& _var) const;
|
||||
//@}
|
||||
|
||||
/// Blockchain related methods.
|
||||
//@{
|
||||
/// Value of `this` address.
|
||||
Expression thisAddress();
|
||||
/// @returns the symbolic balance of address `this`.
|
||||
smt::Expression balance();
|
||||
Expression balance();
|
||||
/// @returns the symbolic balance of an address.
|
||||
smt::Expression balance(smt::Expression _address);
|
||||
Expression balance(Expression _address);
|
||||
/// Transfer _value from _from to _to.
|
||||
void transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value);
|
||||
void transfer(Expression _from, Expression _to, Expression _value);
|
||||
//@}
|
||||
|
||||
private:
|
||||
/// Adds _value to _account's balance.
|
||||
void addBalance(smt::Expression _account, smt::Expression _value);
|
||||
void addBalance(Expression _account, Expression _value);
|
||||
|
||||
SolverInterface& m_solver;
|
||||
|
||||
/// Symbolic variables.
|
||||
std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables;
|
||||
|
||||
/// Symbolic expressions.
|
||||
std::unordered_map<solidity::Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions;
|
||||
|
||||
/// Symbolic representation of global symbols including
|
||||
/// variables and functions.
|
||||
std::unordered_map<std::string, std::shared_ptr<smt::SymbolicVariable>> m_globalContext;
|
||||
|
||||
/// Symbolic `this` address.
|
||||
std::unique_ptr<SymbolicAddressVariable> m_thisAddress;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -55,9 +55,11 @@ public:
|
||||
/// the constructor.
|
||||
std::vector<std::string> unhandledQueries() { return m_interface->unhandledQueries(); }
|
||||
|
||||
/// @return the FunctionDefinition of a called function if possible and should inline,
|
||||
/// @returns the FunctionDefinition of a called function if possible and should inline,
|
||||
/// otherwise nullptr.
|
||||
static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall);
|
||||
/// @returns the leftmost identifier in a multi-d IndexAccess.
|
||||
static Expression const* leftmostBase(IndexAccess const& _indexAccess);
|
||||
|
||||
private:
|
||||
// TODO: Check that we do not have concurrent reads and writes to a variable,
|
||||
@ -115,14 +117,18 @@ private:
|
||||
void inlineFunctionCall(FunctionCall const& _funCall);
|
||||
/// Creates an uninterpreted function call.
|
||||
void abstractFunctionCall(FunctionCall const& _funCall);
|
||||
/// Inlines if the function call is internal or external to `this`.
|
||||
/// Erases knowledge about state variables if external.
|
||||
void internalOrExternalFunctionCall(FunctionCall const& _funCall);
|
||||
void visitFunctionIdentifier(Identifier const& _identifier);
|
||||
|
||||
/// Encodes a modifier or function body according to the modifier
|
||||
/// visit depth.
|
||||
void visitFunctionOrModifier();
|
||||
|
||||
/// Defines a new global variable or function.
|
||||
void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false);
|
||||
void defineGlobalFunction(std::string const& _name, Expression const& _expr);
|
||||
|
||||
/// Handles the side effects of assignment
|
||||
/// to variable of some SMT array type
|
||||
/// while aliasing is not supported.
|
||||
@ -135,7 +141,18 @@ private:
|
||||
smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
|
||||
|
||||
void assignment(VariableDeclaration const& _variable, Expression const& _value, langutil::SourceLocation const& _location);
|
||||
/// Handles assignments to variables of different types.
|
||||
void assignment(VariableDeclaration const& _variable, smt::Expression const& _value, langutil::SourceLocation const& _location);
|
||||
/// Handles assignments between generic expressions.
|
||||
/// Will also be used for assignments of tuple components.
|
||||
void assignment(
|
||||
Expression const& _left,
|
||||
std::vector<smt::Expression> const& _right,
|
||||
TypePointer const& _type,
|
||||
langutil::SourceLocation const& _location
|
||||
);
|
||||
/// Computes the right hand side of a compound assignment.
|
||||
smt::Expression compoundAssignment(Assignment const& _assignment);
|
||||
|
||||
/// Maps a variable to an SSA index.
|
||||
using VariableIndices = std::unordered_map<VariableDeclaration const*, int>;
|
||||
@ -162,6 +179,8 @@ private:
|
||||
std::string const& _description
|
||||
);
|
||||
|
||||
using CallStackEntry = std::pair<CallableDeclaration const*, ASTNode const*>;
|
||||
|
||||
struct OverflowTarget
|
||||
{
|
||||
enum class Type { Underflow, Overflow, All } type;
|
||||
@ -169,9 +188,9 @@ private:
|
||||
smt::Expression value;
|
||||
smt::Expression path;
|
||||
langutil::SourceLocation const& location;
|
||||
std::vector<ASTNode const*> callStack;
|
||||
std::vector<CallStackEntry> callStack;
|
||||
|
||||
OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<ASTNode const*> _callStack):
|
||||
OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<CallStackEntry> _callStack):
|
||||
type(_type),
|
||||
intType(_intType),
|
||||
value(_value),
|
||||
@ -198,11 +217,8 @@ private:
|
||||
|
||||
void initializeLocalVariables(FunctionDefinition const& _function);
|
||||
void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smt::Expression> const& _callArgs);
|
||||
void resetVariable(VariableDeclaration const& _variable);
|
||||
void resetStateVariables();
|
||||
void resetStorageReferences();
|
||||
void resetVariables(std::set<VariableDeclaration const*> const& _variables);
|
||||
void resetVariables(std::function<bool(VariableDeclaration const&)> const& _filter);
|
||||
/// @returns the type without storage pointer information if it has it.
|
||||
TypePointer typeWithoutPointer(TypePointer const& _type);
|
||||
|
||||
@ -211,41 +227,21 @@ private:
|
||||
/// using the branch condition as guard.
|
||||
void mergeVariables(std::set<VariableDeclaration const*> const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse);
|
||||
/// Tries to create an uninitialized variable and returns true on success.
|
||||
/// This fails if the type is not supported.
|
||||
bool createVariable(VariableDeclaration const& _varDecl);
|
||||
|
||||
/// @returns true if _delc is a variable that is known at the current point, i.e.
|
||||
/// has a valid index
|
||||
bool knownVariable(VariableDeclaration const& _decl);
|
||||
/// @returns an expression denoting the value of the variable declared in @a _decl
|
||||
/// at the current point.
|
||||
smt::Expression currentValue(VariableDeclaration const& _decl);
|
||||
/// @returns an expression denoting the value of the variable declared in @a _decl
|
||||
/// at the given index. Does not ensure that this index exists.
|
||||
smt::Expression valueAtIndex(VariableDeclaration const& _decl, int _index);
|
||||
/// Allocates a new index for the declaration, updates the current
|
||||
/// index to this value and returns the expression.
|
||||
smt::Expression newValue(VariableDeclaration const& _decl);
|
||||
|
||||
/// Sets the value of the declaration to zero.
|
||||
void setZeroValue(VariableDeclaration const& _decl);
|
||||
void setZeroValue(SymbolicVariable& _variable);
|
||||
/// Resets the variable to an unknown value (in its range).
|
||||
void setUnknownValue(VariableDeclaration const& decl);
|
||||
void setUnknownValue(SymbolicVariable& _variable);
|
||||
|
||||
/// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
|
||||
smt::Expression expr(Expression const& _e);
|
||||
/// Creates the expression (value can be arbitrary)
|
||||
void createExpr(Expression const& _e);
|
||||
/// Checks if expression was created
|
||||
bool knownExpr(Expression const& _e) const;
|
||||
/// Creates the expression and sets its value.
|
||||
void defineExpr(Expression const& _e, smt::Expression _value);
|
||||
|
||||
/// Checks if special variable or function was seen.
|
||||
bool knownGlobalSymbol(std::string const& _var) const;
|
||||
|
||||
/// Adds a new path condition
|
||||
void pushPathCondition(smt::Expression const& _e);
|
||||
/// Remove the last path condition
|
||||
@ -255,17 +251,14 @@ private:
|
||||
/// Returns the current callstack. Used for models.
|
||||
langutil::SecondarySourceLocation currentCallStack();
|
||||
/// Copies and pops the last called node.
|
||||
ASTNode const* popCallStack();
|
||||
/// Adds @param _node to the callstack.
|
||||
void pushCallStack(ASTNode const* _node);
|
||||
CallStackEntry popCallStack();
|
||||
/// Adds (_definition, _node) to the callstack.
|
||||
void pushCallStack(CallStackEntry _entry);
|
||||
/// Conjoin the current path conditions with the given parameter and add to the solver
|
||||
void addPathConjoinedExpression(smt::Expression const& _e);
|
||||
/// Add to the solver: the given expression implied by the current path conditions
|
||||
void addPathImpliedExpression(smt::Expression const& _e);
|
||||
|
||||
/// Removes local variables from the context.
|
||||
void removeLocalVariables();
|
||||
|
||||
/// Copy the SSA indices of m_variables.
|
||||
VariableIndices copyVariableIndices();
|
||||
/// Resets the variable indices.
|
||||
@ -274,18 +267,16 @@ private:
|
||||
/// @returns variables that are touched in _node's subtree.
|
||||
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node);
|
||||
|
||||
std::shared_ptr<smt::SolverInterface> m_interface;
|
||||
VariableUsage m_variableUsage;
|
||||
/// @returns the VariableDeclaration referenced by an Identifier or nullptr.
|
||||
VariableDeclaration const* identifierToVariable(Expression const& _expr);
|
||||
|
||||
std::unique_ptr<smt::SolverInterface> m_interface;
|
||||
smt::VariableUsage m_variableUsage;
|
||||
bool m_loopExecutionHappened = false;
|
||||
bool m_arrayAssignmentHappened = false;
|
||||
bool m_externalFunctionCallHappened = false;
|
||||
// True if the "No SMT solver available" warning was already created.
|
||||
bool m_noSolverWarning = false;
|
||||
/// An Expression may have multiple smt::Expression due to
|
||||
/// repeated calls to the same function.
|
||||
std::unordered_map<Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions;
|
||||
std::unordered_map<VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables;
|
||||
std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> m_globalContext;
|
||||
|
||||
/// Stores the instances of an Uninterpreted Function applied to arguments.
|
||||
/// These may be direct application of UFs or Array index access.
|
||||
@ -301,10 +292,8 @@ private:
|
||||
langutil::ErrorList m_smtErrors;
|
||||
std::shared_ptr<langutil::Scanner> m_scanner;
|
||||
|
||||
/// Stores the current path of function calls.
|
||||
std::vector<FunctionDefinition const*> m_functionPath;
|
||||
/// Stores the current call/invocation path.
|
||||
std::vector<ASTNode const*> m_callStack;
|
||||
/// Stores the current function/modifier call/invocation path.
|
||||
std::vector<CallStackEntry> m_callStack;
|
||||
/// Returns true if the current function was not visited by
|
||||
/// a function call.
|
||||
bool isRootFunction();
|
||||
|
@ -32,42 +32,42 @@ using namespace dev::solidity::smt;
|
||||
|
||||
SMTPortfolio::SMTPortfolio(map<h256, string> const& _smtlib2Responses)
|
||||
{
|
||||
m_solvers.emplace_back(make_shared<smt::SMTLib2Interface>(_smtlib2Responses));
|
||||
m_solvers.emplace_back(make_unique<smt::SMTLib2Interface>(_smtlib2Responses));
|
||||
#ifdef HAVE_Z3
|
||||
m_solvers.emplace_back(make_shared<smt::Z3Interface>());
|
||||
m_solvers.emplace_back(make_unique<smt::Z3Interface>());
|
||||
#endif
|
||||
#ifdef HAVE_CVC4
|
||||
m_solvers.emplace_back(make_shared<smt::CVC4Interface>());
|
||||
m_solvers.emplace_back(make_unique<smt::CVC4Interface>());
|
||||
#endif
|
||||
}
|
||||
|
||||
void SMTPortfolio::reset()
|
||||
{
|
||||
for (auto s : m_solvers)
|
||||
for (auto const& s: m_solvers)
|
||||
s->reset();
|
||||
}
|
||||
|
||||
void SMTPortfolio::push()
|
||||
{
|
||||
for (auto s : m_solvers)
|
||||
for (auto const& s: m_solvers)
|
||||
s->push();
|
||||
}
|
||||
|
||||
void SMTPortfolio::pop()
|
||||
{
|
||||
for (auto s : m_solvers)
|
||||
for (auto const& s: m_solvers)
|
||||
s->pop();
|
||||
}
|
||||
|
||||
void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort)
|
||||
{
|
||||
for (auto s : m_solvers)
|
||||
for (auto const& s: m_solvers)
|
||||
s->declareVariable(_name, _sort);
|
||||
}
|
||||
|
||||
void SMTPortfolio::addAssertion(Expression const& _expr)
|
||||
{
|
||||
for (auto s : m_solvers)
|
||||
for (auto const& s: m_solvers)
|
||||
s->addAssertion(_expr);
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ pair<CheckResult, vector<string>> SMTPortfolio::check(vector<Expression> const&
|
||||
{
|
||||
CheckResult lastResult = CheckResult::ERROR;
|
||||
vector<string> finalValues;
|
||||
for (auto s : m_solvers)
|
||||
for (auto const& s: m_solvers)
|
||||
{
|
||||
CheckResult result;
|
||||
vector<string> values;
|
||||
@ -134,8 +134,8 @@ vector<string> SMTPortfolio::unhandledQueries()
|
||||
// This code assumes that the constructor guarantees that
|
||||
// SmtLib2Interface is in position 0.
|
||||
solAssert(!m_solvers.empty(), "");
|
||||
solAssert(dynamic_cast<smt::SMTLib2Interface*>(m_solvers.at(0).get()), "");
|
||||
return m_solvers.at(0)->unhandledQueries();
|
||||
solAssert(dynamic_cast<smt::SMTLib2Interface*>(m_solvers.front().get()), "");
|
||||
return m_solvers.front()->unhandledQueries();
|
||||
}
|
||||
|
||||
bool SMTPortfolio::solverAnswered(CheckResult result)
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||
private:
|
||||
static bool solverAnswered(CheckResult result);
|
||||
|
||||
std::vector<std::shared_ptr<smt::SolverInterface>> m_solvers;
|
||||
std::vector<std::unique_ptr<smt::SolverInterface>> m_solvers;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include <libsolidity/formal/SSAVariable.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev::solidity;
|
||||
using namespace dev::solidity::smt;
|
||||
|
||||
SSAVariable::SSAVariable()
|
||||
{
|
||||
@ -28,6 +28,5 @@ SSAVariable::SSAVariable()
|
||||
void SSAVariable::resetIndex()
|
||||
{
|
||||
m_currentIndex = 0;
|
||||
m_nextFreeIndex.reset (new unsigned);
|
||||
*m_nextFreeIndex = 1;
|
||||
m_nextFreeIndex = make_unique<unsigned>(1);
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace smt
|
||||
{
|
||||
|
||||
/**
|
||||
* This class represents the SSA representation of a program variable.
|
||||
@ -44,10 +46,9 @@ public:
|
||||
|
||||
private:
|
||||
unsigned m_currentIndex;
|
||||
/// The next free index is a shared pointer because we want
|
||||
/// the copy and the copied to share it.
|
||||
std::shared_ptr<unsigned> m_nextFreeIndex;
|
||||
std::unique_ptr<unsigned> m_nextFreeIndex;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,105 +22,112 @@
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev::solidity;
|
||||
|
||||
smt::SortPointer dev::solidity::smtSort(Type const& _type)
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace smt
|
||||
{
|
||||
|
||||
SortPointer smtSort(solidity::Type const& _type)
|
||||
{
|
||||
switch (smtKind(_type.category()))
|
||||
{
|
||||
case smt::Kind::Int:
|
||||
return make_shared<smt::Sort>(smt::Kind::Int);
|
||||
case smt::Kind::Bool:
|
||||
return make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
case smt::Kind::Function:
|
||||
case Kind::Int:
|
||||
return make_shared<Sort>(Kind::Int);
|
||||
case Kind::Bool:
|
||||
return make_shared<Sort>(Kind::Bool);
|
||||
case Kind::Function:
|
||||
{
|
||||
auto fType = dynamic_cast<FunctionType const*>(&_type);
|
||||
auto fType = dynamic_cast<solidity::FunctionType const*>(&_type);
|
||||
solAssert(fType, "");
|
||||
vector<smt::SortPointer> parameterSorts = smtSort(fType->parameterTypes());
|
||||
vector<SortPointer> parameterSorts = smtSort(fType->parameterTypes());
|
||||
auto returnTypes = fType->returnParameterTypes();
|
||||
smt::SortPointer returnSort;
|
||||
SortPointer returnSort;
|
||||
// TODO change this when we support tuples.
|
||||
if (returnTypes.size() == 0)
|
||||
// We cannot declare functions without a return sort, so we use the smallest.
|
||||
returnSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
returnSort = make_shared<Sort>(Kind::Bool);
|
||||
else if (returnTypes.size() > 1)
|
||||
// Abstract sort.
|
||||
returnSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||
returnSort = make_shared<Sort>(Kind::Int);
|
||||
else
|
||||
returnSort = smtSort(*returnTypes.at(0));
|
||||
return make_shared<smt::FunctionSort>(parameterSorts, returnSort);
|
||||
returnSort = smtSort(*returnTypes.front());
|
||||
return make_shared<FunctionSort>(parameterSorts, returnSort);
|
||||
}
|
||||
case smt::Kind::Array:
|
||||
case Kind::Array:
|
||||
{
|
||||
if (isMapping(_type.category()))
|
||||
{
|
||||
auto mapType = dynamic_cast<MappingType const*>(&_type);
|
||||
auto mapType = dynamic_cast<solidity::MappingType const*>(&_type);
|
||||
solAssert(mapType, "");
|
||||
return make_shared<smt::ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType()));
|
||||
return make_shared<ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType()));
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(isArray(_type.category()), "");
|
||||
auto arrayType = dynamic_cast<ArrayType const*>(&_type);
|
||||
auto arrayType = dynamic_cast<solidity::ArrayType const*>(&_type);
|
||||
solAssert(arrayType, "");
|
||||
return make_shared<smt::ArraySort>(make_shared<smt::Sort>(smt::Kind::Int), smtSort(*arrayType->baseType()));
|
||||
return make_shared<ArraySort>(make_shared<Sort>(Kind::Int), smtSort(*arrayType->baseType()));
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Abstract case.
|
||||
return make_shared<smt::Sort>(smt::Kind::Int);
|
||||
return make_shared<Sort>(Kind::Int);
|
||||
}
|
||||
}
|
||||
|
||||
vector<smt::SortPointer> dev::solidity::smtSort(vector<TypePointer> const& _types)
|
||||
vector<SortPointer> smtSort(vector<solidity::TypePointer> const& _types)
|
||||
{
|
||||
vector<smt::SortPointer> sorts;
|
||||
vector<SortPointer> sorts;
|
||||
for (auto const& type: _types)
|
||||
sorts.push_back(smtSort(*type));
|
||||
return sorts;
|
||||
}
|
||||
|
||||
smt::Kind dev::solidity::smtKind(Type::Category _category)
|
||||
Kind smtKind(solidity::Type::Category _category)
|
||||
{
|
||||
if (isNumber(_category))
|
||||
return smt::Kind::Int;
|
||||
return Kind::Int;
|
||||
else if (isBool(_category))
|
||||
return smt::Kind::Bool;
|
||||
return Kind::Bool;
|
||||
else if (isFunction(_category))
|
||||
return smt::Kind::Function;
|
||||
return Kind::Function;
|
||||
else if (isMapping(_category) || isArray(_category))
|
||||
return smt::Kind::Array;
|
||||
return Kind::Array;
|
||||
// Abstract case.
|
||||
return smt::Kind::Int;
|
||||
return Kind::Int;
|
||||
}
|
||||
|
||||
bool dev::solidity::isSupportedType(Type::Category _category)
|
||||
bool isSupportedType(solidity::Type::Category _category)
|
||||
{
|
||||
return isNumber(_category) ||
|
||||
isBool(_category) ||
|
||||
isMapping(_category) ||
|
||||
isArray(_category);
|
||||
isArray(_category) ||
|
||||
isTuple(_category);
|
||||
}
|
||||
|
||||
bool dev::solidity::isSupportedTypeDeclaration(Type::Category _category)
|
||||
bool isSupportedTypeDeclaration(solidity::Type::Category _category)
|
||||
{
|
||||
return isSupportedType(_category) ||
|
||||
isFunction(_category);
|
||||
}
|
||||
|
||||
pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
|
||||
Type const& _type,
|
||||
pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
solidity::Type const& _type,
|
||||
std::string const& _uniqueName,
|
||||
smt::SolverInterface& _solver
|
||||
SolverInterface& _solver
|
||||
)
|
||||
{
|
||||
bool abstract = false;
|
||||
shared_ptr<SymbolicVariable> var;
|
||||
TypePointer type = &_type;
|
||||
solidity::TypePointer type = &_type;
|
||||
if (!isSupportedTypeDeclaration(_type))
|
||||
{
|
||||
abstract = true;
|
||||
var = make_shared<SymbolicIntVariable>(TypeProvider::uint256(), _uniqueName, _solver);
|
||||
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver);
|
||||
}
|
||||
else if (isBool(_type.category()))
|
||||
var = make_shared<SymbolicBoolVariable>(type, _uniqueName, _solver);
|
||||
@ -130,7 +137,7 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
|
||||
var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver);
|
||||
else if (isFixedBytes(_type.category()))
|
||||
{
|
||||
auto fixedBytesType = dynamic_cast<FixedBytesType const*>(type);
|
||||
auto fixedBytesType = dynamic_cast<solidity::FixedBytesType const*>(type);
|
||||
solAssert(fixedBytesType, "");
|
||||
var = make_shared<SymbolicFixedBytesVariable>(fixedBytesType->numBytes(), _uniqueName, _solver);
|
||||
}
|
||||
@ -140,10 +147,10 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
|
||||
var = make_shared<SymbolicEnumVariable>(type, _uniqueName, _solver);
|
||||
else if (isRational(_type.category()))
|
||||
{
|
||||
auto rational = dynamic_cast<RationalNumberType const*>(&_type);
|
||||
auto rational = dynamic_cast<solidity::RationalNumberType const*>(&_type);
|
||||
solAssert(rational, "");
|
||||
if (rational->isFractional())
|
||||
var = make_shared<SymbolicIntVariable>(TypeProvider::uint256(), _uniqueName, _solver);
|
||||
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver);
|
||||
else
|
||||
var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver);
|
||||
}
|
||||
@ -151,52 +158,54 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
|
||||
var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _solver);
|
||||
else if (isArray(_type.category()))
|
||||
var = make_shared<SymbolicArrayVariable>(type, _uniqueName, _solver);
|
||||
else if (isTuple(_type.category()))
|
||||
var = make_shared<SymbolicTupleVariable>(type, _uniqueName, _solver);
|
||||
else
|
||||
solAssert(false, "");
|
||||
return make_pair(abstract, var);
|
||||
}
|
||||
|
||||
bool dev::solidity::isSupportedType(Type const& _type)
|
||||
bool isSupportedType(solidity::Type const& _type)
|
||||
{
|
||||
return isSupportedType(_type.category());
|
||||
}
|
||||
|
||||
bool dev::solidity::isSupportedTypeDeclaration(Type const& _type)
|
||||
bool isSupportedTypeDeclaration(solidity::Type const& _type)
|
||||
{
|
||||
return isSupportedTypeDeclaration(_type.category());
|
||||
}
|
||||
|
||||
bool dev::solidity::isInteger(Type::Category _category)
|
||||
bool isInteger(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Integer;
|
||||
return _category == solidity::Type::Category::Integer;
|
||||
}
|
||||
|
||||
bool dev::solidity::isRational(Type::Category _category)
|
||||
bool isRational(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::RationalNumber;
|
||||
return _category == solidity::Type::Category::RationalNumber;
|
||||
}
|
||||
|
||||
bool dev::solidity::isFixedBytes(Type::Category _category)
|
||||
bool isFixedBytes(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::FixedBytes;
|
||||
return _category == solidity::Type::Category::FixedBytes;
|
||||
}
|
||||
|
||||
bool dev::solidity::isAddress(Type::Category _category)
|
||||
bool isAddress(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Address;
|
||||
return _category == solidity::Type::Category::Address;
|
||||
}
|
||||
|
||||
bool dev::solidity::isContract(Type::Category _category)
|
||||
bool isContract(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Contract;
|
||||
return _category == solidity::Type::Category::Contract;
|
||||
}
|
||||
|
||||
bool dev::solidity::isEnum(Type::Category _category)
|
||||
bool isEnum(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Enum;
|
||||
return _category == solidity::Type::Category::Enum;
|
||||
}
|
||||
|
||||
bool dev::solidity::isNumber(Type::Category _category)
|
||||
bool isNumber(solidity::Type::Category _category)
|
||||
{
|
||||
return isInteger(_category) ||
|
||||
isRational(_category) ||
|
||||
@ -206,70 +215,79 @@ bool dev::solidity::isNumber(Type::Category _category)
|
||||
isEnum(_category);
|
||||
}
|
||||
|
||||
bool dev::solidity::isBool(Type::Category _category)
|
||||
bool isBool(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Bool;
|
||||
return _category == solidity::Type::Category::Bool;
|
||||
}
|
||||
|
||||
bool dev::solidity::isFunction(Type::Category _category)
|
||||
bool isFunction(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Function;
|
||||
return _category == solidity::Type::Category::Function;
|
||||
}
|
||||
|
||||
bool dev::solidity::isMapping(Type::Category _category)
|
||||
bool isMapping(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Mapping;
|
||||
return _category == solidity::Type::Category::Mapping;
|
||||
}
|
||||
|
||||
bool dev::solidity::isArray(Type::Category _category)
|
||||
bool isArray(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Array;
|
||||
return _category == solidity::Type::Category::Array;
|
||||
}
|
||||
|
||||
smt::Expression dev::solidity::minValue(IntegerType const& _type)
|
||||
bool isTuple(solidity::Type::Category _category)
|
||||
{
|
||||
return smt::Expression(_type.minValue());
|
||||
return _category == solidity::Type::Category::Tuple;
|
||||
}
|
||||
|
||||
smt::Expression dev::solidity::maxValue(IntegerType const& _type)
|
||||
Expression minValue(solidity::IntegerType const& _type)
|
||||
{
|
||||
return smt::Expression(_type.maxValue());
|
||||
return Expression(_type.minValue());
|
||||
}
|
||||
|
||||
void dev::solidity::smt::setSymbolicZeroValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface)
|
||||
Expression maxValue(solidity::IntegerType const& _type)
|
||||
{
|
||||
return Expression(_type.maxValue());
|
||||
}
|
||||
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface)
|
||||
{
|
||||
setSymbolicZeroValue(_variable.currentValue(), _variable.type(), _interface);
|
||||
}
|
||||
|
||||
void dev::solidity::smt::setSymbolicZeroValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface)
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isInteger(_type->category()))
|
||||
_interface.addAssertion(_expr == 0);
|
||||
else if (isBool(_type->category()))
|
||||
_interface.addAssertion(_expr == smt::Expression(false));
|
||||
_interface.addAssertion(_expr == Expression(false));
|
||||
}
|
||||
|
||||
void dev::solidity::smt::setSymbolicUnknownValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface)
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface)
|
||||
{
|
||||
setSymbolicUnknownValue(_variable.currentValue(), _variable.type(), _interface);
|
||||
}
|
||||
|
||||
void dev::solidity::smt::setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface)
|
||||
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isEnum(_type->category()))
|
||||
{
|
||||
auto enumType = dynamic_cast<EnumType const*>(_type);
|
||||
auto enumType = dynamic_cast<solidity::EnumType const*>(_type);
|
||||
solAssert(enumType, "");
|
||||
_interface.addAssertion(_expr >= 0);
|
||||
_interface.addAssertion(_expr < enumType->numberOfMembers());
|
||||
}
|
||||
else if (isInteger(_type->category()))
|
||||
{
|
||||
auto intType = dynamic_cast<IntegerType const*>(_type);
|
||||
auto intType = dynamic_cast<solidity::IntegerType const*>(_type);
|
||||
solAssert(intType, "");
|
||||
_interface.addAssertion(_expr >= minValue(*intType));
|
||||
_interface.addAssertion(_expr <= maxValue(*intType));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,49 +26,48 @@ namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace smt
|
||||
{
|
||||
|
||||
/// Returns the SMT sort that models the Solidity type _type.
|
||||
smt::SortPointer smtSort(Type const& _type);
|
||||
std::vector<smt::SortPointer> smtSort(std::vector<TypePointer> const& _types);
|
||||
SortPointer smtSort(solidity::Type const& _type);
|
||||
std::vector<SortPointer> smtSort(std::vector<solidity::TypePointer> const& _types);
|
||||
/// Returns the SMT kind that models the Solidity type type category _category.
|
||||
smt::Kind smtKind(Type::Category _category);
|
||||
Kind smtKind(solidity::Type::Category _category);
|
||||
|
||||
/// Returns true if type is fully supported (declaration and operations).
|
||||
bool isSupportedType(Type::Category _category);
|
||||
bool isSupportedType(Type const& _type);
|
||||
bool isSupportedType(solidity::Type::Category _category);
|
||||
bool isSupportedType(solidity::Type const& _type);
|
||||
/// Returns true if type is partially supported (declaration).
|
||||
bool isSupportedTypeDeclaration(Type::Category _category);
|
||||
bool isSupportedTypeDeclaration(Type const& _type);
|
||||
bool isSupportedTypeDeclaration(solidity::Type::Category _category);
|
||||
bool isSupportedTypeDeclaration(solidity::Type const& _type);
|
||||
|
||||
bool isInteger(Type::Category _category);
|
||||
bool isRational(Type::Category _category);
|
||||
bool isFixedBytes(Type::Category _category);
|
||||
bool isAddress(Type::Category _category);
|
||||
bool isContract(Type::Category _category);
|
||||
bool isEnum(Type::Category _category);
|
||||
bool isNumber(Type::Category _category);
|
||||
bool isBool(Type::Category _category);
|
||||
bool isFunction(Type::Category _category);
|
||||
bool isMapping(Type::Category _category);
|
||||
bool isArray(Type::Category _category);
|
||||
bool isInteger(solidity::Type::Category _category);
|
||||
bool isRational(solidity::Type::Category _category);
|
||||
bool isFixedBytes(solidity::Type::Category _category);
|
||||
bool isAddress(solidity::Type::Category _category);
|
||||
bool isContract(solidity::Type::Category _category);
|
||||
bool isEnum(solidity::Type::Category _category);
|
||||
bool isNumber(solidity::Type::Category _category);
|
||||
bool isBool(solidity::Type::Category _category);
|
||||
bool isFunction(solidity::Type::Category _category);
|
||||
bool isMapping(solidity::Type::Category _category);
|
||||
bool isArray(solidity::Type::Category _category);
|
||||
bool isTuple(solidity::Type::Category _category);
|
||||
|
||||
/// Returns a new symbolic variable, according to _type.
|
||||
/// Also returns whether the type is abstract or not,
|
||||
/// which is true for unsupported types.
|
||||
std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(Type const& _type, std::string const& _uniqueName, smt::SolverInterface& _solver);
|
||||
std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(solidity::Type const& _type, std::string const& _uniqueName, SolverInterface& _solver);
|
||||
|
||||
smt::Expression minValue(IntegerType const& _type);
|
||||
smt::Expression maxValue(IntegerType const& _type);
|
||||
Expression minValue(solidity::IntegerType const& _type);
|
||||
Expression maxValue(solidity::IntegerType const& _type);
|
||||
|
||||
namespace smt
|
||||
{
|
||||
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface);
|
||||
void setSymbolicZeroValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface);
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface);
|
||||
void setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface);
|
||||
|
||||
}
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface);
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface);
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface);
|
||||
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,17 +23,17 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
using namespace dev::solidity::smt;
|
||||
|
||||
SymbolicVariable::SymbolicVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
m_type(move(_type)),
|
||||
m_uniqueName(move(_uniqueName)),
|
||||
m_interface(_interface),
|
||||
m_ssa(make_shared<SSAVariable>())
|
||||
m_ssa(make_unique<SSAVariable>())
|
||||
{
|
||||
solAssert(m_type, "");
|
||||
m_sort = smtSort(*m_type);
|
||||
@ -41,19 +41,19 @@ SymbolicVariable::SymbolicVariable(
|
||||
}
|
||||
|
||||
SymbolicVariable::SymbolicVariable(
|
||||
smt::SortPointer _sort,
|
||||
SortPointer _sort,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
m_sort(move(_sort)),
|
||||
m_uniqueName(move(_uniqueName)),
|
||||
m_interface(_interface),
|
||||
m_ssa(make_shared<SSAVariable>())
|
||||
m_ssa(make_unique<SSAVariable>())
|
||||
{
|
||||
solAssert(m_sort, "");
|
||||
}
|
||||
|
||||
smt::Expression SymbolicVariable::currentValue() const
|
||||
Expression SymbolicVariable::currentValue() const
|
||||
{
|
||||
return valueAtIndex(m_ssa->index());
|
||||
}
|
||||
@ -63,7 +63,7 @@ string SymbolicVariable::currentName() const
|
||||
return uniqueSymbol(m_ssa->index());
|
||||
}
|
||||
|
||||
smt::Expression SymbolicVariable::valueAtIndex(int _index) const
|
||||
Expression SymbolicVariable::valueAtIndex(int _index) const
|
||||
{
|
||||
return m_interface.newVariable(uniqueSymbol(_index), m_sort);
|
||||
}
|
||||
@ -73,26 +73,26 @@ string SymbolicVariable::uniqueSymbol(unsigned _index) const
|
||||
return m_uniqueName + "_" + to_string(_index);
|
||||
}
|
||||
|
||||
smt::Expression SymbolicVariable::increaseIndex()
|
||||
Expression SymbolicVariable::increaseIndex()
|
||||
{
|
||||
++(*m_ssa);
|
||||
return currentValue();
|
||||
}
|
||||
|
||||
SymbolicBoolVariable::SymbolicBoolVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
{
|
||||
solAssert(m_type->category() == Type::Category::Bool, "");
|
||||
solAssert(m_type->category() == solidity::Type::Category::Bool, "");
|
||||
}
|
||||
|
||||
SymbolicIntVariable::SymbolicIntVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
{
|
||||
@ -101,7 +101,7 @@ SymbolicIntVariable::SymbolicIntVariable(
|
||||
|
||||
SymbolicAddressVariable::SymbolicAddressVariable(
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicIntVariable(TypeProvider::uint(160), move(_uniqueName), _interface)
|
||||
{
|
||||
@ -110,21 +110,21 @@ SymbolicAddressVariable::SymbolicAddressVariable(
|
||||
SymbolicFixedBytesVariable::SymbolicFixedBytesVariable(
|
||||
unsigned _numBytes,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), move(_uniqueName), _interface)
|
||||
{
|
||||
}
|
||||
|
||||
SymbolicFunctionVariable::SymbolicFunctionVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface),
|
||||
m_declaration(m_interface.newVariable(currentName(), m_sort))
|
||||
{
|
||||
solAssert(m_type->category() == Type::Category::Function, "");
|
||||
solAssert(m_type->category() == solidity::Type::Category::Function, "");
|
||||
}
|
||||
|
||||
void SymbolicFunctionVariable::resetDeclaration()
|
||||
@ -132,22 +132,22 @@ void SymbolicFunctionVariable::resetDeclaration()
|
||||
m_declaration = m_interface.newVariable(currentName(), m_sort);
|
||||
}
|
||||
|
||||
smt::Expression SymbolicFunctionVariable::increaseIndex()
|
||||
Expression SymbolicFunctionVariable::increaseIndex()
|
||||
{
|
||||
++(*m_ssa);
|
||||
resetDeclaration();
|
||||
return currentValue();
|
||||
}
|
||||
|
||||
smt::Expression SymbolicFunctionVariable::operator()(vector<smt::Expression> _arguments) const
|
||||
Expression SymbolicFunctionVariable::operator()(vector<Expression> _arguments) const
|
||||
{
|
||||
return m_declaration(_arguments);
|
||||
}
|
||||
|
||||
SymbolicMappingVariable::SymbolicMappingVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
{
|
||||
@ -155,9 +155,9 @@ SymbolicMappingVariable::SymbolicMappingVariable(
|
||||
}
|
||||
|
||||
SymbolicArrayVariable::SymbolicArrayVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
{
|
||||
@ -165,11 +165,29 @@ SymbolicArrayVariable::SymbolicArrayVariable(
|
||||
}
|
||||
|
||||
SymbolicEnumVariable::SymbolicEnumVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
{
|
||||
solAssert(isEnum(m_type->category()), "");
|
||||
}
|
||||
|
||||
SymbolicTupleVariable::SymbolicTupleVariable(
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
{
|
||||
solAssert(isTuple(m_type->category()), "");
|
||||
}
|
||||
|
||||
void SymbolicTupleVariable::setComponents(vector<shared_ptr<SymbolicVariable>> _components)
|
||||
{
|
||||
solAssert(m_components.empty(), "");
|
||||
auto const& tupleType = dynamic_cast<solidity::TupleType const*>(m_type);
|
||||
solAssert(_components.size() == tupleType->components().size(), "");
|
||||
m_components = move(_components);
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace smt
|
||||
{
|
||||
|
||||
class Type;
|
||||
|
||||
@ -36,23 +38,23 @@ class SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
SymbolicVariable(
|
||||
smt::SortPointer _sort,
|
||||
SortPointer _sort,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
|
||||
virtual ~SymbolicVariable() = default;
|
||||
|
||||
smt::Expression currentValue() const;
|
||||
Expression currentValue() const;
|
||||
std::string currentName() const;
|
||||
virtual smt::Expression valueAtIndex(int _index) const;
|
||||
virtual smt::Expression increaseIndex();
|
||||
virtual smt::Expression operator()(std::vector<smt::Expression> /*_arguments*/) const
|
||||
virtual Expression valueAtIndex(int _index) const;
|
||||
virtual Expression increaseIndex();
|
||||
virtual Expression operator()(std::vector<Expression> /*_arguments*/) const
|
||||
{
|
||||
solAssert(false, "Function application to non-function.");
|
||||
}
|
||||
@ -60,18 +62,18 @@ public:
|
||||
unsigned index() const { return m_ssa->index(); }
|
||||
unsigned& index() { return m_ssa->index(); }
|
||||
|
||||
TypePointer const& type() const { return m_type; }
|
||||
solidity::TypePointer const& type() const { return m_type; }
|
||||
|
||||
protected:
|
||||
std::string uniqueSymbol(unsigned _index) const;
|
||||
|
||||
/// SMT sort.
|
||||
smt::SortPointer m_sort;
|
||||
SortPointer m_sort;
|
||||
/// Solidity type, used for size and range in number types.
|
||||
TypePointer m_type;
|
||||
solidity::TypePointer m_type;
|
||||
std::string m_uniqueName;
|
||||
smt::SolverInterface& m_interface;
|
||||
std::shared_ptr<SSAVariable> m_ssa;
|
||||
SolverInterface& m_interface;
|
||||
std::unique_ptr<SSAVariable> m_ssa;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -81,9 +83,9 @@ class SymbolicBoolVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicBoolVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
@ -94,9 +96,9 @@ class SymbolicIntVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicIntVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
@ -108,7 +110,7 @@ class SymbolicAddressVariable: public SymbolicIntVariable
|
||||
public:
|
||||
SymbolicAddressVariable(
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
@ -121,7 +123,7 @@ public:
|
||||
SymbolicFixedBytesVariable(
|
||||
unsigned _numBytes,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
@ -132,20 +134,20 @@ class SymbolicFunctionVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicFunctionVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
|
||||
smt::Expression increaseIndex();
|
||||
smt::Expression operator()(std::vector<smt::Expression> _arguments) const;
|
||||
Expression increaseIndex();
|
||||
Expression operator()(std::vector<Expression> _arguments) const;
|
||||
|
||||
private:
|
||||
/// Creates a new function declaration.
|
||||
void resetDeclaration();
|
||||
|
||||
/// Stores the current function declaration.
|
||||
smt::Expression m_declaration;
|
||||
Expression m_declaration;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -155,9 +157,9 @@ class SymbolicMappingVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicMappingVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
@ -168,9 +170,9 @@ class SymbolicArrayVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicArrayVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
@ -181,11 +183,35 @@ class SymbolicEnumVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicEnumVariable(
|
||||
TypePointer _type,
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialization of SymbolicVariable for Tuple
|
||||
*/
|
||||
class SymbolicTupleVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicTupleVariable(
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
);
|
||||
|
||||
std::vector<std::shared_ptr<SymbolicVariable>> const& components()
|
||||
{
|
||||
return m_components;
|
||||
}
|
||||
|
||||
void setComponents(std::vector<std::shared_ptr<SymbolicVariable>> _components);
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<SymbolicVariable>> m_components;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,33 +24,53 @@
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
using namespace dev::solidity::smt;
|
||||
|
||||
set<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _node, vector<CallableDeclaration const*> const& _outerCallstack)
|
||||
{
|
||||
m_touchedVariables.clear();
|
||||
m_callStack.clear();
|
||||
m_callStack += _outerCallstack;
|
||||
m_lastCall = m_callStack.back();
|
||||
_node.accept(*this);
|
||||
return m_touchedVariables;
|
||||
}
|
||||
|
||||
void VariableUsage::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
|
||||
solAssert(declaration, "");
|
||||
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
if (_identifier.annotation().lValueRequested)
|
||||
m_touchedVariables.insert(varDecl);
|
||||
checkIdentifier(_identifier);
|
||||
}
|
||||
|
||||
void VariableUsage::endVisit(IndexAccess const& _indexAccess)
|
||||
{
|
||||
if (_indexAccess.annotation().lValueRequested)
|
||||
{
|
||||
/// identifier.annotation().lValueRequested == false, that's why we
|
||||
/// need to check that before.
|
||||
auto identifier = dynamic_cast<Identifier const*>(SMTChecker::leftmostBase(_indexAccess));
|
||||
if (identifier)
|
||||
checkIdentifier(*identifier);
|
||||
}
|
||||
}
|
||||
|
||||
void VariableUsage::endVisit(FunctionCall const& _funCall)
|
||||
{
|
||||
if (auto const& funDef = SMTChecker::inlinedFunctionCallToDefinition(_funCall))
|
||||
if (find(m_functionPath.begin(), m_functionPath.end(), funDef) == m_functionPath.end())
|
||||
if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end())
|
||||
funDef->accept(*this);
|
||||
}
|
||||
|
||||
bool VariableUsage::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
m_functionPath.push_back(&_function);
|
||||
m_callStack.push_back(&_function);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VariableUsage::endVisit(FunctionDefinition const&)
|
||||
{
|
||||
solAssert(!m_functionPath.empty(), "");
|
||||
m_functionPath.pop_back();
|
||||
solAssert(!m_callStack.empty(), "");
|
||||
m_callStack.pop_back();
|
||||
}
|
||||
|
||||
void VariableUsage::endVisit(ModifierInvocation const& _modifierInv)
|
||||
@ -62,18 +82,23 @@ void VariableUsage::endVisit(ModifierInvocation const& _modifierInv)
|
||||
|
||||
void VariableUsage::endVisit(PlaceholderStatement const&)
|
||||
{
|
||||
solAssert(!m_functionPath.empty(), "");
|
||||
FunctionDefinition const* function = m_functionPath.back();
|
||||
solAssert(function, "");
|
||||
if (function->isImplemented())
|
||||
function->body().accept(*this);
|
||||
solAssert(!m_callStack.empty(), "");
|
||||
FunctionDefinition const* funDef = nullptr;
|
||||
for (auto it = m_callStack.rbegin(); it != m_callStack.rend() && !funDef; ++it)
|
||||
funDef = dynamic_cast<FunctionDefinition const*>(*it);
|
||||
solAssert(funDef, "");
|
||||
if (funDef->isImplemented())
|
||||
funDef->body().accept(*this);
|
||||
}
|
||||
|
||||
set<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _node, vector<FunctionDefinition const*> const& _outerCallstack)
|
||||
void VariableUsage::checkIdentifier(Identifier const& _identifier)
|
||||
{
|
||||
m_touchedVariables.clear();
|
||||
m_functionPath.clear();
|
||||
m_functionPath += _outerCallstack;
|
||||
_node.accept(*this);
|
||||
return m_touchedVariables;
|
||||
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
|
||||
solAssert(declaration, "");
|
||||
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
{
|
||||
solAssert(m_lastCall, "");
|
||||
if (!varDecl->isLocalVariable() || varDecl->functionOrModifierDefinition() == m_lastCall)
|
||||
m_touchedVariables.insert(varDecl);
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace smt
|
||||
{
|
||||
|
||||
/**
|
||||
* This class computes information about which variables are modified in a certain subtree.
|
||||
@ -34,19 +36,25 @@ class VariableUsage: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
/// @param _outerCallstack the current callstack in the callers context.
|
||||
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<FunctionDefinition const*> const& _outerCallstack);
|
||||
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<CallableDeclaration const*> const& _outerCallstack);
|
||||
|
||||
private:
|
||||
void endVisit(Identifier const& _node) override;
|
||||
void endVisit(IndexAccess const& _node) override;
|
||||
void endVisit(FunctionCall const& _node) override;
|
||||
bool visit(FunctionDefinition const& _node) override;
|
||||
void endVisit(FunctionDefinition const& _node) override;
|
||||
void endVisit(ModifierInvocation const& _node) override;
|
||||
void endVisit(PlaceholderStatement const& _node) override;
|
||||
|
||||
/// Checks whether an identifier should be added to touchedVariables.
|
||||
void checkIdentifier(Identifier const& _identifier);
|
||||
|
||||
std::set<VariableDeclaration const*> m_touchedVariables;
|
||||
std::vector<FunctionDefinition const*> m_functionPath;
|
||||
std::vector<CallableDeclaration const*> m_callStack;
|
||||
CallableDeclaration const* m_lastCall = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include <libevmasm/Exceptions.h>
|
||||
|
||||
#include <libdevcore/SwarmHash.h>
|
||||
#include <libdevcore/IpfsHash.h>
|
||||
#include <libdevcore/JSON.h>
|
||||
|
||||
#include <json/json.h>
|
||||
@ -216,7 +217,7 @@ bool CompilerStack::parse()
|
||||
string const& path = sourcesToParse[i];
|
||||
Source& source = m_sources[path];
|
||||
source.scanner->reset();
|
||||
source.ast = Parser(m_errorReporter).parse(source.scanner);
|
||||
source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner);
|
||||
if (!source.ast)
|
||||
solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error.");
|
||||
else
|
||||
@ -249,7 +250,7 @@ bool CompilerStack::analyze()
|
||||
bool noErrors = true;
|
||||
|
||||
try {
|
||||
SyntaxChecker syntaxChecker(m_errorReporter);
|
||||
SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (!syntaxChecker.checkSyntax(*source->ast))
|
||||
noErrors = false;
|
||||
@ -260,7 +261,7 @@ bool CompilerStack::analyze()
|
||||
noErrors = false;
|
||||
|
||||
m_globalContext = make_shared<GlobalContext>();
|
||||
NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
|
||||
NameAndTypeResolver resolver(*m_globalContext, m_scopes, m_errorReporter);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (!resolver.registerDeclarations(*source->ast))
|
||||
return false;
|
||||
@ -278,11 +279,8 @@ bool CompilerStack::analyze()
|
||||
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
m_globalContext->setCurrentContract(*contract);
|
||||
if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false;
|
||||
if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
|
||||
if (!resolver.resolveNamesAndTypes(*contract)) return false;
|
||||
|
||||
if (!resolver.resolveNamesAndTypes(*contract)) return false;
|
||||
// Note that we now reference contracts by their fully qualified names, and
|
||||
// thus contracts can only conflict if declared in the same source file. This
|
||||
// already causes a double-declaration error elsewhere, so we do not report
|
||||
@ -397,7 +395,8 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con
|
||||
return
|
||||
m_requestedContractNames.empty() ||
|
||||
m_requestedContractNames.count(_contract.fullyQualifiedName()) ||
|
||||
m_requestedContractNames.count(_contract.name());
|
||||
m_requestedContractNames.count(_contract.name()) ||
|
||||
m_requestedContractNames.count(":" + _contract.name());
|
||||
}
|
||||
|
||||
bool CompilerStack::compile()
|
||||
@ -771,6 +770,13 @@ h256 const& CompilerStack::Source::swarmHash() const
|
||||
return swarmHashCached;
|
||||
}
|
||||
|
||||
string const& CompilerStack::Source::ipfsUrl() const
|
||||
{
|
||||
if (ipfsUrlCached.empty())
|
||||
if (scanner->source().size() < 1024 * 256)
|
||||
ipfsUrlCached = "dweb:/ipfs/" + dev::ipfsHashBase58(scanner->source());
|
||||
return ipfsUrlCached;
|
||||
}
|
||||
|
||||
StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath)
|
||||
{
|
||||
@ -1032,6 +1038,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
{
|
||||
meta["sources"][s.first]["urls"] = Json::arrayValue;
|
||||
meta["sources"][s.first]["urls"].append("bzzr://" + toHex(s.second.swarmHash().asBytes()));
|
||||
meta["sources"][s.first]["urls"].append(s.second.ipfsUrl());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1180,6 +1187,10 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
|
||||
encoder.pushBytes("bzzr0", dev::swarmHash(_metadata).asBytes());
|
||||
if (_experimentalMode)
|
||||
encoder.pushBool("experimental", true);
|
||||
if (m_release)
|
||||
encoder.pushBytes("solc", VersionCompactBytes);
|
||||
else
|
||||
encoder.pushString("solc", VersionStringStrict);
|
||||
return encoder.serialise();
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
@ -131,6 +132,14 @@ public:
|
||||
/// Must be set before parsing.
|
||||
void setOptimiserSettings(OptimiserSettings _settings);
|
||||
|
||||
/// Set whether or not parser error is desired.
|
||||
/// When called without an argument it will revert to the default.
|
||||
/// Must be set before parsing.
|
||||
void setParserErrorRecovery(bool _wantErrorRecovery = false)
|
||||
{
|
||||
m_parserErrorRecovery = _wantErrorRecovery;
|
||||
}
|
||||
|
||||
/// Set the EVM version used before running compile.
|
||||
/// When called without an argument it will revert to the default version.
|
||||
/// Must be set before parsing.
|
||||
@ -261,6 +270,8 @@ public:
|
||||
/// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
|
||||
Json::Value gasEstimates(std::string const& _contractName) const;
|
||||
|
||||
/// Overwrites the release/prerelease flag. Should only be used for testing.
|
||||
void overwriteReleaseFlag(bool release) { m_release = release; }
|
||||
private:
|
||||
/// The state per source unit. Filled gradually during parsing.
|
||||
struct Source
|
||||
@ -269,9 +280,11 @@ private:
|
||||
std::shared_ptr<SourceUnit> ast;
|
||||
h256 mutable keccak256HashCached;
|
||||
h256 mutable swarmHashCached;
|
||||
std::string mutable ipfsUrlCached;
|
||||
void reset() { *this = Source(); }
|
||||
h256 const& keccak256() const;
|
||||
h256 const& swarmHash() const;
|
||||
std::string const& ipfsUrl() const;
|
||||
};
|
||||
|
||||
/// The state per contract. Filled gradually during compilation.
|
||||
@ -333,7 +346,7 @@ private:
|
||||
std::string createMetadata(Contract const& _contract) const;
|
||||
|
||||
/// @returns the metadata CBOR for the given serialised metadata JSON.
|
||||
static bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
|
||||
bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
|
||||
|
||||
/// @returns the computer source mapping string.
|
||||
std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
|
||||
@ -381,7 +394,9 @@ private:
|
||||
langutil::ErrorList m_errorList;
|
||||
langutil::ErrorReporter m_errorReporter;
|
||||
bool m_metadataLiteralSources = false;
|
||||
bool m_parserErrorRecovery = false;
|
||||
State m_stackState = Empty;
|
||||
bool m_release = VersionIsRelease;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -437,7 +437,7 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
|
||||
return *error;
|
||||
}
|
||||
}
|
||||
return std::move(settings);
|
||||
return { std::move(settings) };
|
||||
}
|
||||
|
||||
}
|
||||
@ -663,7 +663,7 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
|
||||
|
||||
ret.outputSelection = std::move(outputSelection);
|
||||
|
||||
return std::move(ret);
|
||||
return { std::move(ret) };
|
||||
}
|
||||
|
||||
Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings)
|
||||
@ -965,6 +965,8 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
|
||||
|
||||
Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
|
||||
{
|
||||
YulStringRepository::reset();
|
||||
|
||||
try
|
||||
{
|
||||
auto parsed = parseInput(_input);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user