Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth 2020-04-07 11:14:28 +02:00
commit 51883958ab
288 changed files with 6927 additions and 2129 deletions

View File

@ -122,8 +122,8 @@ defaults:
name: command line tests
command: ./test/cmdlineTests.sh
- run_docs_version_pragma_check: &run_docs_version_pragma_check
name: docs version pragma check
- run_docs_pragma_min_version: &run_docs_pragma_min_version
name: docs pragma version check
command: ./scripts/docs_version_pragma_check.sh
- test_ubuntu1604_clang: &test_ubuntu1604_clang
@ -351,6 +351,15 @@ jobs:
pip install --user z3-solver
- run: *run_proofs
chk_docs_pragma_min_version:
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >>
environment:
TERM: xterm
steps:
- checkout
- run: *run_docs_pragma_min_version
b_ubu_clang: &build_ubuntu1904_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.ubuntu-1904-clang-docker-image-rev >>
@ -621,7 +630,6 @@ jobs:
- attach_workspace:
at: build
- run: *run_cmdline_tests
- run: *run_docs_version_pragma_check
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
@ -786,6 +794,7 @@ workflows:
- chk_proofs: *workflow_trigger_on_tags
- chk_pylint: *workflow_trigger_on_tags
- chk_antlr_grammar: *workflow_trigger_on_tags
- chk_docs_pragma_min_version: *workflow_trigger_on_tags
# build-only
- b_docs: *workflow_trigger_on_tags

View File

@ -11,6 +11,8 @@ eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.7.0")
# OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
include(TestBigEndian)

View File

@ -12,7 +12,7 @@ Compiler Features:
Bugfixes:
### 0.6.5 (unreleased)
### 0.6.6 (unreleased)
Language Features:
@ -21,9 +21,33 @@ Compiler Features:
Bugfixes:
* Inline Assembly: Fix internal error when accessing incorrect constant variables.
* SMTChecker: Fix internal error in the CHC engine when calling inherited functions internally.
### 0.6.5 (2020-04-06)
Important Bugfixes:
* Code Generator: Restrict the length of dynamic memory arrays to 64 bits during creation at runtime fixing a possible overflow.
Language Features:
* Allow local storage variables to be declared without initialization, as long as they are assigned before they are accessed.
* State variables can be marked ``immutable`` which causes them to be read-only, but assignable in the constructor. The value will be stored directly in the code.
Compiler Features:
* Commandline Interface: Enable output of storage layout with `--storage-layout`.
* Metadata: Added support for IPFS hashes of large files that need to be split in multiple chunks.
Bugfixes:
* Inheritance: Allow public state variables to override functions with dynamic memory types in their return values.
* Inline Assembly: Fix internal error when accessing invalid constant variables.
* Inline Assembly: Fix internal error when accessing functions.
* JSON AST: Always add pointer suffix for memory reference types.
* Reference Resolver: Fix internal error when accessing invalid struct members.
* Type Checker: Fix internal errors when assigning nested tuples.
### 0.6.4 (2020-03-10)

View File

@ -12,6 +12,7 @@ Solidity is a statically typed, contract-oriented, high-level language for imple
- [Development](#development)
- [Maintainers](#maintainers)
- [License](#license)
- [Security](#security)
## Background
@ -75,3 +76,7 @@ releases [in the projects section](https://github.com/ethereum/solidity/projects
Solidity is licensed under [GNU General Public License v3.0](LICENSE.txt).
Some third-party code has its [own licensing terms](cmake/templates/license.h.in).
## Security
The security policy may be [found here](SECURITY.md).

52
SECURITY.md Normal file
View File

@ -0,0 +1,52 @@
# Security Policy
The Solidity team and community take all security bugs in Solidity seriously.
We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
## Scope
Bugs in the Solidity repository are in scope.
Bugs in third-party dependencies e.g., jsoncpp, boost etc. are not in scope unless they result in a Solidity specific bug.
Only bugs that have a demonstrable security impact on smart contracts are in scope.
For example, a Solidity program whose optimization is incorrect (e.g., leads to an incorrect output) qualifies as a security bug.
Please note that the [rules][2] of the [Ethereum bounty program][1] have precedence over this security policy.
## Supported Versions
As a general rule, only the latest release gets security updates.
Exceptions may be made when the current breaking release is relatively new, e.g. less than three months old.
If you are reporting a bug, please state clearly the Solidity version(s) it affects.
Example 1: Assuming the current release is `0.6.3` and a security bug has been found in it that affects both `0.5.x` and `0.6.x` trees, we may not only patch `0.6.3` (the bug-fix release numbered `0.6.4`) but `0.5.x` as well (the bug-fix release numbered `0.5.(x+1)`).
Example 2: Assuming the current release is `0.6.25` and a security bug has been found in it, we may only patch `0.6.25` (in the bug-fix release numbered `0.6.26`) even if the bug affects a previous tree such as `0.5.x`.
## Reporting a Vulnerability
To report a vulnerability, please follow the instructions stated in the [Ethereum bounty program][1].
In the bug report, please include all details necessary to reproduce the vulnerability such as:
- Input program that triggers the bug
- Compiler version affected
- Target EVM version
- Framework/IDE if applicable
- EVM execution environment/client if applicable
- Operating system
Please include steps to reproduce the bug you have found in as much detail as possible.
Once we have received your bug report, we will try to reproduce it and provide a more detailed response.
Once the reported bug has been successfully reproduced, the Solidity team will work on a fix.
The Solidity team maintains the following JSON-formatted lists of patched security vulnerabilities:
- [Summary of known security vulnerabilities][3]
- [List of security vulnerabilities affecting a specific version of the compiler][4].
[1]: https://bounty.ethereum.org/
[2]: https://bounty.ethereum.org/#rules
[3]: https://solidity.readthedocs.io/en/develop/bugs.html
[4]: https://github.com/ethereum/solidity/blob/develop/docs/bugs_by_version.json

View File

@ -26,6 +26,17 @@ eth_add_cxx_compiler_flag_if_supported(-Wimplicit-fallthrough)
# Prevent the path of the source directory from ending up in the binary via __FILE__ macros.
eth_add_cxx_compiler_flag_if_supported("-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=/solidity")
# -Wpessimizing-move warns when a call to std::move would prevent copy elision
# if the argument was not wrapped in a call. This happens when moving a local
# variable in a return statement when the variable is the same type as the
# return type or using a move to create a new object from a temporary object.
eth_add_cxx_compiler_flag_if_supported(-Wpessimizing-move)
# -Wredundant-move warns when an implicit move would already be made, so the
# std::move call is not needed, such as when moving a local variable in a return
# that is different from the return type.
eth_add_cxx_compiler_flag_if_supported(-Wredundant-move)
if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))
# Enables all the warnings about constructions that some users consider questionable,
# and that are easy to avoid. Also enable some extra warning flags that are not

View File

@ -1,4 +1,12 @@
[
{
"name": "MemoryArrayCreationOverflow",
"summary": "The creation of very large memory arrays can result in overlapping memory regions and thus memory corruption.",
"description": "No runtime overflow checks were performed for the length of memory arrays during creation. In cases for which the memory size of an array in bytes, i.e. the array length times 32, is larger than 2^256-1, the memory allocation will overflow, potentially resulting in overlapping memory areas. The length of the array is still stored correctly, so copying or iterating over such an array will result in out-of-gas.",
"introduced": "0.2.0",
"fixed": "0.6.5",
"severity": "low"
},
{
"name": "YulOptimizerRedundantAssignmentBreakContinue",
"summary": "The Yul optimizer can remove essential assignments to variables declared inside for loops when Yul's continue or break statement is used. You are unlikely to be affected if you do not use inline assembly with for loops and continue and break statements.",

View File

@ -151,6 +151,7 @@
},
"0.2.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector",
@ -171,6 +172,7 @@
},
"0.2.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector",
@ -191,6 +193,7 @@
},
"0.2.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector",
@ -211,6 +214,7 @@
},
"0.3.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -233,6 +237,7 @@
},
"0.3.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -254,6 +259,7 @@
},
"0.3.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -275,6 +281,7 @@
},
"0.3.3": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -295,6 +302,7 @@
},
"0.3.4": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -315,6 +323,7 @@
},
"0.3.5": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -335,6 +344,7 @@
},
"0.3.6": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -353,6 +363,7 @@
},
"0.4.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -371,6 +382,7 @@
},
"0.4.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -389,6 +401,7 @@
},
"0.4.10": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -405,6 +418,7 @@
},
"0.4.11": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -420,6 +434,7 @@
},
"0.4.12": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -434,6 +449,7 @@
},
"0.4.13": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -448,6 +464,7 @@
},
"0.4.14": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -461,6 +478,7 @@
},
"0.4.15": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -473,6 +491,7 @@
},
"0.4.16": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -487,6 +506,7 @@
},
"0.4.17": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -502,6 +522,7 @@
},
"0.4.18": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -516,6 +537,7 @@
},
"0.4.19": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -531,6 +553,7 @@
},
"0.4.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -548,6 +571,7 @@
},
"0.4.20": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -563,6 +587,7 @@
},
"0.4.21": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -578,6 +603,7 @@
},
"0.4.22": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -593,6 +619,7 @@
},
"0.4.23": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -607,6 +634,7 @@
},
"0.4.24": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -621,6 +649,7 @@
},
"0.4.25": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -633,6 +662,7 @@
},
"0.4.26": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -642,6 +672,7 @@
},
"0.4.3": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -658,6 +689,7 @@
},
"0.4.4": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -673,6 +705,7 @@
},
"0.4.5": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -690,6 +723,7 @@
},
"0.4.6": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -706,6 +740,7 @@
},
"0.4.7": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -722,6 +757,7 @@
},
"0.4.8": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -738,6 +774,7 @@
},
"0.4.9": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
@ -754,6 +791,7 @@
},
"0.5.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -766,6 +804,7 @@
},
"0.5.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -778,6 +817,7 @@
},
"0.5.10": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers"
@ -786,6 +826,7 @@
},
"0.5.11": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
@ -793,6 +834,7 @@
},
"0.5.12": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
@ -800,6 +842,7 @@
},
"0.5.13": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
@ -807,6 +850,7 @@
},
"0.5.14": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2LoopYulOptimizer"
@ -815,6 +859,7 @@
},
"0.5.15": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
@ -822,16 +867,20 @@
},
"0.5.16": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden"
],
"released": "2020-01-02"
},
"0.5.17": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-03-17"
},
"0.5.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -844,6 +893,7 @@
},
"0.5.3": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -856,6 +906,7 @@
},
"0.5.4": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -868,6 +919,7 @@
},
"0.5.5": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -882,6 +934,7 @@
},
"0.5.6": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
"SignedArrayStorageCopy",
@ -896,6 +949,7 @@
},
"0.5.7": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
"SignedArrayStorageCopy",
@ -908,6 +962,7 @@
},
"0.5.8": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
@ -919,6 +974,7 @@
},
"0.5.9": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
@ -929,24 +985,37 @@
},
"0.6.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"YulOptimizerRedundantAssignmentBreakContinue"
],
"released": "2019-12-17"
},
"0.6.1": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-01-02"
},
"0.6.2": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-01-27"
},
"0.6.3": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-02-18"
},
"0.6.4": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-03-10"
},
"0.6.5": {
"bugs": [],
"released": "2020-04-06"
}
}

View File

@ -1,11 +1,49 @@
.. index:: ! constant
************************
Constant State Variables
************************
**************************************
Constant and Immutable State Variables
**************************************
State variables can be declared as ``constant``. In this case, they have to be
assigned from an expression which is a constant at compile time. Any expression
State variables can be declared as ``constant`` or ``immutable``.
In both cases, the variables cannot be modified after the contract has been constructed.
For ``constant`` variables, the value has to be fixed at compile-time, while
for ``immutable``, it can still be assigned at construction time.
The compiler does not reserve a storage slot for these variables, and every occurrence is
replaced by the respective value.
Not all types for constants and immutables are implemented at this time. The only supported types are
`strings <strings>`_ (only for constants) and `value types <value-types>`_.
::
pragma solidity >0.6.4 <0.7.0;
contract C {
uint constant X = 32**22 + 8;
string constant TEXT = "abc";
bytes32 constant MY_HASH = keccak256("abc");
uint immutable decimals;
uint immutable maxBalance;
address immutable owner = msg.sender;
constructor(uint _decimals, address _reference) public {
decimals = _decimals;
// Assignments to immutables can even access the environment.
maxBalance = _reference.balance;
}
function isBalanceTooHigh(address _other) public view returns (bool) {
return _other.balance > maxBalance;
}
}
Constant
========
For ``constant`` variables, the value has to be a constant at compile time and it has to be
assigned where the variable is declared. Any expression
that accesses storage, blockchain data (e.g. ``now``, ``address(this).balance`` or
``block.number``) or
execution data (``msg.value`` or ``gasleft()``) or makes calls to external contracts is disallowed. Expressions
@ -18,18 +56,17 @@ The reason behind allowing side-effects on the memory allocator is that it
should be possible to construct complex objects like e.g. lookup-tables.
This feature is not yet fully usable.
The compiler does not reserve a storage slot for these variables, and every occurrence is
replaced by the respective constant expression (which might be computed to a single value by the optimizer).
Immutable
=========
Not all types for constants are implemented at this time. The only supported types are
value types and strings.
Variables declared as ``immutable`` are a bit less restricted than those
declared as ``constant``: Immutable variables can be assigned an arbitrary
value in the constructor of the contract or at the point of their declaration.
They cannot be read during construction time and can only be assigned once.
::
pragma solidity >=0.4.0 <0.8.0;
contract C {
uint constant X = 32**22 + 8;
string constant TEXT = "abc";
bytes32 constant MY_HASH = keccak256("abc");
}
The contract creation code generated by the compiler will modify the
contract's runtime code before it is returned by replacing all references
to immutables by the values assigned to the them. This is important if
you are comparing the
runtime code generated by the compiler with the one actually stored in the
blockchain.

View File

@ -184,7 +184,7 @@ invalid bids.
::
pragma solidity >0.4.23 <0.8.0;
pragma solidity >=0.5.0 <0.8.0;
contract BlindAuction {
struct Bid {

View File

@ -1,193 +0,0 @@
SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)*
// Pragma actually parses anything up to the trailing ';' to be fully forward-compatible.
PragmaDirective = 'pragma' Identifier ([^;]+) ';'
ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = 'abstract'? ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
ContractPart = StateVariableDeclaration | UsingForDeclaration
| StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' | OverrideSpecifier )* Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
ModifierDefinition = 'modifier' Identifier ParameterList? ( 'virtual' | OverrideSpecifier )* Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = FunctionDescriptor ParameterList
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' | 'virtual' | OverrideSpecifier )*
( 'returns' ParameterList )? ( ';' | Block )
FunctionDescriptor = 'function' Identifier | 'constructor' | 'fallback' | 'receive'
OverrideSpecifier = 'override' ( '(' UserDefinedTypeName (',' UserDefinedTypeName)* ')' )?
EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';'
EnumValue = Identifier
EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}'
ParameterList = '(' ( Parameter (',' Parameter)* )? ')'
Parameter = TypeName StorageLocation? Identifier?
EventParameterList = '(' ( EventParameter (',' EventParameter )* )? ')'
EventParameter = TypeName 'indexed'? Identifier?
FunctionTypeParameterList = '(' ( FunctionTypeParameter (',' FunctionTypeParameter )* )? ')'
FunctionTypeParameter = TypeName StorageLocation?
// semantic restriction: mappings and structs (recursively) containing mappings
// are not allowed in argument lists
VariableDeclaration = TypeName StorageLocation? Identifier
TypeName = ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
| ( 'address' 'payable' )
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ( ElementaryTypeName | UserDefinedTypeName ) '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
( 'returns' FunctionTypeParameterList )?
StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+
CatchClause = 'catch' ( Identifier? ParameterList )? Block
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
InlineAssemblyStatement = 'assembly' StringLiteral? AssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
EmitStatement = 'emit' FunctionCall
VariableDefinition = (VariableDeclaration | '(' VariableDeclaration? (',' VariableDeclaration? )* ')' ) ( '=' Expression )?
// Precedence by order (see github.com/ethereum/solidity/pull/732)
Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| IndexRangeAccess
| MemberAccess
| FunctionCall
| Expression '{' NameValueList '}'
| '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression
| Expression ('*' | '/' | '%') Expression
| Expression ('+' | '-') Expression
| Expression ('<<' | '>>') Expression
| Expression '&' Expression
| Expression '^' Expression
| Expression '|' Expression
| Expression ('<' | '>' | '<=' | '>=') Expression
| Expression ('==' | '!=') Expression
| Expression '&&' Expression
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
| PrimaryExpression
PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
| TupleExpression
| Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
IndexRangeAccess = Expression '[' Expression? ':' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+ ( '.' [0-9]* )? ( [eE] [0-9]+ )?
TupleExpression = '(' ( Expression? ( ',' Expression? )* )? ')'
| '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | Int | Uint | Byte | Fixed | Ufixed
Int = 'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' | 'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' | 'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' | 'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256'
Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' | 'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' | 'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' | 'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256'
Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'
Fixed = 'fixed' | ( 'fixed' [0-9]+ 'x' [0-9]+ )
Ufixed = 'ufixed' | ( 'ufixed' [0-9]+ 'x' [0-9]+ )
AssemblyBlock = '{' AssemblyStatement* '}'
AssemblyStatement = AssemblyBlock
| AssemblyFunctionDefinition
| AssemblyVariableDeclaration
| AssemblyAssignment
| AssemblyIf
| AssemblyExpression
| AssemblySwitch
| AssemblyForLoop
| AssemblyBreakContinue
| AssemblyLeave
AssemblyFunctionDefinition =
'function' Identifier '(' AssemblyIdentifierList? ')'
( '->' AssemblyIdentifierList )? AssemblyBlock
AssemblyVariableDeclaration = 'let' AssemblyIdentifierList ( ':=' AssemblyExpression )?
AssemblyAssignment = AssemblyIdentifierList ':=' AssemblyExpression
AssemblyExpression = AssemblyFunctionCall | Identifier | Literal
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression ( AssemblyCase+ AssemblyDefault? | AssemblyDefault )
AssemblyCase = 'case' Literal AssemblyBlock
AssemblyDefault = 'default' AssemblyBlock
AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock
AssemblyBreakContinue = 'break' | 'continue'
AssemblyLeave = 'leave'
AssemblyFunctionCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
AssemblyIdentifierList = Identifier ( ',' Identifier )*

View File

@ -788,6 +788,7 @@ Modifiers
- ``view`` for functions: Disallows modification of state.
- ``payable`` for functions: Allows them to receive Ether together with a call.
- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code.
- ``anonymous`` for events: Does not store event signature as topic.
- ``indexed`` for event parameters: Stores the parameter as topic.
- ``virtual`` for functions and modifiers: Allows the function's or modifier's
@ -809,5 +810,5 @@ These keywords are reserved in Solidity. They might become part of the syntax in
Language Grammar
================
.. literalinclude:: grammar.txt
:language: none
.. literalinclude:: Solidity.g4
:language: antlr

View File

@ -124,6 +124,12 @@ Accessing an array past its end causes a failing assertion. Methods ``.push()``
to append a new element at the end of the array, where ``.push()`` appends a zero-initialized element and returns
a reference to it.
.. index:: ! string, ! bytes
.. _strings:
.. _bytes:
``bytes`` and ``strings`` as Arrays
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -381,7 +387,7 @@ If ``start`` is greater than ``end`` or if ``end`` is greater
than the length of the array, an exception is thrown.
Both ``start`` and ``end`` are optional: ``start`` defaults
to ``0`` and ``end`` defaults to the length of the array.
to ``0`` and ``end`` defaults to the length of the array.
Array slices do not have any members. They are implicitly
convertible to arrays of their underlying type
@ -494,7 +500,7 @@ shown in the following example:
The contract does not provide the full functionality of a crowdfunding
contract, but it contains the basic concepts necessary to understand structs.
Struct types can be used inside mappings and arrays and they can itself
Struct types can be used inside mappings and arrays and they can themselves
contain mappings and arrays.
It is not possible for a struct to contain a member of its own type,

View File

@ -302,7 +302,8 @@ Input Description
// evm.bytecode.opcodes - Opcodes list
// evm.bytecode.sourceMap - Source mapping (useful for debugging)
// evm.bytecode.linkReferences - Link references (if unlinked object)
// evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode)
// evm.deployedBytecode* - Deployed bytecode (has all the options that evm.bytecode has)
// evm.deployedBytecode.immutableReferences - Map from AST ids to bytecode ranges that reference immutables
// evm.methodIdentifiers - The list of function hashes
// evm.gasEstimates - Function gas estimates
// ewasm.wast - eWASM S-expressions format (not supported at the moment)
@ -424,8 +425,14 @@ Output Description
}
}
},
// The same layout as above.
"deployedBytecode": { },
"deployedBytecode": {
..., // The same layout as above.
"immutableReferences": [
// There are two references to the immutable with AST ID 3, both 32 bytes long. One is
// at bytecode offset 42, the other at bytecode offset 80.
"3": [{ "start": 42, "length": 32 }, { "start": 80, "length": 32 }]
]
},
// The list of function hashes
"methodIdentifiers": {
"delegate(address)": "5c19a95c"
@ -605,8 +612,8 @@ Assume you have the following contracts you want to update declared in ``Source.
.. code-block:: none
// This will not compile
pragma solidity >0.4.23;
// This will not compile after 0.5.0
pragma solidity >0.4.23 <0.5.0;
contract Updateable {
function run() public view returns (bool);

View File

@ -283,6 +283,24 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end)
);
break;
case PushImmutable:
collection.append(createJsonValue(
"PUSHIMMUTABLE",
sourceIndex,
i.location().start,
i.location().end,
m_immutables.at(h256(i.data()))
));
break;
case AssignImmutable:
collection.append(createJsonValue(
"ASSIGNIMMUTABLE",
sourceIndex,
i.location().start,
i.location().end,
m_immutables.at(h256(i.data()))
));
break;
case Tag:
collection.append(
createJsonValue("tag", sourceIndex, i.location().start, i.location().end, toString(i.data())));
@ -333,6 +351,20 @@ AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier)
return AssemblyItem{PushLibraryAddress, h};
}
AssemblyItem Assembly::newPushImmutable(string const& _identifier)
{
h256 h(util::keccak256(_identifier));
m_immutables[h] = _identifier;
return AssemblyItem{PushImmutable, h};
}
AssemblyItem Assembly::newImmutableAssignment(string const& _identifier)
{
h256 h(util::keccak256(_identifier));
m_immutables[h] = _identifier;
return AssemblyItem{AssignImmutable, h};
}
Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs)
{
OptimiserSettings settings;
@ -495,16 +527,44 @@ LinkerObject const& Assembly::assemble() const
// Otherwise ensure the object is actually clear.
assertThrow(m_assembledObject.linkReferences.empty(), AssemblyException, "Unexpected link references.");
LinkerObject& ret = m_assembledObject;
size_t subTagSize = 1;
map<u256, pair<string, vector<size_t>>> immutableReferencesBySub;
for (auto const& sub: m_subs)
{
sub->assemble();
auto const& linkerObject = sub->assemble();
if (!linkerObject.immutableReferences.empty())
{
assertThrow(
immutableReferencesBySub.empty(),
AssemblyException,
"More than one sub-assembly references immutables."
);
immutableReferencesBySub = linkerObject.immutableReferences;
}
for (size_t tagPos: sub->m_tagPositionsInBytecode)
if (tagPos != size_t(-1) && tagPos > subTagSize)
subTagSize = tagPos;
}
LinkerObject& ret = m_assembledObject;
bool setsImmutables = false;
bool pushesImmutables = false;
for (auto const& i: m_items)
if (i.type() == AssignImmutable)
{
i.setImmutableOccurrences(immutableReferencesBySub[i.data()].second.size());
setsImmutables = true;
}
else if (i.type() == PushImmutable)
pushesImmutables = true;
if (setsImmutables || pushesImmutables)
assertThrow(
setsImmutables != pushesImmutables,
AssemblyException,
"Cannot push and assign immutables in the same assembly subroutine."
);
size_t bytesRequiredForCode = bytesRequired(subTagSize);
m_tagPositionsInBytecode = vector<size_t>(m_usedTags, -1);
@ -598,6 +658,25 @@ LinkerObject const& Assembly::assemble() const
ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data());
ret.bytecode.resize(ret.bytecode.size() + 20);
break;
case PushImmutable:
ret.bytecode.push_back(uint8_t(Instruction::PUSH32));
ret.immutableReferences[i.data()].first = m_immutables.at(i.data());
ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
ret.bytecode.resize(ret.bytecode.size() + 32);
break;
case AssignImmutable:
for (auto const& offset: immutableReferencesBySub[i.data()].second)
{
ret.bytecode.push_back(uint8_t(Instruction::DUP1));
// TODO: should we make use of the constant optimizer methods for pushing the offsets?
bytes offsetBytes = toCompactBigEndian(u256(offset));
ret.bytecode.push_back(uint8_t(Instruction::PUSH1) - 1 + offsetBytes.size());
ret.bytecode += offsetBytes;
ret.bytecode.push_back(uint8_t(Instruction::MSTORE));
}
immutableReferencesBySub.erase(i.data());
ret.bytecode.push_back(uint8_t(Instruction::POP));
break;
case PushDeployTimeAddress:
ret.bytecode.push_back(uint8_t(Instruction::PUSH20));
ret.bytecode.resize(ret.bytecode.size() + 20);
@ -615,6 +694,13 @@ LinkerObject const& Assembly::assemble() const
}
}
assertThrow(
immutableReferencesBySub.empty(),
AssemblyException,
"Some immutables were read from but never assigned."
);
if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty())
// Append an INVALID here to help tests find miscompilation.
ret.bytecode.push_back(uint8_t(Instruction::INVALID));

View File

@ -54,6 +54,8 @@ public:
Assembly& sub(size_t _sub) { return *m_subs.at(_sub); }
AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); }
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(std::string const& _identifier);
AssemblyItem const& append(AssemblyItem const& _i);
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
@ -64,6 +66,8 @@ public:
/// after compilation and CODESIZE is not an option.
void appendProgramSize() { append(AssemblyItem(PushProgramSize)); }
void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); }
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
@ -166,6 +170,7 @@ protected:
std::vector<std::shared_ptr<Assembly>> m_subs;
std::map<util::h256, std::string> m_strings;
std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables.
mutable LinkerObject m_assembledObject;
mutable std::vector<size_t> m_tagPositionsInBytecode;

View File

@ -80,6 +80,13 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
case PushLibraryAddress:
case PushDeployTimeAddress:
return 1 + 20;
case PushImmutable:
return 1 + 32;
case AssignImmutable:
if (m_immutableOccurrences)
return 1 + (3 + 32) * *m_immutableOccurrences;
else
return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
default:
break;
}
@ -90,6 +97,8 @@ int AssemblyItem::arguments() const
{
if (type() == Operation)
return instructionInfo(instruction()).args;
else if (type() == AssignImmutable)
return 1;
else
return 0;
}
@ -108,6 +117,7 @@ int AssemblyItem::returnValues() const
case PushSubSize:
case PushProgramSize:
case PushLibraryAddress:
case PushImmutable:
case PushDeployTimeAddress:
return 1;
case Tag:
@ -135,6 +145,7 @@ bool AssemblyItem::canBeFunctional() const
case PushProgramSize:
case PushLibraryAddress:
case PushDeployTimeAddress:
case PushImmutable:
return true;
case Tag:
return false;
@ -210,6 +221,12 @@ string AssemblyItem::toAssemblyText() const
case PushDeployTimeAddress:
text = string("deployTimeAddress()");
break;
case PushImmutable:
text = string("immutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
break;
case AssignImmutable:
text = string("assignImmutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
break;
case UndefinedItem:
assertThrow(false, AssemblyException, "Invalid assembly item.");
break;
@ -275,6 +292,12 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
case PushDeployTimeAddress:
_out << " PushDeployTimeAddress";
break;
case PushImmutable:
_out << " PushImmutable";
break;
case AssignImmutable:
_out << " AssignImmutable";
break;
case UndefinedItem:
_out << " ???";
break;

View File

@ -44,7 +44,9 @@ enum AssemblyItemType {
Tag,
PushData,
PushLibraryAddress, ///< Push a currently unknown address of another (library) contract.
PushDeployTimeAddress ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
AssignImmutable ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
};
class Assembly;
@ -153,6 +155,8 @@ public:
size_t m_modifierDepth = 0;
void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = std::make_shared<size_t>(_n); }
private:
AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation
@ -162,6 +166,8 @@ private:
/// Pushed value for operations with data to be determined during assembly stage,
/// e.g. PushSubSize, PushTag, PushSub, etc.
mutable std::shared_ptr<u256> m_pushedValue;
/// Number of PushImmutable's with the same hash. Only used for AssignImmutable.
mutable std::shared_ptr<size_t> m_immutableOccurrences;
};
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)

View File

@ -91,6 +91,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
{
// can be ignored
}
else if (_item.type() == AssignImmutable)
// Since AssignImmutable breaks blocks, it should be fine to only consider its changes to the stack, which
// is the same as POP.
return feedItem(AssemblyItem(Instruction::POP), _copyItem);
else if (_item.type() != Operation)
{
assertThrow(_item.deposit() == 1, InvalidDeposit, "");

View File

@ -40,6 +40,10 @@ struct LinkerObject
/// need to be replaced by the actual addresses by the linker.
std::map<size_t, std::string> linkReferences;
/// Map from hashes of the identifiers of immutable variables to the full identifier of the immutable and
/// to a list of offsets into the bytecode that refer to their values.
std::map<u256, std::pair<std::string, std::vector<size_t>>> immutableReferences;
/// Appends the bytecode of @a _other and incorporates its link references.
void append(LinkerObject const& _other);

View File

@ -36,6 +36,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
case UndefinedItem:
case Tag:
case PushDeployTimeAddress:
case AssignImmutable:
return true;
case Push:
case PushString:
@ -45,6 +46,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
case PushProgramSize:
case PushData:
case PushLibraryAddress:
case PushImmutable:
return false;
case Operation:
{

View File

@ -14,6 +14,8 @@ set(sources
analysis/DeclarationContainer.h
analysis/DocStringAnalyser.cpp
analysis/DocStringAnalyser.h
analysis/ImmutableValidator.cpp
analysis/ImmutableValidator.h
analysis/GlobalContext.cpp
analysis/GlobalContext.h
analysis/NameAndTypeResolver.cpp
@ -100,8 +102,12 @@ set(sources
formal/SMTPortfolio.cpp
formal/SMTPortfolio.h
formal/SolverInterface.h
formal/Sorts.cpp
formal/Sorts.h
formal/SSAVariable.cpp
formal/SSAVariable.h
formal/SymbolicState.cpp
formal/SymbolicState.h
formal/SymbolicTypes.cpp
formal/SymbolicTypes.h
formal/SymbolicVariables.cpp

View File

@ -54,11 +54,11 @@ public:
TypePointer evaluate(Expression const& _expr);
private:
virtual void endVisit(BinaryOperation const& _operation);
virtual void endVisit(UnaryOperation const& _operation);
virtual void endVisit(Literal const& _literal);
virtual void endVisit(Identifier const& _identifier);
virtual void endVisit(TupleExpression const& _tuple);
void endVisit(BinaryOperation const& _operation) override;
void endVisit(UnaryOperation const& _operation) override;
void endVisit(Literal const& _literal) override;
void endVisit(Identifier const& _identifier) override;
void endVisit(TupleExpression const& _tuple) override;
void setType(ASTNode const& _node, TypePointer const& _type);
TypePointer type(ASTNode const& _node);

View File

@ -0,0 +1,220 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/analysis/ImmutableValidator.h>
#include <libsolutil/CommonData.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace solidity::frontend;
void ImmutableValidator::analyze()
{
m_inConstructionContext = true;
auto linearizedContracts = m_currentContract.annotation().linearizedBaseContracts | boost::adaptors::reversed;
for (ContractDefinition const* contract: linearizedContracts)
for (VariableDeclaration const* stateVar: contract->stateVariables())
if (stateVar->value())
{
stateVar->value()->accept(*this);
solAssert(m_initializedStateVariables.emplace(stateVar).second, "");
}
for (ContractDefinition const* contract: linearizedContracts)
if (contract->constructor())
visitCallableIfNew(*contract->constructor());
for (ContractDefinition const* contract: linearizedContracts)
for (std::shared_ptr<InheritanceSpecifier> const inheritSpec: contract->baseContracts())
if (auto args = inheritSpec->arguments())
ASTNode::listAccept(*args, *this);
m_inConstructionContext = false;
for (ContractDefinition const* contract: linearizedContracts)
{
for (auto funcDef: contract->definedFunctions())
visitCallableIfNew(*funcDef);
for (auto modDef: contract->functionModifiers())
visitCallableIfNew(*modDef);
}
checkAllVariablesInitialized(m_currentContract.location());
}
bool ImmutableValidator::visit(FunctionDefinition const& _functionDefinition)
{
return analyseCallable(_functionDefinition);
}
bool ImmutableValidator::visit(ModifierDefinition const& _modifierDefinition)
{
return analyseCallable(_modifierDefinition);
}
bool ImmutableValidator::visit(MemberAccess const& _memberAccess)
{
_memberAccess.expression().accept(*this);
if (auto contractType = dynamic_cast<ContractType const*>(_memberAccess.expression().annotation().type))
if (!contractType->isSuper())
// external access, no analysis needed.
return false;
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
analyseVariableReference(*varDecl, _memberAccess);
else if (auto funcType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
if (funcType->kind() == FunctionType::Kind::Internal && funcType->hasDeclaration())
visitCallableIfNew(funcType->declaration());
return false;
}
bool ImmutableValidator::visit(IfStatement const& _ifStatement)
{
bool prevInBranch = m_inBranch;
_ifStatement.condition().accept(*this);
m_inBranch = true;
_ifStatement.trueStatement().accept(*this);
if (auto falseStatement = _ifStatement.falseStatement())
falseStatement->accept(*this);
m_inBranch = prevInBranch;
return false;
}
bool ImmutableValidator::visit(WhileStatement const& _whileStatement)
{
bool prevInLoop = m_inLoop;
m_inLoop = true;
_whileStatement.condition().accept(*this);
_whileStatement.body().accept(*this);
m_inLoop = prevInLoop;
return false;
}
void ImmutableValidator::endVisit(Identifier const& _identifier)
{
if (auto const callableDef = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration))
visitCallableIfNew(callableDef->resolveVirtual(m_currentContract));
if (auto const varDecl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
analyseVariableReference(*varDecl, _identifier);
}
void ImmutableValidator::endVisit(Return const& _return)
{
if (m_currentConstructor != nullptr)
checkAllVariablesInitialized(_return.location());
}
bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDeclaration)
{
FunctionDefinition const* prevConstructor = m_currentConstructor;
m_currentConstructor = nullptr;
if (FunctionDefinition const* funcDef = dynamic_cast<decltype(funcDef)>(&_callableDeclaration))
{
ASTNode::listAccept(funcDef->modifiers(), *this);
if (funcDef->isConstructor())
m_currentConstructor = funcDef;
if (funcDef->isImplemented())
funcDef->body().accept(*this);
}
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
modDef->body().accept(*this);
m_currentConstructor = prevConstructor;
return false;
}
void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression)
{
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
return;
if (_expression.annotation().lValueRequested && _expression.annotation().lValueOfOrdinaryAssignment)
{
if (!m_currentConstructor)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables can only be initialized inline or assigned directly in the constructor."
);
else if (m_currentConstructor->annotation().contract->id() != _variableReference.annotation().contract->id())
m_errorReporter.typeError(
_expression.location(),
"Immutable variables must be initialized in the constructor of the contract they are defined in."
);
else if (m_inLoop)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables can only be initialized once, not in a while statement."
);
else if (m_inBranch)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables must be initialized unconditionally, not in an if statement."
);
if (!m_initializedStateVariables.emplace(&_variableReference).second)
m_errorReporter.typeError(
_expression.location(),
"Immutable state variable already initialized."
);
}
else if (m_inConstructionContext)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables cannot be read during contract creation time, which means "
"they cannot be read in the constructor or any function or modifier called from it."
);
}
void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::SourceLocation const& _location)
{
for (ContractDefinition const* contract: m_currentContract.annotation().linearizedBaseContracts)
for (VariableDeclaration const* varDecl: contract->stateVariables())
if (varDecl->immutable())
if (!util::contains(m_initializedStateVariables, varDecl))
m_errorReporter.typeError(
_location,
solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()),
"Construction control flow ends without initializing all immutable state variables."
);
}
void ImmutableValidator::visitCallableIfNew(Declaration const& _declaration)
{
CallableDeclaration const* _callable = dynamic_cast<CallableDeclaration const*>(&_declaration);
solAssert(_callable != nullptr, "");
if (m_visitedCallables.emplace(_callable).second)
_declaration.accept(*this);
}

View File

@ -0,0 +1,78 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <liblangutil/ErrorReporter.h>
#include <memory>
namespace solidity::frontend
{
/**
* Validates access and initialization of immutable variables:
* must be directly initialized in their respective c'tor
* can not be read by any function/modifier called by the c'tor (or the c'tor itself)
* must be initialized outside loops (only one initialization)
* must be initialized outside ifs (must be initialized unconditionally)
* must be initialized exactly once (no multiple statements)
* must be initialized exactly once (no early return to skip initialization)
*/
class ImmutableValidator: private ASTConstVisitor
{
using CallableDeclarationSet = std::set<CallableDeclaration const*, ASTNode::CompareByID>;
public:
ImmutableValidator(langutil::ErrorReporter& _errorReporter, ContractDefinition const& _contractDefinition):
m_currentContract(_contractDefinition),
m_errorReporter(_errorReporter)
{ }
void analyze();
private:
bool visit(FunctionDefinition const& _functionDefinition);
bool visit(ModifierDefinition const& _modifierDefinition);
bool visit(MemberAccess const& _memberAccess);
bool visit(IfStatement const& _ifStatement);
bool visit(WhileStatement const& _whileStatement);
void endVisit(Identifier const& _identifier);
void endVisit(Return const& _return);
bool analyseCallable(CallableDeclaration const& _callableDeclaration);
void analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression);
void checkAllVariablesInitialized(langutil::SourceLocation const& _location);
void visitCallableIfNew(Declaration const& _declaration);
ContractDefinition const& m_currentContract;
CallableDeclarationSet m_visitedCallables;
std::set<VariableDeclaration const*, ASTNode::CompareByID> m_initializedStateVariables;
langutil::ErrorReporter& m_errorReporter;
FunctionDefinition const* m_currentConstructor = nullptr;
bool m_inLoop = false;
bool m_inBranch = false;
bool m_inConstructionContext = false;
};
}

View File

@ -204,7 +204,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
typeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
}
}

View File

@ -50,7 +50,7 @@ class StaticAnalyzer: private ASTConstVisitor
public:
/// @param _errorReporter provides the error logging functionality.
explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter);
~StaticAnalyzer();
~StaticAnalyzer() override;
/// Performs static analysis on the given source unit and all of its sub-nodes.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings

View File

@ -303,7 +303,9 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
);
}
if (!_function.isImplemented() && !_function.modifiers().empty())
if (m_isInterface && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions in interfaces cannot have modifiers.");
else if (!_function.isImplemented() && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
return true;

View File

@ -483,8 +483,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
);
}
else if (_variable.immutable())
{
if (!_variable.type()->isValueType())
m_errorReporter.typeError(_variable.location(), "Immutable variables cannot have a non-value type.");
if (
auto const* functionType = dynamic_cast<FunctionType const*>(_variable.type());
functionType && functionType->kind() == FunctionType::Kind::External
)
m_errorReporter.typeError(_variable.location(), "Immutable variables of external function type are not yet supported.");
solAssert(_variable.type()->sizeOnStack() == 1 || m_errorReporter.hasErrors(), "");
}
if (!_variable.isStateVariable())
{
@ -742,6 +750,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
if (dynamic_cast<FunctionDefinition const*>(declaration))
{
m_errorReporter.declarationError(_identifier.location, "Access to functions is not allowed in inline assembly.");
return size_t(-1);
}
else if (dynamic_cast<VariableDeclaration const*>(declaration))
{
@ -1063,17 +1073,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
if (!varDecl.annotation().type)
m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed.");
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl)))
{
if (ref->dataStoredIn(DataLocation::Storage))
{
string errorText{"Uninitialized storage pointer."};
solAssert(varDecl.referenceLocation() != VariableDeclaration::Location::Unspecified, "Expected a specified location at this point");
solAssert(m_scope, "");
m_errorReporter.declarationError(varDecl.location(), errorText);
}
}
else if (dynamic_cast<MappingType const*>(type(varDecl)))
if (dynamic_cast<MappingType const*>(type(varDecl)))
m_errorReporter.typeError(
varDecl.location(),
"Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
@ -1319,11 +1319,11 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
{
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
auto const& types = tupleType ? tupleType->components() : vector<TypePointer> { &_type };
auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector<TypePointer> { &_type };
solAssert(
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
"Array sizes don't match or no errors generated."
"Array sizes don't match and no errors generated."
);
for (size_t i = 0; i < min(tupleExpression->components().size(), types.size()); i++)
@ -1347,7 +1347,10 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
bool TypeChecker::visit(Assignment const& _assignment)
{
requireLValue(_assignment.leftHandSide());
requireLValue(
_assignment.leftHandSide(),
_assignment.assignmentOperator() == Token::Assign
);
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
@ -1405,7 +1408,10 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
for (auto const& component: components)
if (component)
{
requireLValue(*component);
requireLValue(
*component,
_tuple.annotation().lValueOfOrdinaryAssignment
);
types.push_back(type(*component));
}
else
@ -1490,7 +1496,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
Token op = _operation.getOperator();
bool const modifying = (op == Token::Inc || op == Token::Dec || op == Token::Delete);
if (modifying)
requireLValue(_operation.subExpression());
requireLValue(_operation.subExpression(), false);
else
_operation.subExpression().accept(*this);
TypePointer const& subExprType = type(_operation.subExpression());
@ -2998,9 +3004,10 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
return true;
}
void TypeChecker::requireLValue(Expression const& _expression)
void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAssignment)
{
_expression.annotation().lValueRequested = true;
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
_expression.accept(*this);
if (_expression.annotation().isLValue)

View File

@ -158,7 +158,7 @@ private:
/// convertible to @a _expectedType.
bool expectType(Expression const& _expression, Type const& _expectedType);
/// Runs type checks on @a _expression to infer its type and then checks that it is an LValue.
void requireLValue(Expression const& _expression);
void requireLValue(Expression const& _expression, bool _ordinaryAssignment);
ContractDefinition const* m_scope = nullptr;

View File

@ -217,6 +217,37 @@ ContractDefinitionAnnotation& ContractDefinition::annotation() const
return initAnnotation<ContractDefinitionAnnotation>();
}
ContractDefinition const* ContractDefinition::superContract(ContractDefinition const& _mostDerivedContract) const
{
auto const& hierarchy = _mostDerivedContract.annotation().linearizedBaseContracts;
auto it = find(hierarchy.begin(), hierarchy.end(), this);
solAssert(it != hierarchy.end(), "Base not found in inheritance hierarchy.");
++it;
if (it == hierarchy.end())
return nullptr;
else
{
solAssert(*it != this, "");
return *it;
}
}
FunctionDefinition const* ContractDefinition::nextConstructor(ContractDefinition const& _mostDerivedContract) const
{
ContractDefinition const* next = superContract(_mostDerivedContract);
if (next == nullptr)
return nullptr;
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
if (c == next || next == nullptr)
{
if (c->constructor())
return c->constructor();
next = nullptr;
}
return nullptr;
}
TypeNameAnnotation& TypeName::annotation() const
{
return initAnnotation<TypeNameAnnotation>();
@ -319,6 +350,37 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
return initAnnotation<FunctionDefinitionAnnotation>();
}
FunctionDefinition const& FunctionDefinition::resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart
) const
{
solAssert(!isConstructor(), "");
// If we are not doing super-lookup and the function is not virtual, we can stop here.
if (_searchStart == nullptr && !virtualSemantics())
return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
FunctionType const* functionType = TypeProvider::function(*this)->asCallableFunction(false);
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
if (_searchStart != nullptr && c != _searchStart)
continue;
_searchStart = nullptr;
for (FunctionDefinition const* function: c->definedFunctions())
if (
function->name() == name() &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(*functionType)
)
return *function;
}
solAssert(false, "Virtual function " + name() + " not found.");
return *this; // not reached
}
TypePointer ModifierDefinition::type() const
{
return TypeProvider::modifier(*this);
@ -329,6 +391,33 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const
return initAnnotation<ModifierDefinitionAnnotation>();
}
ModifierDefinition const& ModifierDefinition::resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart
) const
{
solAssert(_searchStart == nullptr, "Used super in connection with modifiers.");
// If we are not doing super-lookup and the modifier is not virtual, we can stop here.
if (_searchStart == nullptr && !virtualSemantics())
return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
if (_searchStart != nullptr && c != _searchStart)
continue;
_searchStart = nullptr;
for (ModifierDefinition const* modifier: c->functionModifiers())
if (modifier->name() == name())
return *modifier;
}
solAssert(false, "Virtual modifier " + name() + " not found.");
return *this; // not reached
}
TypePointer EventDefinition::type() const
{
return TypeProvider::function(*this);

View File

@ -62,6 +62,24 @@ class ASTConstVisitor;
class ASTNode: private boost::noncopyable
{
public:
struct CompareByID
{
using is_transparent = void;
bool operator()(ASTNode const* _lhs, ASTNode const* _rhs) const
{
return _lhs->id() < _rhs->id();
}
bool operator()(ASTNode const* _lhs, int64_t _rhs) const
{
return _lhs->id() < _rhs;
}
bool operator()(int64_t _lhs, ASTNode const* _rhs) const
{
return _lhs < _rhs->id();
}
};
using SourceLocation = langutil::SourceLocation;
explicit ASTNode(int64_t _id, SourceLocation const& _location);
@ -500,6 +518,10 @@ public:
bool abstract() const { return m_abstract; }
ContractDefinition const* superContract(ContractDefinition const& _mostDerivedContract) const;
/// @returns the next constructor in the inheritance hierarchy.
FunctionDefinition const* nextConstructor(ContractDefinition const& _mostDerivedContract) const;
private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<ASTNode>> m_subNodes;
@ -689,6 +711,18 @@ public:
CallableDeclarationAnnotation& annotation() const override = 0;
/// Performs virtual or super function/modifier lookup:
/// If @a _searchStart is nullptr, performs virtual function lookup, i.e.
/// searches the inheritance hierarchy of @a _mostDerivedContract towards the base
/// and returns the first function/modifier definition that
/// is overwritten by this callable.
/// If @a _searchStart is non-null, starts searching only from that contract, but
/// still in the hierarchy of @a _mostDerivedContract.
virtual CallableDeclaration const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const = 0;
protected:
ASTPointer<ParameterList> m_parameters;
ASTPointer<OverrideSpecifier> m_overrides;
@ -799,6 +833,12 @@ public:
CallableDeclaration::virtualSemantics() ||
(annotation().contract && annotation().contract->isInterface());
}
FunctionDefinition const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const override;
private:
StateMutability m_stateMutability;
Token const m_kind;
@ -945,6 +985,12 @@ public:
ModifierDefinitionAnnotation& annotation() const override;
ModifierDefinition const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const override;
private:
ASTPointer<Block> m_body;
};
@ -1010,6 +1056,14 @@ public:
EventDefinitionAnnotation& annotation() const override;
CallableDeclaration const& resolveVirtual(
ContractDefinition const&,
ContractDefinition const*
) const override
{
return *this;
}
private:
bool m_anonymous = false;
};

View File

@ -47,6 +47,14 @@ using TypePointer = Type const*;
struct ASTAnnotation
{
ASTAnnotation() = default;
ASTAnnotation(ASTAnnotation const&) = delete;
ASTAnnotation(ASTAnnotation&&) = delete;
ASTAnnotation& operator=(ASTAnnotation const&) = delete;
ASTAnnotation& operator=(ASTAnnotation&&) = delete;
virtual ~ASTAnnotation() = default;
};
@ -58,7 +66,16 @@ struct DocTag
struct StructurallyDocumentedAnnotation
{
StructurallyDocumentedAnnotation() = default;
StructurallyDocumentedAnnotation(StructurallyDocumentedAnnotation const&) = delete;
StructurallyDocumentedAnnotation(StructurallyDocumentedAnnotation&&) = delete;
StructurallyDocumentedAnnotation& operator=(StructurallyDocumentedAnnotation const&) = delete;
StructurallyDocumentedAnnotation& operator=(StructurallyDocumentedAnnotation&&) = delete;
virtual ~StructurallyDocumentedAnnotation() = default;
/// Mapping docstring tag name -> content.
std::multimap<std::string, DocTag> docTags;
};
@ -75,6 +92,16 @@ struct SourceUnitAnnotation: ASTAnnotation
struct ScopableAnnotation
{
ScopableAnnotation() = default;
ScopableAnnotation(ScopableAnnotation const&) = delete;
ScopableAnnotation(ScopableAnnotation&&) = delete;
ScopableAnnotation& operator=(ScopableAnnotation const&) = delete;
ScopableAnnotation& operator=(ScopableAnnotation&&) = delete;
virtual ~ScopableAnnotation() = default;
/// The scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step.
ASTNode const* scope = nullptr;
@ -208,6 +235,9 @@ struct ExpressionAnnotation: ASTAnnotation
bool isLValue = false;
/// Whether the expression is used in a context where the LValue is actually required.
bool lValueRequested = false;
/// Whether the expression is an lvalue that is only assigned.
/// Would be false for --, ++, delete, +=, -=, ....
bool lValueOfOrdinaryAssignment = false;
/// Types and - if given - names of arguments if the expr. is a function
/// that is called, used for overload resoultion

View File

@ -41,7 +41,16 @@ namespace solidity::frontend
class ASTVisitor
{
public:
ASTVisitor() = default;
ASTVisitor(ASTVisitor const&) = delete;
ASTVisitor(ASTVisitor&&) = delete;
ASTVisitor& operator=(ASTVisitor const&) = delete;
ASTVisitor& operator=(ASTVisitor&&) = delete;
virtual ~ASTVisitor() = default;
virtual bool visit(SourceUnit& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective& _node) { return visitNode(_node); }
@ -158,7 +167,16 @@ protected:
class ASTConstVisitor
{
public:
ASTConstVisitor() = default;
ASTConstVisitor(ASTConstVisitor const&) = delete;
ASTConstVisitor(ASTConstVisitor&&) = delete;
ASTConstVisitor& operator=(ASTConstVisitor const&) = delete;
ASTConstVisitor& operator=(ASTConstVisitor&&) = delete;
virtual ~ASTConstVisitor() = default;
virtual bool visit(SourceUnit const& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective const& _node) { return visitNode(_node); }

View File

@ -2012,6 +2012,16 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
return variablesAndOffsets;
}
vector<VariableDeclaration const*> ContractType::immutableVariables() const
{
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (variable->immutable())
variables.push_back(variable);
return variables;
}
vector<tuple<string, TypePointer>> ContractType::makeStackItems() const
{
if (m_super)
@ -2328,7 +2338,7 @@ TypePointers StructType::memoryMemberTypes() const
TypePointers types;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (variable->annotation().type->canLiveOutsideStorage())
types.push_back(variable->annotation().type);
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
return types;
}

View File

@ -895,6 +895,8 @@ public:
/// @returns a list of all state variables (including inherited) of the contract and their
/// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
/// @returns a list of all immutable variables (including inherited) of the contract.
std::vector<VariableDeclaration const*> immutableVariables() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:

View File

@ -71,6 +71,49 @@ void CompilerContext::addStateVariable(
m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset);
}
void CompilerContext::addImmutable(VariableDeclaration const& _variable)
{
solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable.");
solUnimplementedAssert(_variable.annotation().type->isValueType(), "Only immutable variables of value type are supported.");
solAssert(m_runtimeContext, "Attempted to register an immutable variable for runtime code generation.");
m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory;
solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap.");
*m_reservedMemory += _variable.annotation().type->memoryHeadSize();
}
size_t CompilerContext::immutableMemoryOffset(VariableDeclaration const& _variable) const
{
solAssert(m_immutableVariables.count(&_variable), "Memory offset of unknown immutable queried.");
solAssert(m_runtimeContext, "Attempted to fetch the memory offset of an immutable variable during runtime code generation.");
return m_immutableVariables.at(&_variable);
}
vector<string> CompilerContext::immutableVariableSlotNames(VariableDeclaration const& _variable)
{
string baseName = to_string(_variable.id());
solAssert(_variable.annotation().type->sizeOnStack() > 0, "");
if (_variable.annotation().type->sizeOnStack() == 1)
return {baseName};
vector<string> names;
auto collectSlotNames = [&](string const& _baseName, TypePointer type, auto const& _recurse) -> void {
for (auto const& [slot, type]: type->stackItems())
if (type)
_recurse(_baseName + " " + slot, type, _recurse);
else
names.emplace_back(_baseName);
};
collectSlotNames(baseName, _variable.annotation().type, collectSlotNames);
return names;
}
size_t CompilerContext::reservedMemory()
{
solAssert(m_reservedMemory.has_value(), "Reserved memory was used before ");
size_t reservedMemory = *m_reservedMemory;
m_reservedMemory = std::nullopt;
return reservedMemory;
}
void CompilerContext::startFunction(Declaration const& _function)
{
m_functionCompilationQueue.startFunction(_function);
@ -223,32 +266,18 @@ evmasm::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration con
return m_functionCompilationQueue.entryLabelIfExists(_declaration);
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function)
{
// Libraries do not allow inheritance and their functions can be inlined, so we should not
// search the inheritance hierarchy (which will be the wrong one in case the function
// is inlined).
if (auto scope = dynamic_cast<ContractDefinition const*>(_function.scope()))
if (scope->isLibrary())
return _function;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin());
}
FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base)
{
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
return resolveVirtualFunction(_function, superContract(_base));
solAssert(m_mostDerivedContract, "No most derived contract set.");
ContractDefinition const* super = _base.superContract(mostDerivedContract());
solAssert(super, "Super contract not available.");
return _function.resolveVirtual(mostDerivedContract(), super);
}
FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const
ContractDefinition const& CompilerContext::mostDerivedContract() const
{
vector<ContractDefinition const*>::const_iterator it = superContract(_contract);
for (; it != m_inheritanceHierarchy.end(); ++it)
if ((*it)->constructor())
return (*it)->constructor();
return nullptr;
solAssert(m_mostDerivedContract, "Most derived contract not set.");
return *m_mostDerivedContract;
}
Declaration const* CompilerContext::nextFunctionToCompile() const
@ -256,24 +285,6 @@ Declaration const* CompilerContext::nextFunctionToCompile() const
return m_functionCompilationQueue.nextFunctionToCompile();
}
ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier(
ModifierDefinition const& _modifier
) const
{
// Libraries do not allow inheritance and their functions can be inlined, so we should not
// search the inheritance hierarchy (which will be the wrong one in case the function
// is inlined).
if (auto scope = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
if (scope->isLibrary())
return _modifier;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
for (ContractDefinition const* contract: m_inheritanceHierarchy)
for (ModifierDefinition const* modifier: contract->functionModifiers())
if (modifier->name() == _modifier.name())
return *modifier;
solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy.");
}
unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const
{
auto res = m_localVariables.find(&_declaration);
@ -500,32 +511,11 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
#endif
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
FunctionDefinition const& _function,
vector<ContractDefinition const*>::const_iterator _searchStart
)
LinkerObject const& CompilerContext::assembledObject() const
{
string name = _function.name();
FunctionType functionType(_function);
auto it = _searchStart;
for (; it != m_inheritanceHierarchy.end(); ++it)
for (FunctionDefinition const* function: (*it)->definedFunctions())
if (
function->name() == name &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
return _function; // not reached
}
vector<ContractDefinition const*>::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const
{
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract);
solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy.");
return ++it;
LinkerObject const& object = m_asm->assemble();
solAssert(object.immutableReferences.empty(), "Leftover immutables.");
return object;
}
string CompilerContext::revertReasonIfDebug(string const& _message)

View File

@ -64,6 +64,7 @@ public:
m_asm(std::make_shared<evmasm::Assembly>()),
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_reservedMemory{0},
m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
@ -80,6 +81,16 @@ public:
bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addImmutable(VariableDeclaration const& _declaration);
/// @returns the reserved memory for storing the value of the immutable @a _variable during contract creation.
size_t immutableMemoryOffset(VariableDeclaration const& _variable) const;
/// @returns a list of slot names referring to the stack slots of an immutable variable.
static std::vector<std::string> immutableVariableSlotNames(VariableDeclaration const& _variable);
/// @returns the reserved memory and resets it to mark it as used.
size_t reservedMemory();
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(Declaration const& _declaration);
/// Removes all local variables currently allocated above _stackHeight.
@ -103,15 +114,12 @@ public:
/// @returns the entry label of the given function. Might return an AssemblyItem of type
/// UndefinedItem if it does not exist yet.
evmasm::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const;
/// @returns the entry label of the given function and takes overrides into account.
FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function);
/// @returns the function that overrides the given declaration from the most derived class just
/// above _base in the current inheritance hierarchy.
FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base);
/// @returns the next constructor in the inheritance hierarchy.
FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const;
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; }
/// Sets the contract currently being compiled - the most derived one.
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
ContractDefinition const& mostDerivedContract() const;
/// @returns the next function in the queue of functions that are still to be compiled
/// (i.e. that were referenced during compilation but where we did not yet generate code for).
@ -160,7 +168,6 @@ public:
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
/// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns
@ -217,6 +224,10 @@ public:
evmasm::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); }
/// Appends the address (virtual, will be filled in by linker) of a library.
void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); }
/// Appends an immutable variable. The value will be filled in by the constructor.
void appendImmutable(std::string const& _identifier) { m_asm->appendImmutable(_identifier); }
/// Appends an assignment to an immutable variable. Only valid in creation code.
void appendImmutableAssignment(std::string const& _identifier) { m_asm->appendImmutableAssignment(_identifier); }
/// Appends a zero-address that can be replaced by something else at deploy time (if the
/// position in bytecode is known).
void appendDeployTimeAddress() { m_asm->append(evmasm::PushDeployTimeAddress); }
@ -282,7 +293,7 @@ public:
return m_asm->assemblyJSON(_indicies);
}
evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
evmasm::LinkerObject const& assembledObject() const;
evmasm::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); }
/**
@ -300,14 +311,6 @@ public:
RevertStrings revertStrings() const { return m_revertStrings; }
private:
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
/// the first function definition that is overwritten by _function.
FunctionDefinition const& resolveVirtualFunction(
FunctionDefinition const& _function,
std::vector<ContractDefinition const*>::const_iterator _searchStart
);
/// @returns an iterator to the contract directly above the given contract.
std::vector<ContractDefinition const*>::const_iterator superContract(ContractDefinition const& _contract) const;
/// Updates source location set in the assembly.
void updateSourceLocation();
@ -355,13 +358,19 @@ private:
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers;
/// Storage offsets of state variables
std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Memory offsets reserved for the values of immutable variables during contract creation.
std::map<VariableDeclaration const*, size_t> m_immutableVariables;
/// Total amount of reserved memory. Reserved memory is used to store immutable variables during contract creation.
/// This has to be finalized before initialiseFreeMemoryPointer() is called. That function
/// will reset the optional to verify that.
std::optional<size_t> m_reservedMemory = {0};
/// Offsets of local variables on the stack (relative to stack base).
/// This needs to be a stack because if a modifier contains a local variable and this
/// modifier is applied twice, the position of the variable needs to be restored
/// after the nested modifier is left.
std::map<Declaration const*, std::vector<unsigned>> m_localVariables;
/// List of current inheritance hierarchy from derived to base.
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
/// The contract currently being compiled. Virtual function lookup starts from this contarct.
ContractDefinition const* m_mostDerivedContract = nullptr;
/// Stack of current visited AST nodes, used for location attachment
std::stack<ASTNode const*> m_visitedNodes;
/// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime.

View File

@ -51,7 +51,9 @@ static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPoi
void CompilerUtils::initialiseFreeMemoryPointer()
{
m_context << u256(generalPurposeMemoryStart);
size_t reservedMemory = m_context.reservedMemory();
solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, "");
m_context << (u256(generalPurposeMemoryStart) + reservedMemory);
storeFreeMemoryPointer();
}

View File

@ -129,7 +129,9 @@ void ContractCompiler::initializeContext(
{
m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setOtherCompilers(_otherCompilers);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
if (m_runtimeCompiler)
registerImmutableVariables(_contract);
CompilerUtils(m_context).initialiseFreeMemoryPointer();
registerStateVariables(_contract);
m_context.resetVisitedNodes(&_contract);
@ -157,7 +159,7 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
if (FunctionDefinition const* constructor = _contract.constructor())
appendConstructor(*constructor);
else if (auto c = m_context.nextConstructor(_contract))
else if (auto c = _contract.nextConstructor(m_context.mostDerivedContract()))
appendBaseConstructor(*c);
else
appendCallValueCheck();
@ -183,10 +185,26 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
m_context << deployRoutine;
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
ContractType contractType(_contract);
auto const& immutables = contractType.immutableVariables();
// Push all immutable values on the stack.
for (auto const& immutable: immutables)
CompilerUtils(m_context).loadFromMemory(m_context.immutableMemoryOffset(*immutable), *immutable->annotation().type);
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << Instruction::DUP1;
if (immutables.empty())
m_context << Instruction::DUP1;
m_context.pushSubroutineOffset(m_context.runtimeSub());
m_context << u256(0) << Instruction::CODECOPY;
// Assign immutable values from stack in reversed order.
for (auto const& immutable: immutables | boost::adaptors::reversed)
{
auto slotNames = m_context.immutableVariableSlotNames(*immutable);
for (auto&& slotName: slotNames | boost::adaptors::reversed)
m_context.appendImmutableAssignment(slotName);
}
if (!immutables.empty())
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << u256(0) << Instruction::RETURN;
return m_context.runtimeSub();
@ -521,11 +539,18 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}
void ContractCompiler::registerImmutableVariables(ContractDefinition const& _contract)
{
solAssert(m_runtimeCompiler, "Attempted to register immutables for runtime code generation.");
for (auto const& var: ContractType(_contract).immutableVariables())
m_context.addImmutable(*var);
}
void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract)
{
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
for (VariableDeclaration const* variable: _contract.stateVariables())
if (variable->value() && !variable->isConstant() && !variable->immutable())
if (variable->value() && !variable->isConstant())
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
}
@ -541,8 +566,6 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
if (_variableDeclaration.isConstant())
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendConstStateVariableAccessor(_variableDeclaration);
else if (_variableDeclaration.immutable())
solUnimplementedAssert(false, "");
else
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendStateVariableAccessor(_variableDeclaration);
@ -573,7 +596,9 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
appendStackVariableInitialisation(*variable);
if (_function.isConstructor())
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
if (auto c = dynamic_cast<ContractDefinition const&>(*_function.scope()).nextConstructor(
m_context.mostDerivedContract()
))
appendBaseConstructor(*c);
solAssert(m_returnTags.empty(), "");
@ -664,7 +689,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
functionDef = &functionDef->resolveVirtual(m_context.mostDerivedContract());
auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag();
solAssert(functionEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendLabelReference(size_t(functionEntryLabel.data()));
@ -1310,10 +1335,9 @@ void ContractCompiler::appendModifierOrFunctionCode()
appendModifierOrFunctionCode();
else
{
ModifierDefinition const& nonVirtualModifier = dynamic_cast<ModifierDefinition const&>(
ModifierDefinition const& modifier = dynamic_cast<ModifierDefinition const&>(
*modifierInvocation->name()->annotation().referencedDeclaration
);
ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier);
).resolveVirtual(m_context.mostDerivedContract());
CompilerContext::LocationSetter locationSetter(m_context, modifier);
std::vector<ASTPointer<Expression>> const& modifierArguments =
modifierInvocation->arguments() ? *modifierInvocation->arguments() : std::vector<ASTPointer<Expression>>();

View File

@ -99,6 +99,7 @@ private:
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
void registerStateVariables(ContractDefinition const& _contract);
void registerImmutableVariables(ContractDefinition const& _contract);
void initializeStateVariables(ContractDefinition const& _contract);
bool visit(VariableDeclaration const& _variableDeclaration) override;

View File

@ -73,7 +73,10 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
utils().convertType(*type, *_varDecl.annotation().type);
type = _varDecl.annotation().type;
}
StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
if (_varDecl.immutable())
ImmutableItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
else
StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
}
void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl)
@ -88,16 +91,22 @@ void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration co
void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl)
{
solAssert(!_varDecl.isConstant() && !_varDecl.immutable(), "");
solAssert(!_varDecl.isConstant(), "");
CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
FunctionType accessorType(_varDecl);
TypePointers paramTypes = accessorType.parameterTypes();
if (_varDecl.immutable())
solAssert(paramTypes.empty(), "");
m_context.adjustStackOffset(1 + CompilerUtils::sizeOnStack(paramTypes));
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
m_context << location.first << u256(location.second);
if (!_varDecl.immutable())
{
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
m_context << location.first << u256(location.second);
}
TypePointer returnType = _varDecl.annotation().type;
@ -179,6 +188,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
solAssert(returnTypes.size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(returnType))
{
solAssert(!_varDecl.immutable(), "");
// remove offset
m_context << Instruction::POP;
auto const& names = accessorType.returnParameterNames();
@ -205,7 +215,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{
// simple value or array
solAssert(returnTypes.size() == 1, "");
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
if (_varDecl.immutable())
ImmutableItem(m_context, _varDecl).retrieveValue(SourceLocation());
else
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
utils().convertType(*returnType, *returnTypes.front());
retSizeOnStack = returnTypes.front()->sizeOnStack();
}
@ -572,7 +585,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Do not directly visit the identifier, because this way, we can avoid
// the runtime entry label to be created at the creation time context.
CompilerContext::LocationSetter locationSetter2(m_context, *identifier);
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false);
utils().pushCombinedFunctionEntryLabel(
functionDef->resolveVirtual(m_context.mostDerivedContract()),
false
);
shortcutTaken = true;
}
}
@ -992,6 +1008,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Fetch requested length.
acceptAndConvert(*arguments[0], *TypeProvider::uint256());
// Make sure we can allocate memory without overflow
m_context << u256(0xffffffffffffffff);
m_context << Instruction::DUP2;
m_context << Instruction::GT;
m_context.appendConditionalRevert();
// Stack: requested_length
utils().fetchFreeMemoryPointer();
@ -1861,7 +1883,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
// we want to avoid having a reference to the runtime function entry point in the
// constructor context, since this would force the compiler to include unreferenced
// internal functions in the runtime contex.
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef));
utils().pushCombinedFunctionEntryLabel(functionDef->resolveVirtual(m_context.mostDerivedContract()));
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
appendVariable(*variable, static_cast<Expression const&>(_identifier));
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
@ -2436,7 +2458,7 @@ void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Ex
if (_variable.isConstant())
acceptAndConvert(*_variable.value(), *_variable.annotation().type);
else if (_variable.immutable())
solUnimplemented("");
setLValue<ImmutableItem>(_expression, _variable);
else
setLValueFromDeclaration(_variable, _expression);
}

View File

@ -144,9 +144,46 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const
m_context << Instruction::POP;
}
ImmutableItem::ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable):
LValue(_compilerContext, _variable.annotation().type), m_variable(_variable)
{
solAssert(_variable.immutable(), "");
}
void ImmutableItem::retrieveValue(SourceLocation const&, bool) const
{
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(!m_context.runtimeContext(), "Tried to read immutable at construction time.");
for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable))
m_context.appendImmutable(slotName);
}
void ImmutableItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
{
CompilerUtils utils(m_context);
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(_sourceType.isValueType(), "");
utils.convertType(_sourceType, *m_dataType, true);
m_context << m_context.immutableMemoryOffset(m_variable);
if (_move)
utils.moveIntoStack(m_dataType->sizeOnStack());
else
utils.copyToStackTop(m_dataType->sizeOnStack() + 1, m_dataType->sizeOnStack());
utils.storeInMemoryDynamic(*m_dataType, false);
m_context << Instruction::POP;
}
void ImmutableItem::setToZero(SourceLocation const&, bool) const
{
solAssert(false, "Attempted to set immutable variable to zero.");
}
StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
StorageItem(_compilerContext, *_declaration.annotation().type)
{
solAssert(!_declaration.immutable(), "");
auto const& location = m_context.storageLocationOfVariable(_declaration);
m_context << location.first << u256(location.second);
}

View File

@ -23,6 +23,7 @@
#pragma once
#include <libsolidity/codegen/ArrayUtils.h>
#include <libsolutil/Common.h>
#include <liblangutil/SourceLocation.h>
#include <memory>
#include <vector>
@ -82,12 +83,12 @@ public:
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -108,12 +109,12 @@ public:
MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true);
unsigned sizeOnStack() const override { return 1; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -122,6 +123,30 @@ private:
bool m_padded = false;
};
/**
* Reference to an immutable variable. During contract creation this refers to a location in memory. At the
* end of contract creation the values from these memory locations are copied into all occurrences of the immutable
* variable in the runtime code.
*/
class ImmutableItem: public LValue
{
public:
ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable);
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
private:
VariableDeclaration const& m_variable;
};
/**
* Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>,
* where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied
@ -136,12 +161,12 @@ public:
StorageItem(CompilerContext& _compilerContext, Type const& _type);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -158,12 +183,12 @@ public:
StorageByteArrayElement(CompilerContext& _compilerContext);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -180,12 +205,12 @@ public:
TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues);
unsigned sizeOnStack() const override;
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;

View File

@ -658,6 +658,8 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
solAssert(_type.baseType()->isValueType(), "");
string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
@ -794,6 +796,7 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
});
}
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
@ -1178,7 +1181,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, value)
}
}
)")
("functionName", functionName)
.render();
@ -1202,7 +1205,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, <cleanup>(value))
}
}
)")
("functionName", functionName)
("cleanup", cleanupFunction(_type))
@ -1334,28 +1337,112 @@ string YulUtilFunctions::allocationFunction()
});
}
string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
if (_type.baseType()->hasSimpleZeroValueInMemory())
return zeroMemoryFunction(*_type.baseType());
return zeroComplexMemoryArrayFunction(_type);
}
string functionName = "allocate_memory_array_" + _type.identifier();
string YulUtilFunctions::zeroMemoryFunction(Type const& _type)
{
solAssert(_type.hasSimpleZeroValueInMemory(), "");
string functionName = "zero_memory_chunk_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
memPtr := <alloc>(<allocSize>(length))
<?dynamic>
mstore(memPtr, length)
</dynamic>
function <functionName>(dataStart, dataSizeInBytes) {
calldatacopy(dataStart, calldatasize(), dataSizeInBytes)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::zeroComplexMemoryArrayFunction(ArrayType const& _type)
{
solAssert(!_type.baseType()->hasSimpleZeroValueInMemory(), "");
string functionName = "zero_complex_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
solAssert(_type.memoryStride() == 32, "");
return Whiskers(R"(
function <functionName>(dataStart, dataSizeInBytes) {
for {let i := 0} lt(i, dataSizeInBytes) { i := add(i, <stride>) } {
mstore(add(dataStart, i), <zeroValue>())
}
}
)")
("functionName", functionName)
("stride", to_string(_type.memoryStride()))
("zeroValue", zeroValueFunction(*_type.baseType(), false))
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
string functionName = "allocate_and_zero_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
let allocSize := <allocSize>(length)
memPtr := <alloc>(allocSize)
let dataStart := memPtr
let dataSize := allocSize
<?dynamic>
dataStart := add(dataStart, 32)
dataSize := sub(dataSize, 32)
mstore(memPtr, length)
</dynamic>
<zeroArrayFunction>(dataStart, dataSize)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("zeroArrayFunction", zeroMemoryArrayFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
{
string functionName = "allocate_and_initialize_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
let allocSize := <allocSize>()
memPtr := <alloc>(allocSize)
let offset := memPtr
<#member>
mstore(offset, <zeroValue>())
offset := add(offset, 32)
</member>
}
)");
templ("functionName", functionName);
templ("alloc", allocationFunction());
TypePointers const& members = _type.memoryMemberTypes();
templ("allocSize", _type.memoryDataSize().str());
vector<map<string, string>> memberParams(members.size());
for (size_t i = 0; i < members.size(); ++i)
{
solAssert(members[i]->memoryHeadSize() == 32, "");
solAssert(members[i]->dataStoredIn(DataLocation::Memory), "");
memberParams[i]["zeroValue"] = zeroValueFunction(*members[i], false);
}
templ("member", memberParams);
return templ.render();
});
}
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::Function)
@ -1884,23 +1971,58 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type)
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!");
solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented");
solAssert(_type.category() != Type::Category::Mapping, "");
string const functionName = "zero_value_for_" + _type.identifier();
string const functionName = "zero_value_for_" + string(_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> ret {
<body>
}
)")
FunctionType const* fType = dynamic_cast<FunctionType const*>(&_type);
if (fType && fType->kind() == FunctionType::Kind::External && _splitFunctionTypes)
return Whiskers(R"(
function <functionName>() -> retAddress, retFunction {
retAddress := 0
retFunction := 0
}
)")
("functionName", functionName)
("body", "ret := 0x0")
.render();
});
Whiskers templ(R"(
function <functionName>() -> ret {
ret := <zeroValue>
}
)");
templ("functionName", functionName);
if (_type.isValueType())
{
solAssert((
_type.hasSimpleZeroValueInMemory() ||
(fType && (fType->kind() == FunctionType::Kind::Internal || fType->kind() == FunctionType::Kind::External))
), "");
templ("zeroValue", "0");
}
else
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type))
{
if (_type.isDynamicallySized())
templ("zeroValue", to_string(CompilerUtils::zeroPointer));
else
templ("zeroValue", allocateAndInitializeMemoryArrayFunction(*arrayType) + "(" + to_string(unsigned(arrayType->length())) + ")");
}
else if (auto const* structType = dynamic_cast<StructType const*>(&_type))
templ("zeroValue", allocateAndInitializeMemoryStructFunction(*structType) + "()");
else
solUnimplementedAssert(false, "");
}
return templ.render();
});
}
string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)

View File

@ -37,6 +37,7 @@ class Type;
class ArrayType;
class MappingType;
class IntegerType;
class StructType;
/**
* Component that can generate various useful Yul functions.
@ -154,6 +155,7 @@ public:
/// to store an array in memory given its length (internally encoded, not ABI encoded).
/// The function reverts for too large lengths.
std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
/// for the data position of an array which is stored in that slot / memory area / calldata area.
@ -250,10 +252,27 @@ public:
/// Return value: pointer
std::string allocationFunction();
/// @returns the name of a function that allocates a memory array.
/// @returns the name of a function that zeroes an array.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that zeroes a chunk of memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryFunction(Type const& _type);
/// @returns the name of a function that zeroes an array
/// where the base does not have simple zero value in memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroComplexMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory array.
/// For dynamic arrays it adds space for length and stores it.
/// signature: (length) -> memPtr
std::string allocateMemoryArrayFunction(ArrayType const& _type);
std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory struct.
/// signature: (members) -> memPtr
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
/// @returns the name of the function that converts a value of type @a _from
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
@ -288,8 +307,9 @@ public:
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);
/// provided type.
/// @param _splitFunctionTypes if false, returns two zeroes
std::string zeroValueFunction(Type const& _type, bool _splitFunctionTypes = true);
/// @returns the name of a function that will set the given storage item to
/// zero

View File

@ -22,6 +22,7 @@
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
@ -31,6 +32,12 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
ContractDefinition const& IRGenerationContext::mostDerivedContract() const
{
solAssert(m_mostDerivedContract, "Most derived contract requested but not set.");
return *m_mostDerivedContract;
}
IRVariable const& IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
{
auto const& [it, didInsert] = m_localVariables.emplace(
@ -70,26 +77,9 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
// but since we do not work with jump positions anymore, this should not be a problem, right?
string name = _function.name();
FunctionType functionType(_function);
for (auto const& contract: m_inheritanceHierarchy)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
function->name() == name &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
}
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
{
return functionName(virtualFunction(_functionDeclaration));
return functionName(_functionDeclaration.resolveVirtual(mostDerivedContract()));
}
string IRGenerationContext::newYulVariable()
@ -120,12 +110,13 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
templ("arrow", _out > 0 ? "->" : "");
templ("out", suffixedVariableNameList("out_", 0, _out));
vector<map<string, string>> functions;
for (auto const& contract: m_inheritanceHierarchy)
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
FunctionType const* functionType = TypeProvider::function(*function)->asCallableFunction(false);
!function->isConstructor() &&
function->parameters().size() == _in &&
function->returnParameters().size() == _out
TupleType(functionType->parameterTypes()).sizeOnStack() == _in &&
TupleType(functionType->returnParameterTypes()).sizeOnStack() == _out
)
{
// 0 is reserved for uninitialized function pointers

View File

@ -61,11 +61,12 @@ public:
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
/// Sets the most derived contract (the one currently being compiled)>
void setMostDerivedContract(ContractDefinition const& _mostDerivedContract)
{
m_inheritanceHierarchy = std::move(_hierarchy);
m_mostDerivedContract = &_mostDerivedContract;
}
ContractDefinition const& mostDerivedContract() const;
IRVariable const& addLocalVariable(VariableDeclaration const& _varDecl);
@ -81,7 +82,6 @@ public:
std::string functionName(FunctionDefinition const& _function);
std::string functionName(VariableDeclaration const& _varDecl);
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
std::string newYulVariable();
@ -103,7 +103,7 @@ private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
ContractDefinition const* m_mostDerivedContract = nullptr;
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;

View File

@ -110,7 +110,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
t("functions", m_context.functionCollector().requestedFunctions());
resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
@ -133,6 +133,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
return m_context.functionCollector().createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
<initReturnVariables>
<body>
}
)");
@ -142,9 +143,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string retParams;
string retInit;
for (auto const& varDecl: _function.returnParameters())
{
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
retInit += generateInitialAssignment(*varDecl);
}
t("returns", retParams.empty() ? "" : " -> " + retParams);
t("initReturnVariables", retInit);
t("body", generate(_function.body()));
return t.render();
});
@ -226,6 +232,13 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
}
}
string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl)
{
IRGeneratorForStatements generator(m_context, m_utils);
generator.initializeLocalVar(_varDecl);
return generator.code();
}
string IRGenerator::constructorCode(ContractDefinition const& _contract)
{
// Initialization of state variables in base-to-derived order.
@ -259,11 +272,28 @@ string IRGenerator::constructorCode(ContractDefinition const& _contract)
if (constructor)
{
solUnimplementedAssert(constructor->parameters().empty(), "");
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
unsigned paramVars = make_shared<TupleType>(constructor->functionType(false)->parameterTypes())->sizeOnStack();
// TODO base constructors
Whiskers t(R"X(
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
out << m_context.functionName(*constructor) + "()\n";
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
<assignToParams> <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
<constructorName>(<params>)
)X");
t("object", creationObjectName(_contract));
t("allocate", m_utils.allocationFunction());
t("assignToParams", paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ");
t("params", suffixedVariableNameList("param_", 0, paramVars));
t("abiDecode", abiFunctions.tupleDecoder(constructor->functionType(false)->parameterTypes(), true));
t("constructorName", m_context.functionName(*constructor));
out << t.render();
}
return out.str();
@ -389,7 +419,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
);
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
for (auto const& var: ContractType(_contract).stateVariables())
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}

View File

@ -61,6 +61,9 @@ private:
/// Generates a getter for the given declaration and returns its name
std::string generateGetter(VariableDeclaration const& _varDecl);
/// Generates code that assigns the initial value of the respective type.
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
std::string constructorCode(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();

View File

@ -154,6 +154,19 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
}
}
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
{
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
auto const* type = _varDecl.type();
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
return;
IRVariable zero = zeroValue(*type);
assign(m_context.localVariable(_varDecl), zero);
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (Expression const* expression = _varDeclStatement.initialValue())
@ -179,7 +192,10 @@ void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _var
else
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
{
declare(m_context.addLocalVariable(*decl));
initializeLocalVar(*decl);
}
}
bool IRGeneratorForStatements::visit(Conditional const& _conditional)
@ -568,7 +584,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
define(_functionCall) <<
m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) <<
m_context.internalDispatch(
TupleType(functionType->parameterTypes()).sizeOnStack(),
TupleType(functionType->returnParameterTypes()).sizeOnStack()
) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
@ -667,7 +686,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
define(_functionCall) <<
m_utils.allocateMemoryArrayFunction(arrayType) <<
m_utils.allocateAndInitializeMemoryArrayFunction(arrayType) <<
"(" <<
value.commaSeparatedList() <<
")\n";
@ -1165,7 +1184,7 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
return;
}
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
define(_identifier) << to_string(m_context.virtualFunction(*functionDef).id()) << "\n";
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
{
// TODO for the constant case, we have to be careful:
@ -1436,6 +1455,12 @@ std::ostream& IRGeneratorForStatements::define(IRVariable const& _var)
return m_code;
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
}
void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable const& _rhs, bool _declare)
{
string output;
@ -1455,10 +1480,15 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable
_rhs.commaSeparatedList() <<
")\n";
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
IRVariable irVar{
"zero_value_for_type_" + _type.identifier() + m_context.newYulVariable(),
_type
};
define(irVar) << m_utils.zeroValueFunction(_type, _splitFunctionTypes) << "()\n";
return irVar;
}
void IRGeneratorForStatements::appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr)

View File

@ -46,6 +46,8 @@ public:
/// Generates code to initialize the given state variable.
void initializeStateVar(VariableDeclaration const& _varDecl);
/// Generates code to initialize the given local variable.
void initializeLocalVar(VariableDeclaration const& _varDecl);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
@ -100,6 +102,11 @@ private:
void declareAssign(IRVariable const& _var, IRVariable const& _value, bool _define);
/// @returns an IRVariable with the zero
/// value of @a _type.
/// @param _splitFunctionTypes if false, returns two zeroes
IRVariable zeroValue(Type const& _type, bool _splitFunctionTypes = true);
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);

View File

@ -18,6 +18,7 @@
#include <libsolidity/formal/BMC.h>
#include <libsolidity/formal/SMTPortfolio.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicTypes.h>
#include <boost/algorithm/string/replace.hpp>
@ -376,7 +377,7 @@ void BMC::endVisit(FunctionCall const& _funCall)
SMTEncoder::endVisit(_funCall);
auto value = _funCall.arguments().front();
solAssert(value, "");
smt::Expression thisBalance = m_context.balance();
smt::Expression thisBalance = m_context.state().balance();
addVerificationTarget(
VerificationTarget::Type::Balance,

View File

@ -79,10 +79,9 @@ void CHC::analyze(SourceUnit const& _source)
resetSourceAnalysis();
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto genesisSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
boolSort
smt::SortProvider::boolSort
);
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
addRule(genesis(), "genesis");
@ -131,12 +130,9 @@ bool CHC::visit(ContractDefinition const& _contract)
clearIndices(&_contract);
// TODO create static instances for Bool/Int sorts in SolverInterface.
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto errorFunctionSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
boolSort
smt::SortProvider::boolSort
);
string suffix = _contract.name() + "_" + to_string(_contract.id());
@ -664,29 +660,25 @@ vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
smt::SortPointer CHC::constructorSort()
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts,
smt::SortProvider::boolSort
);
}
smt::SortPointer CHC::interfaceSort()
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
m_stateSorts,
boolSort
smt::SortProvider::boolSort
);
}
smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
stateSorts(_contract),
boolSort
smt::SortProvider::boolSort
);
}
@ -703,8 +695,6 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
/// - 1 set of output variables
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
@ -712,8 +702,8 @@ smt::SortPointer CHC::sort(FunctionDefinition const& _function)
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
smt::SortProvider::boolSort
);
}
@ -725,13 +715,12 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
solAssert(fSort, "");
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
vector<smt::SortPointer> varSorts;
for (auto const& var: m_currentFunction->localVariables())
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
fSort->domain + varSorts,
boolSort
smt::SortProvider::boolSort
);
}
@ -740,16 +729,14 @@ smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractD
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
auto sorts = stateSorts(_contract);
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts, outputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + sorts + inputSorts + sorts + outputSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + sorts + inputSorts + sorts + outputSorts,
smt::SortProvider::boolSort
);
}
@ -974,7 +961,11 @@ smt::Expression CHC::predicate(FunctionCall const& _funCall)
for (auto const& var: function->returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
return (*m_summaries.at(contract).at(function))(args);
if (contract->isLibrary())
return (*m_summaries.at(contract).at(function))(args);
solAssert(m_currentContract, "");
return (*m_summaries.at(m_currentContract).at(function))(args);
}
void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)

View File

@ -25,13 +25,8 @@ using namespace solidity::util;
using namespace solidity::frontend::smt;
EncodingContext::EncodingContext():
m_thisAddress(make_unique<SymbolicAddressVariable>("this", *this))
m_state(*this)
{
auto sort = make_shared<ArraySort>(
make_shared<Sort>(Kind::Int),
make_shared<Sort>(Kind::Int)
);
m_balances = make_unique<SymbolicVariable>(sort, "balances", *this);
}
void EncodingContext::reset()
@ -39,8 +34,7 @@ void EncodingContext::reset()
resetAllVariables();
m_expressions.clear();
m_globalContext.clear();
m_thisAddress->resetIndex();
m_balances->resetIndex();
m_state.reset();
m_assertions.clear();
}
@ -183,40 +177,6 @@ bool EncodingContext::knownGlobalSymbol(string const& _var) const
return m_globalContext.count(_var);
}
// Blockchain
Expression EncodingContext::thisAddress()
{
return m_thisAddress->currentValue();
}
Expression EncodingContext::balance()
{
return balance(m_thisAddress->currentValue());
}
Expression EncodingContext::balance(Expression _address)
{
return Expression::select(m_balances->currentValue(), move(_address));
}
void EncodingContext::transfer(Expression _from, Expression _to, Expression _value)
{
unsigned indexBefore = m_balances->index();
addBalance(_from, 0 - _value);
addBalance(_to, move(_value));
unsigned indexAfter = m_balances->index();
solAssert(indexAfter > indexBefore, "");
m_balances->increaseIndex();
/// Do not apply the transfer operation if _from == _to.
auto newBalances = Expression::ite(
move(_from) == move(_to),
m_balances->valueAtIndex(indexBefore),
m_balances->valueAtIndex(indexAfter)
);
addAssertion(m_balances->currentValue() == newBalances);
}
/// Solver.
Expression EncodingContext::assertions()
@ -248,16 +208,3 @@ void EncodingContext::addAssertion(Expression const& _expr)
else
m_assertions.back() = _expr && move(m_assertions.back());
}
/// Private helpers.
void EncodingContext::addBalance(Expression _address, Expression _value)
{
auto newBalances = Expression::store(
m_balances->currentValue(),
_address,
balance(_address) + move(_value)
);
m_balances->increaseIndex();
addAssertion(newBalances == m_balances->currentValue());
}

View File

@ -18,6 +18,7 @@
#pragma once
#include <libsolidity/formal/SolverInterface.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicVariables.h>
#include <unordered_map>
@ -121,18 +122,6 @@ public:
bool knownGlobalSymbol(std::string const& _var) const;
//@}
/// Blockchain.
//@{
/// Value of `this` address.
Expression thisAddress();
/// @returns the symbolic balance of address `this`.
Expression balance();
/// @returns the symbolic balance of an address.
Expression balance(Expression _address);
/// Transfer _value from _from to _to.
void transfer(Expression _from, Expression _to, Expression _value);
//@}
/// Solver.
//@{
/// @returns conjunction of all added assertions.
@ -148,10 +137,9 @@ public:
}
//@}
private:
/// Adds _value to _account's balance.
void addBalance(Expression _account, Expression _value);
SymbolicState& state() { return m_state; }
private:
/// Symbolic expressions.
//{@
/// Symbolic variables.
@ -164,11 +152,8 @@ private:
/// variables and functions.
std::unordered_map<std::string, std::shared_ptr<smt::SymbolicVariable>> m_globalContext;
/// Symbolic `this` address.
std::unique_ptr<SymbolicAddressVariable> m_thisAddress;
/// Symbolic balances.
std::unique_ptr<SymbolicVariable> m_balances;
/// Symbolic representation of the blockchain state.
SymbolicState m_state;
//@}
/// Solver related.

View File

@ -19,6 +19,7 @@
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/formal/SMTPortfolio.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicTypes.h>
#include <boost/range/adaptors.hpp>
@ -619,10 +620,10 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
auto const& value = args.front();
solAssert(value, "");
smt::Expression thisBalance = m_context.balance();
smt::Expression thisBalance = m_context.state().balance();
setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_context);
m_context.transfer(m_context.thisAddress(), expr(address), expr(*value));
m_context.state().transfer(m_context.state().thisAddress(), expr(address), expr(*value));
break;
}
default:
@ -711,7 +712,7 @@ void SMTEncoder::endVisit(Identifier const& _identifier)
defineGlobalVariable(_identifier.name(), _identifier);
else if (_identifier.name() == "this")
{
defineExpr(_identifier, m_context.thisAddress());
defineExpr(_identifier, m_context.state().thisAddress());
m_uninterpretedTerms.insert(&_identifier);
}
else
@ -858,7 +859,7 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
_memberAccess.expression().accept(*this);
if (_memberAccess.memberName() == "balance")
{
defineExpr(_memberAccess, m_context.balance(expr(_memberAccess.expression())));
defineExpr(_memberAccess, m_context.state().balance(expr(_memberAccess.expression())));
setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_context);
m_uninterpretedTerms.insert(&_memberAccess);
return false;

View File

@ -17,6 +17,8 @@
#pragma once
#include <libsolidity/formal/Sorts.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/interface/ReadFile.h>
#include <liblangutil/Exceptions.h>
@ -52,94 +54,6 @@ enum class CheckResult
SATISFIABLE, UNSATISFIABLE, UNKNOWN, CONFLICTING, ERROR
};
enum class Kind
{
Int,
Bool,
Function,
Array,
Sort
};
struct Sort
{
Sort(Kind _kind):
kind(_kind) {}
virtual ~Sort() = default;
virtual bool operator==(Sort const& _other) const { return kind == _other.kind; }
Kind const kind;
};
using SortPointer = std::shared_ptr<Sort>;
struct FunctionSort: public Sort
{
FunctionSort(std::vector<SortPointer> _domain, SortPointer _codomain):
Sort(Kind::Function), domain(std::move(_domain)), codomain(std::move(_codomain)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherFunction = dynamic_cast<FunctionSort const*>(&_other);
solAssert(_otherFunction, "");
if (domain.size() != _otherFunction->domain.size())
return false;
if (!std::equal(
domain.begin(),
domain.end(),
_otherFunction->domain.begin(),
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
))
return false;
solAssert(codomain, "");
solAssert(_otherFunction->codomain, "");
return *codomain == *_otherFunction->codomain;
}
std::vector<SortPointer> domain;
SortPointer codomain;
};
struct ArraySort: public Sort
{
/// _domain is the sort of the indices
/// _range is the sort of the values
ArraySort(SortPointer _domain, SortPointer _range):
Sort(Kind::Array), domain(std::move(_domain)), range(std::move(_range)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherArray = dynamic_cast<ArraySort const*>(&_other);
solAssert(_otherArray, "");
solAssert(_otherArray->domain, "");
solAssert(_otherArray->range, "");
solAssert(domain, "");
solAssert(range, "");
return *domain == *_otherArray->domain && *range == *_otherArray->range;
}
SortPointer domain;
SortPointer range;
};
struct SortSort: public Sort
{
SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
solAssert(_otherSort, "");
solAssert(_otherSort->inner, "");
solAssert(inner, "");
return *inner == *_otherSort->inner;
}
SortPointer inner;
};
// Forward declaration.
SortPointer smtSort(Type const& _type);

View File

@ -0,0 +1,29 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/Sorts.h>
using namespace std;
namespace solidity::frontend::smt
{
shared_ptr<Sort> const SortProvider::boolSort{make_shared<Sort>(Kind::Bool)};
shared_ptr<Sort> const SortProvider::intSort{make_shared<Sort>(Kind::Int)};
}

125
libsolidity/formal/Sorts.h Normal file
View File

@ -0,0 +1,125 @@
/*
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 <liblangutil/Exceptions.h>
#include <libsolutil/Common.h>
#include <libsolutil/Exceptions.h>
#include <memory>
#include <vector>
namespace solidity::frontend::smt
{
enum class Kind
{
Int,
Bool,
Function,
Array,
Sort
};
struct Sort
{
Sort(Kind _kind):
kind(_kind) {}
virtual ~Sort() = default;
virtual bool operator==(Sort const& _other) const { return kind == _other.kind; }
Kind const kind;
};
using SortPointer = std::shared_ptr<Sort>;
struct FunctionSort: public Sort
{
FunctionSort(std::vector<SortPointer> _domain, SortPointer _codomain):
Sort(Kind::Function), domain(std::move(_domain)), codomain(std::move(_codomain)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherFunction = dynamic_cast<FunctionSort const*>(&_other);
solAssert(_otherFunction, "");
if (domain.size() != _otherFunction->domain.size())
return false;
if (!std::equal(
domain.begin(),
domain.end(),
_otherFunction->domain.begin(),
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
))
return false;
solAssert(codomain, "");
solAssert(_otherFunction->codomain, "");
return *codomain == *_otherFunction->codomain;
}
std::vector<SortPointer> domain;
SortPointer codomain;
};
struct ArraySort: public Sort
{
/// _domain is the sort of the indices
/// _range is the sort of the values
ArraySort(SortPointer _domain, SortPointer _range):
Sort(Kind::Array), domain(std::move(_domain)), range(std::move(_range)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherArray = dynamic_cast<ArraySort const*>(&_other);
solAssert(_otherArray, "");
solAssert(_otherArray->domain, "");
solAssert(_otherArray->range, "");
solAssert(domain, "");
solAssert(range, "");
return *domain == *_otherArray->domain && *range == *_otherArray->range;
}
SortPointer domain;
SortPointer range;
};
struct SortSort: public Sort
{
SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
solAssert(_otherSort, "");
solAssert(_otherSort->inner, "");
solAssert(inner, "");
return *inner == *_otherSort->inner;
}
SortPointer inner;
};
/** Frequently used sorts.*/
struct SortProvider
{
static std::shared_ptr<Sort> const boolSort;
static std::shared_ptr<Sort> const intSort;
};
}

View File

@ -0,0 +1,82 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/EncodingContext.h>
using namespace std;
using namespace solidity::frontend::smt;
SymbolicState::SymbolicState(EncodingContext& _context):
m_context(_context)
{
}
void SymbolicState::reset()
{
m_thisAddress.resetIndex();
m_balances.resetIndex();
}
// Blockchain
Expression SymbolicState::thisAddress()
{
return m_thisAddress.currentValue();
}
Expression SymbolicState::balance()
{
return balance(m_thisAddress.currentValue());
}
Expression SymbolicState::balance(Expression _address)
{
return Expression::select(m_balances.currentValue(), move(_address));
}
void SymbolicState::transfer(Expression _from, Expression _to, Expression _value)
{
unsigned indexBefore = m_balances.index();
addBalance(_from, 0 - _value);
addBalance(_to, move(_value));
unsigned indexAfter = m_balances.index();
solAssert(indexAfter > indexBefore, "");
m_balances.increaseIndex();
/// Do not apply the transfer operation if _from == _to.
auto newBalances = Expression::ite(
move(_from) == move(_to),
m_balances.valueAtIndex(indexBefore),
m_balances.valueAtIndex(indexAfter)
);
m_context.addAssertion(m_balances.currentValue() == newBalances);
}
/// Private helpers.
void SymbolicState::addBalance(Expression _address, Expression _value)
{
auto newBalances = Expression::store(
m_balances.currentValue(),
_address,
balance(_address) + move(_value)
);
m_balances.increaseIndex();
m_context.addAssertion(newBalances == m_balances.currentValue());
}

View File

@ -0,0 +1,71 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/formal/Sorts.h>
#include <libsolidity/formal/SolverInterface.h>
#include <libsolidity/formal/SymbolicVariables.h>
namespace solidity::frontend::smt
{
class EncodingContext;
/**
* Symbolic representation of the blockchain state.
*/
class SymbolicState
{
public:
SymbolicState(EncodingContext& _context);
void reset();
/// Blockchain.
//@{
/// Value of `this` address.
Expression thisAddress();
/// @returns the symbolic balance of address `this`.
Expression balance();
/// @returns the symbolic balance of an address.
Expression balance(Expression _address);
/// Transfer _value from _from to _to.
void transfer(Expression _from, Expression _to, Expression _value);
//@}
private:
/// Adds _value to _account's balance.
void addBalance(Expression _account, Expression _value);
EncodingContext& m_context;
/// Symbolic `this` address.
SymbolicAddressVariable m_thisAddress{
"this",
m_context
};
/// Symbolic balances.
SymbolicArrayVariable m_balances{
std::make_shared<ArraySort>(SortProvider::intSort, SortProvider::intSort),
"balances",
m_context
};
};
}

View File

@ -32,9 +32,9 @@ SortPointer smtSort(frontend::Type const& _type)
switch (smtKind(_type.category()))
{
case Kind::Int:
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
case Kind::Bool:
return make_shared<Sort>(Kind::Bool);
return SortProvider::boolSort;
case Kind::Function:
{
auto fType = dynamic_cast<frontend::FunctionType const*>(&_type);
@ -45,10 +45,10 @@ SortPointer smtSort(frontend::Type const& _type)
// 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<Sort>(Kind::Bool);
returnSort = SortProvider::boolSort;
else if (returnTypes.size() > 1)
// Abstract sort.
returnSort = make_shared<Sort>(Kind::Int);
returnSort = SortProvider::intSort;
else
returnSort = smtSort(*returnTypes.front());
return make_shared<FunctionSort>(parameterSorts, returnSort);
@ -65,20 +65,19 @@ SortPointer smtSort(frontend::Type const& _type)
{
auto stringLitType = dynamic_cast<frontend::StringLiteralType const*>(&_type);
solAssert(stringLitType, "");
auto intSort = make_shared<Sort>(Kind::Int);
return make_shared<ArraySort>(intSort, intSort);
return make_shared<ArraySort>(SortProvider::intSort, SortProvider::intSort);
}
else
{
solAssert(isArray(_type.category()), "");
auto arrayType = dynamic_cast<frontend::ArrayType const*>(&_type);
solAssert(arrayType, "");
return make_shared<ArraySort>(make_shared<Sort>(Kind::Int), smtSortAbstractFunction(*arrayType->baseType()));
return make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
}
}
default:
// Abstract case.
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
}
}
@ -93,7 +92,7 @@ vector<SortPointer> smtSort(vector<frontend::TypePointer> const& _types)
SortPointer smtSortAbstractFunction(frontend::Type const& _type)
{
if (isFunction(_type.category()))
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
return smtSort(_type);
}

View File

@ -218,15 +218,28 @@ SymbolicArrayVariable::SymbolicArrayVariable(
solAssert(isArray(m_type->category()), "");
}
SymbolicArrayVariable::SymbolicArrayVariable(
SortPointer _sort,
string _uniqueName,
EncodingContext& _context
):
SymbolicVariable(move(_sort), move(_uniqueName), _context)
{
solAssert(m_sort->kind == Kind::Array, "");
}
smt::Expression SymbolicArrayVariable::currentValue(frontend::TypePointer const& _targetType) const
{
if (_targetType)
{
solAssert(m_originalType, "");
// StringLiterals are encoded as SMT arrays in the generic case,
// but they can also be compared/assigned to fixed bytes, in which
// case they'd need to be encoded as numbers.
if (auto strType = dynamic_cast<StringLiteralType const*>(m_originalType))
if (_targetType->category() == frontend::Type::Category::FixedBytes)
return smt::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add)));
}
return SymbolicVariable::currentValue(_targetType);
}

View File

@ -47,6 +47,8 @@ public:
EncodingContext& _context
);
SymbolicVariable(SymbolicVariable&&) = default;
virtual ~SymbolicVariable() = default;
virtual Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const;
@ -212,6 +214,13 @@ public:
std::string _uniqueName,
EncodingContext& _context
);
SymbolicArrayVariable(
SortPointer _sort,
std::string _uniqueName,
EncodingContext& _context
);
SymbolicArrayVariable(SymbolicArrayVariable&&) = default;
Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const override;
};

View File

@ -35,6 +35,7 @@
#include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/analysis/ViewPureChecker.h>
#include <libsolidity/analysis/ImmutableValidator.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
@ -383,6 +384,15 @@ bool CompilerStack::analyze()
noErrors = false;
}
// Check that immutable variables are never read in c'tors and assigned
// exactly once
if (noErrors)
for (Source const* source: m_sourceOrder)
if (source->ast)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
ImmutableValidator(m_errorReporter, *contract).analyze();
if (noErrors)
{
// Control flow graph generator and analyzer. It can check for issues such as

View File

@ -234,6 +234,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
"wast", "wasm", "ewasm.wast", "ewasm.wasm",
"evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes",
"evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences",
"evm.deployedBytecode.immutableReferences",
"evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap",
"evm.bytecode.linkReferences",
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
@ -309,13 +310,36 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere
return ret;
}
Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const* _sourceMap)
Json::Value formatImmutableReferences(map<u256, pair<string, vector<size_t>>> const& _immutableReferences)
{
Json::Value ret(Json::objectValue);
for (auto const& immutableReference: _immutableReferences)
{
auto const& [identifier, byteOffsets] = immutableReference.second;
Json::Value array(Json::arrayValue);
for (size_t byteOffset: byteOffsets)
{
Json::Value byteRange(Json::objectValue);
byteRange["start"] = Json::UInt(byteOffset);
byteRange["length"] = Json::UInt(32); // immutable references are currently always 32 bytes wide
array.append(byteRange);
}
ret[identifier] = array;
}
return ret;
}
Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const* _sourceMap, bool _runtimeObject)
{
Json::Value output = Json::objectValue;
output["object"] = _object.toHex();
output["opcodes"] = evmasm::disassemble(_object.bytecode);
output["sourceMap"] = _sourceMap ? *_sourceMap : "";
output["linkReferences"] = formatLinkReferences(_object.linkReferences);
if (_runtimeObject)
output["immutableReferences"] = formatImmutableReferences(_object.immutableReferences);
return output;
}
@ -982,19 +1006,21 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
))
evmData["bytecode"] = collectEVMObject(
compilerStack.object(contractName),
compilerStack.sourceMapping(contractName)
compilerStack.sourceMapping(contractName),
false
);
if (compilationSuccess && isArtifactRequested(
_inputsAndSettings.outputSelection,
file,
name,
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" },
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", "evm.deployedBytecode.immutableReferences" },
wildcardMatchesExperimental
))
evmData["deployedBytecode"] = collectEVMObject(
compilerStack.runtimeObject(contractName),
compilerStack.runtimeSourceMapping(contractName)
compilerStack.runtimeSourceMapping(contractName),
true
);
if (!evmData.empty())
@ -1081,7 +1107,7 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
wildcardMatchesExperimental
))
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get());
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get(), false);
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();

View File

@ -203,6 +203,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
EthAssemblyAdapter adapter(assembly);
compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation);
object.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble());
yulAssert(object.bytecode->immutableReferences.empty(), "Leftover immutables.");
object.assembly = assembly.assemblyString();
object.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(

View File

@ -38,7 +38,7 @@ class EVMAssembly: public AbstractAssembly
{
public:
explicit EVMAssembly(bool _evm15 = false): m_evm15(_evm15) { }
virtual ~EVMAssembly() = default;
~EVMAssembly() override = default;
/// Set a new source location valid starting from the next instruction.
void setSourceLocation(langutil::SourceLocation const& _location) override;

View File

@ -92,10 +92,10 @@ public:
{}
public:
void operator()(Identifier const& _identifier);
void operator()(FunctionDefinition const&);
void operator()(ForLoop const&);
void operator()(Block const& _block);
void operator()(Identifier const& _identifier) override;
void operator()(FunctionDefinition const&) override;
void operator()(ForLoop const&) override;
void operator()(Block const& _block) override;
private:
void increaseRefIfFound(YulString _variableName);

View File

@ -45,7 +45,7 @@ class NoOutputAssembly: public AbstractAssembly
{
public:
explicit NoOutputAssembly(bool _evm15 = false): m_evm15(_evm15) { }
virtual ~NoOutputAssembly() = default;
~NoOutputAssembly() override = default;
void setSourceLocation(langutil::SourceLocation const&) override {}
int stackHeight() const override { return m_stackHeight; }

View File

@ -65,7 +65,7 @@ public:
class ASTCopier: public ExpressionCopier, public StatementCopier
{
public:
virtual ~ASTCopier() = default;
~ASTCopier() override = default;
Expression operator()(Literal const& _literal) override;
Expression operator()(Identifier const& _identifier) override;
Expression operator()(FunctionCall const&) override;

View File

@ -46,7 +46,7 @@ public:
static void run(OptimiserStepContext&, Block& _ast);
using ASTModifier::operator();
virtual void visit(Expression& _expression);
void visit(Expression& _expression) override;
private:
explicit ExpressionSimplifier(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {}

View File

@ -42,7 +42,7 @@ public:
static void run(OptimiserStepContext&, Block& _ast) { FunctionHoister{}(_ast); }
using ASTModifier::operator();
virtual void operator()(Block& _block);
void operator()(Block& _block) override;
private:
FunctionHoister() = default;

View File

@ -36,9 +36,9 @@ void NameCollector::operator()(VariableDeclaration const& _varDecl)
void NameCollector::operator ()(FunctionDefinition const& _funDef)
{
m_names.emplace(_funDef.name);
for (auto const arg: _funDef.parameters)
for (auto const& arg: _funDef.parameters)
m_names.emplace(arg.name);
for (auto const ret: _funDef.returnVariables)
for (auto const& ret: _funDef.returnVariables)
m_names.emplace(ret.name);
ASTWalker::operator ()(_funDef);
}

View File

@ -61,8 +61,8 @@ public:
{}
using ASTWalker::operator ();
virtual void operator()(Identifier const& _identifier);
virtual void operator()(FunctionCall const& _funCall);
void operator()(Identifier const& _identifier) override;
void operator()(FunctionCall const& _funCall) override;
static std::map<YulString, size_t> countReferences(Block const& _block, CountWhat _countWhat = VariablesAndFunctions);
static std::map<YulString, size_t> countReferences(FunctionDefinition const& _function, CountWhat _countWhat = VariablesAndFunctions);

View File

@ -104,7 +104,7 @@ public:
static bool containsMSize(Dialect const& _dialect, Block const& _ast);
using ASTWalker::operator();
void operator()(FunctionCall const& _funCall);
void operator()(FunctionCall const& _funCall) override;
private:
MSizeFinder(Dialect const& _dialect): m_dialect(_dialect) {}
@ -129,7 +129,7 @@ public:
}
using ASTWalker::operator();
void operator()(Leave const&) { m_leaveFound = true; }
void operator()(Leave const&) override { m_leaveFound = true; }
private:
LeaveFinder() = default;

View File

@ -50,12 +50,12 @@ public:
ASTWalker::operator()(_funDef);
auto& funType = functionTypes[_funDef.name];
for (auto const arg: _funDef.parameters)
for (auto const& arg: _funDef.parameters)
{
funType.parameters.emplace_back(arg.type);
variableTypes[arg.name] = arg.type;
}
for (auto const ret: _funDef.returnVariables)
for (auto const& ret: _funDef.returnVariables)
{
funType.returns.emplace_back(ret.type);
variableTypes[ret.name] = ret.type;

View File

@ -4,7 +4,7 @@
# first exporting a .sol file to JSON, then loading it into the compiler
# and exporting it again. The second JSON should be identical to the first
REPO_ROOT=$(realpath "$(dirname "$0")"/..)
REPO_ROOT=$(readlink -f "$(dirname "$0")"/..)
SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build}
SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py

View File

@ -32,6 +32,97 @@ SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build}
source "${REPO_ROOT}/scripts/common.sh"
source "${REPO_ROOT}/scripts/common_cmdline.sh"
function versionGreater()
{
v1=$1
v2=$2
ver1=( ${v1//./ } )
ver2=( ${v2//./ } )
if (( ${ver1[0]} > ${ver2[0]} ))
then
return 0
elif (( ${ver1[0]} == ${ver2[0]} )) && (( ${ver1[1]} > ${ver2[1]} ))
then
return 0
elif (( ${ver1[0]} == ${ver2[0]} )) && (( ${ver1[1]} == ${ver2[1]} )) && (( ${ver1[2]} > ${ver2[2]} ))
then
return 0
fi
return 1
}
function versionEqual()
{
if [ "$1" == "$2" ]
then
return 0
fi
return 1
}
function getAllAvailableVersions()
{
allVersions=()
local allListedVersions=( $(
wget -q -O- https://ethereum.github.io/solc-bin/bin/list.txt |
grep -Po '(?<=soljson-v)\d+.\d+.\d+(?=\+commit)' |
sort -V
) )
for listed in "${allListedVersions[@]}"
do
if versionGreater "$listed" "0.4.10"
then
allVersions+=( $listed )
fi
done
}
function findMinimalVersion()
{
local f=$1
local greater=false
local pragmaVersion
# Get minimum compiler version defined by pragma
if (grep -Po '(?<=pragma solidity >=)\d+.\d+.\d+' "$f" >/dev/null)
then
pragmaVersion="$(grep -Po '(?<=pragma solidity >=)\d+.\d+.\d+' "$f")"
sign=">="
elif (grep -Po '(?<=pragma solidity \^)\d+.\d+.\d+' "$f" >/dev/null)
then
pragmaVersion="$(grep -Po '(?<=pragma solidity \^)\d+.\d+.\d+' "$f")"
sign="^"
elif (grep -Po '(?<=pragma solidity >)\d+.\d+.\d+' "$f" >/dev/null)
then
pragmaVersion="$(grep -Po '(?<=pragma solidity >)\d+.\d+.\d+' "$f")"
sign=">"
greater=true;
else
printError "No valid pragma statement in file. Skipping..."
return
fi
version=""
for ver in "${allVersions[@]}"
do
if versionGreater "$ver" "$pragmaVersion"
then
minVersion="$ver"
break
elif ([ $greater == false ]) && versionEqual "$ver" "$pragmaVersion"
then
version="$ver"
break
fi
done
if [ -z version ]
then
printError "No release $sign$pragmaVersion was listed in available releases!"
fi
}
printTask "Verifying that all examples from the documentation have the correct version range..."
SOLTMPDIR=$(mktemp -d)
(
@ -39,6 +130,8 @@ SOLTMPDIR=$(mktemp -d)
cd "$SOLTMPDIR"
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/ docs
getAllAvailableVersions
for f in *.sol
do
# The contributors guide uses syntax tests, but we cannot
@ -61,27 +154,26 @@ SOLTMPDIR=$(mktemp -d)
# ignore warnings in this case
opts="$opts -o"
# Get minimum compiler version defined by pragma
if (grep -Po '(?<=pragma solidity >=)\d+.\d+.\d+' "$f" >/dev/null); then
version="$(grep -Po '(?<=pragma solidity >=)\d+.\d+.\d+' "$f")"
if (echo $version | grep -Po '(?<=0.4.)\d+' >/dev/null); then
patch=$(echo $version | grep -Po '(?<=0.4.)\d+')
if (( patch < 11 )); then
version="0.4.11" # first available release on github
fi
fi
elif (grep -Po '(?<=pragma solidity \^)\d+.\d+.\d+' "$f" >/dev/null); then
version="$(grep -Po '(?<=pragma solidity \^)\d+.\d+.\d+' "$f")"
findMinimalVersion $f
if [ -z "$version" ]
then
continue
fi
opts="$opts -v $version"
solc_bin="solc-$version"
echo "$solc_bin"
if [[ ! -f "$solc_bin" ]]; then
if [[ ! -f "$solc_bin" ]]
then
echo "Downloading release from github..."
wget https://github.com/ethereum/solidity/releases/download/v$version/solc-static-linux
mv solc-static-linux $solc_bin
if wget -q https://github.com/ethereum/solidity/releases/download/v$version/solc-static-linux >/dev/null
then
mv solc-static-linux $solc_bin
else
printError "No release $version was found on github!"
continue
fi
fi
ln -sf "$solc_bin" "solc"

View File

@ -34,6 +34,7 @@
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/GasEstimator.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/StorageLayout.h>
#include <libyul/AssemblyStack.h>
@ -147,6 +148,7 @@ static string const g_strOptimizeYul = "optimize-yul";
static string const g_strOutputDir = "output-dir";
static string const g_strOverwrite = "overwrite";
static string const g_strRevertStrings = "revert-strings";
static string const g_strStorageLayout = "storage-layout";
/// Possible arguments to for --revert-strings
static set<string> const g_revertStringsArgs
@ -207,6 +209,7 @@ static string const g_argOptimizeRuns = g_strOptimizeRuns;
static string const g_argOutputDir = g_strOutputDir;
static string const g_argSignatureHashes = g_strSignatureHashes;
static string const g_argStandardJSON = g_strStandardJSON;
static string const g_argStorageLayout = g_strStorageLayout;
static string const g_argStrictAssembly = g_strStrictAssembly;
static string const g_argVersion = g_strVersion;
static string const g_stdinFileName = g_stdinFileNameStr;
@ -231,7 +234,8 @@ static set<string> const g_combinedJsonArgs
g_strOpcodes,
g_strSignatureHashes,
g_strSrcMap,
g_strSrcMapRuntime
g_strSrcMapRuntime,
g_strStorageLayout
};
/// Possible arguments to for --machine
@ -293,7 +297,8 @@ static bool needsHumanTargetedStdout(po::variables_map const& _args)
g_argNatspecUser,
g_argNatspecDev,
g_argOpcodes,
g_argSignatureHashes
g_argSignatureHashes,
g_argStorageLayout
})
if (_args.count(arg))
return true;
@ -433,6 +438,18 @@ void CommandLineInterface::handleABI(string const& _contract)
sout() << "Contract JSON ABI" << endl << data << endl;
}
void CommandLineInterface::handleStorageLayout(string const& _contract)
{
if (!m_args.count(g_argStorageLayout))
return;
string data = jsonCompactPrint(m_compiler->storageLayout(_contract));
if (m_args.count(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contract) + "_storage.json", data);
else
sout() << "Contract Storage Layout:" << endl << data << endl;
}
void CommandLineInterface::handleNatspec(bool _natspecDev, string const& _contract)
{
std::string argName;
@ -833,7 +850,8 @@ Allowed options)",
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
(g_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.")
(g_argMetadata.c_str(), "Combined Metadata JSON whose Swarm hash is stored on-chain.");
(g_argMetadata.c_str(), "Combined Metadata JSON whose Swarm hash is stored on-chain.")
(g_argStorageLayout.c_str(), "Slots, offsets and types of the contract's state variables.");
desc.add(outputComponents);
po::options_description allOptions = desc;
@ -1276,6 +1294,8 @@ void CommandLineInterface::handleCombinedJSON()
contractData[g_strOpcodes] = evmasm::disassemble(m_compiler->object(contractName).bytecode);
if (requests.count(g_strAsm) && m_compiler->compilationSuccessful())
contractData[g_strAsm] = m_compiler->assemblyJSON(contractName);
if (requests.count(g_strStorageLayout) && m_compiler->compilationSuccessful())
contractData[g_strStorageLayout] = jsonCompactPrint(m_compiler->storageLayout(contractName));
if (requests.count(g_strSrcMap) && m_compiler->compilationSuccessful())
{
auto map = m_compiler->sourceMapping(contractName);
@ -1653,6 +1673,7 @@ void CommandLineInterface::outputCompilationResults()
handleSignatureHashes(contract);
handleMetadata(contract);
handleABI(contract);
handleStorageLayout(contract);
handleNatspec(true, contract);
handleNatspec(false, contract);
} // end of contracts iteration

View File

@ -74,6 +74,7 @@ private:
void handleNatspec(bool _natspecDev, std::string const& _contract);
void handleGasEstimation(std::string const& _contract);
void handleFormal();
void handleStorageLayout(std::string const& _contract);
/// Fills @a m_sourceCodes initially and @a m_redirects.
bool readInputFilesAndConfigureRemappings();

View File

@ -154,6 +154,7 @@ set(yul_phaser_sources
yulPhaser/Phaser.cpp
yulPhaser/Population.cpp
yulPhaser/Program.cpp
yulPhaser/ProgramCache.cpp
yulPhaser/Selections.cpp
yulPhaser/SimulationRNG.cpp
@ -170,6 +171,7 @@ set(yul_phaser_sources
../tools/yulPhaser/Phaser.cpp
../tools/yulPhaser/Population.cpp
../tools/yulPhaser/Program.cpp
../tools/yulPhaser/ProgramCache.cpp
../tools/yulPhaser/Selections.cpp
../tools/yulPhaser/SimulationRNG.cpp
)

View File

@ -187,7 +187,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] )
if (solidity::test::CommonOptions::get().disableSMT)
removeTestSuite("SMTChecker");
return 0;
return nullptr;
}
// BOOST_TEST_DYN_LINK should be defined if user want to link against shared boost test library

View File

@ -0,0 +1,18 @@
{
"language": "Solidity",
"sources": {
"a.sol": {
"content": "contract A { uint256 immutable x = 1; function f() public view returns (uint256) { return x; } }"
}
},
"settings": {
"evmVersion": "petersburg",
"outputSelection": {
"*": {
"A": [
"evm.deployedBytecode.immutableReferences"
]
}
}
}
}

View File

@ -0,0 +1,2 @@
{"contracts":{"a.sol":{"A":{"evm":{"deployedBytecode":{"immutableReferences":{"3":[{"length":32,"start":77}]},"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"0:96:0:-:0;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;0:96:0;;;;;;;;;;;;;;;;12:1:-1;9;2:12;38:56:0;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;72:7;90:1;83:8;;38:56;:::o"}}}}},"errors":[{"component":"general","formattedMessage":"a.sol: Warning: Source file does not specify required compiler version!
","message":"Source file does not specify required compiler version!","severity":"warning","sourceLocation":{"end":-1,"file":"a.sol","start":-1},"type":"Warning"}],"sources":{"a.sol":{"id":0}}}

View File

@ -20,6 +20,7 @@ object \"C_6\" {
function fun_f_5() {
}
}
@ -69,6 +70,7 @@ object \"C_6\" {
function fun_f_5() {
}
function shift_right_224_unsigned(value) -> newValue {

View File

@ -35,11 +35,18 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4_mpos {
let zero_value_for_type_t_string_memory_ptr_1_mpos := zero_value_for_split_t_string_memory_ptr()
vloc__4_mpos := zero_value_for_type_t_string_memory_ptr_1_mpos
vloc__4_mpos := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr()
leave
}
function zero_value_for_split_t_string_memory_ptr() -> ret {
ret := 96
}
}
object \"C_10_deployed\" {
code {
@ -131,6 +138,9 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4_mpos {
let zero_value_for_type_t_string_memory_ptr_1_mpos := zero_value_for_split_t_string_memory_ptr()
vloc__4_mpos := zero_value_for_type_t_string_memory_ptr_1_mpos
vloc__4_mpos := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr()
leave
@ -147,6 +157,10 @@ object \"C_10\" {
}
function zero_value_for_split_t_string_memory_ptr() -> ret {
ret := 96
}
}
}
}

View File

@ -23,11 +23,18 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
let zero_value_for_type_t_bytes32_1 := zero_value_for_split_t_bytes32()
vloc__4 := zero_value_for_type_t_bytes32_1
vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32()
leave
}
function zero_value_for_split_t_bytes32() -> ret {
ret := 0
}
}
object \"C_10_deployed\" {
code {
@ -88,6 +95,9 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
let zero_value_for_type_t_bytes32_1 := zero_value_for_split_t_bytes32()
vloc__4 := zero_value_for_type_t_bytes32_1
vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32()
leave
@ -100,6 +110,10 @@ object \"C_10\" {
}
function zero_value_for_split_t_bytes32() -> ret {
ret := 0
}
}
}
}

View File

@ -27,6 +27,9 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
let zero_value_for_type_t_bytes4_1 := zero_value_for_split_t_bytes4()
vloc__4 := zero_value_for_type_t_bytes4_1
let expr_6 := 0x61626364
vloc__4 := convert_t_rational_1633837924_by_1_to_t_bytes4(expr_6)
leave
@ -40,6 +43,10 @@ object \"C_10\" {
}
function zero_value_for_split_t_bytes4() -> ret {
ret := 0
}
}
object \"C_10_deployed\" {
code {
@ -104,6 +111,9 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
let zero_value_for_type_t_bytes4_1 := zero_value_for_split_t_bytes4()
vloc__4 := zero_value_for_type_t_bytes4_1
let expr_6 := 0x61626364
vloc__4 := convert_t_rational_1633837924_by_1_to_t_bytes4(expr_6)
leave
@ -124,6 +134,10 @@ object \"C_10\" {
}
function zero_value_for_split_t_bytes4() -> ret {
ret := 0
}
}
}
}

View File

@ -39,11 +39,18 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4_mpos {
let zero_value_for_type_t_string_memory_ptr_1_mpos := zero_value_for_split_t_string_memory_ptr()
vloc__4_mpos := zero_value_for_type_t_string_memory_ptr_1_mpos
vloc__4_mpos := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr()
leave
}
function zero_value_for_split_t_string_memory_ptr() -> ret {
ret := 96
}
}
object \"C_10_deployed\" {
code {
@ -139,6 +146,9 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4_mpos {
let zero_value_for_type_t_string_memory_ptr_1_mpos := zero_value_for_split_t_string_memory_ptr()
vloc__4_mpos := zero_value_for_type_t_string_memory_ptr_1_mpos
vloc__4_mpos := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr()
leave
@ -155,6 +165,10 @@ object \"C_10\" {
}
function zero_value_for_split_t_string_memory_ptr() -> ret {
ret := 96
}
}
}
}

View File

@ -27,6 +27,9 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
let zero_value_for_type_t_bytes4_1 := zero_value_for_split_t_bytes4()
vloc__4 := zero_value_for_type_t_bytes4_1
let expr_6 := 0xaabbccdd
vloc__4 := convert_t_rational_2864434397_by_1_to_t_bytes4(expr_6)
leave
@ -40,6 +43,10 @@ object \"C_10\" {
}
function zero_value_for_split_t_bytes4() -> ret {
ret := 0
}
}
object \"C_10_deployed\" {
code {
@ -104,6 +111,9 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
let zero_value_for_type_t_bytes4_1 := zero_value_for_split_t_bytes4()
vloc__4 := zero_value_for_type_t_bytes4_1
let expr_6 := 0xaabbccdd
vloc__4 := convert_t_rational_2864434397_by_1_to_t_bytes4(expr_6)
leave
@ -124,6 +134,10 @@ object \"C_10\" {
}
function zero_value_for_split_t_bytes4() -> ret {
ret := 0
}
}
}
}

View File

@ -61,6 +61,8 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
Assembly _subAsm;
auto sub_asm = make_shared<CharStream>("lorem ipsum", "sub.asm");
_subAsm.setSourceLocation({6, 8, sub_asm});
// PushImmutable
_subAsm.appendImmutable("someImmutable");
_subAsm.append(Instruction::INVALID);
shared_ptr<Assembly> _subAsmPtr = make_shared<Assembly>(_subAsm);
@ -86,6 +88,11 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
_assembly.pushSubroutineOffset(size_t(sub.data()));
// PushDeployTimeAddress
_assembly.append(PushDeployTimeAddress);
// AssignImmutable.
// Note that since there is no reference to "someOtherImmutable", this will compile to a simple POP in the hex output.
_assembly.appendImmutableAssignment("someOtherImmutable");
_assembly.append(u256(2));
_assembly.appendImmutableAssignment("someImmutable");
// Operation
_assembly.append(Instruction::STOP);
_assembly.appendAuxiliaryDataToEnd(bytes{0x42, 0x66});
@ -95,8 +102,11 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
BOOST_CHECK_EQUAL(
_assembly.assemble().toHex(),
"5b6001600220604673__$bf005014d9d0f534b8fcb268bd84c491a2$__"
"600056603e6001603d73000000000000000000000000000000000000000000fe"
"5b6001600220606f73__$bf005014d9d0f534b8fcb268bd84c491a2$__"
"60005660676022604573000000000000000000000000000000000000000050"
"60028060015250"
"00fe"
"7f0000000000000000000000000000000000000000000000000000000000000000"
"fe010203044266eeaa"
);
BOOST_CHECK_EQUAL(
@ -111,12 +121,16 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
" dataSize(sub_0)\n"
" dataOffset(sub_0)\n"
" deployTimeAddress()\n"
" assignImmutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n"
" 0x02\n"
" assignImmutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
" stop\n"
"stop\n"
"data_a6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b 01020304\n"
"\n"
"sub_0: assembly {\n"
" /* \"sub.asm\":6:8 */\n"
" immutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
" invalid\n"
"}\n"
"\n"
@ -138,9 +152,104 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
"{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSHDEPLOYADDRESS\",\"source\":0},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someOtherImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2\"},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"STOP\",\"source\":0}"
"],\".data\":{\"0\":{\".code\":[{\"begin\":6,\"end\":8,\"name\":\"INVALID\",\"source\":1}]},"
"\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\":\"01020304\"}}"
"],\".data\":{\"0\":{\".code\":["
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"},"
"{\"begin\":6,\"end\":8,\"name\":\"INVALID\",\"source\":1}"
"]},\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\":\"01020304\"}}"
);
}
BOOST_AUTO_TEST_CASE(immutable)
{
map<string, unsigned> indices = {
{ "root.asm", 0 },
{ "sub.asm", 1 }
};
Assembly _assembly;
auto root_asm = make_shared<CharStream>("lorem ipsum", "root.asm");
_assembly.setSourceLocation({1, 3, root_asm});
Assembly _subAsm;
auto sub_asm = make_shared<CharStream>("lorem ipsum", "sub.asm");
_subAsm.setSourceLocation({6, 8, sub_asm});
_subAsm.appendImmutable("someImmutable");
_subAsm.appendImmutable("someOtherImmutable");
_subAsm.appendImmutable("someImmutable");
shared_ptr<Assembly> _subAsmPtr = make_shared<Assembly>(_subAsm);
_assembly.append(u256(42));
_assembly.appendImmutableAssignment("someImmutable");
_assembly.append(u256(23));
_assembly.appendImmutableAssignment("someOtherImmutable");
auto sub = _assembly.appendSubroutine(_subAsmPtr);
_assembly.pushSubroutineOffset(size_t(sub.data()));
checkCompilation(_assembly);
BOOST_CHECK_EQUAL(
_assembly.assemble().toHex(),
// root.asm
// assign "someImmutable"
"602a" // PUSH1 42 - value for someImmutable
"80" // DUP1
"6001" // PUSH1 1 - offset of first someImmutable in sub_0
"52" // MSTORE
"80" // DUP1
"6043" // PUSH1 67 - offset of second someImmutable in sub_0
"52" // MSTORE
"50" // POP
// assign "someOtherImmutable"
"6017" // PUSH1 23 - value for someOtherImmutable
"80" // DUP1
"6022" // PUSH1 34 - offset of someOtherImmutable in sub_0
"52" // MSTORE
"50" // POP
"6063" // PUSH1 0x63 - dataSize(sub_0)
"6017" // PUSH1 0x17 - dataOffset(sub_0)
"fe" // INVALID
// end of root.asm
// sub.asm
"7f0000000000000000000000000000000000000000000000000000000000000000" // PUSHIMMUTABLE someImmutable - data at offset 1
"7f0000000000000000000000000000000000000000000000000000000000000000" // PUSHIMMUTABLE someOtherImmutable - data at offset 34
"7f0000000000000000000000000000000000000000000000000000000000000000" // PUSHIMMUTABLE someImmutable - data at offset 67
);
BOOST_CHECK_EQUAL(
_assembly.assemblyString(),
" /* \"root.asm\":1:3 */\n"
" 0x2a\n"
" assignImmutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
" 0x17\n"
" assignImmutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n"
" dataSize(sub_0)\n"
" dataOffset(sub_0)\n"
"stop\n"
"\n"
"sub_0: assembly {\n"
" /* \"sub.asm\":6:8 */\n"
" immutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
" immutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n"
" immutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
"}\n"
);
BOOST_CHECK_EQUAL(
util::jsonCompactPrint(_assembly.assemblyJSON(indices)),
"{\".code\":["
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2A\"},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"17\"},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someOtherImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}"
"],\".data\":{\"0\":{\".code\":["
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"},"
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someOtherImmutable\"},"
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"}"
"]}}}"
);
}

View File

@ -112,6 +112,44 @@ namespace
BOOST_AUTO_TEST_SUITE(Optimiser)
BOOST_AUTO_TEST_CASE(cse_push_immutable_same)
{
AssemblyItem pushImmutable{PushImmutable, 0x1234};
checkCSE({pushImmutable, pushImmutable}, {pushImmutable, Instruction::DUP1});
}
BOOST_AUTO_TEST_CASE(cse_push_immutable_different)
{
AssemblyItems input{{PushImmutable, 0x1234},{PushImmutable, 0xABCD}};
checkCSE(input, input);
}
BOOST_AUTO_TEST_CASE(cse_assign_immutable)
{
{
AssemblyItems input{u256(0x42), {AssignImmutable, 0x1234}};
checkCSE(input, input);
}
{
AssemblyItems input{{AssignImmutable, 0x1234}};
checkCSE(input, input);
}
}
BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks)
{
AssemblyItems input = addDummyLocations(AssemblyItems{
u256(0x42),
{AssignImmutable, 0x1234},
Instruction::ORIGIN
});
evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState()};
// Make sure CSE breaks after AssignImmutable.
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.begin() + 2);
}
BOOST_AUTO_TEST_CASE(cse_intermediate_swap)
{
evmasm::KnownState state;
@ -798,6 +836,68 @@ BOOST_AUTO_TEST_CASE(block_deduplicator)
BOOST_CHECK_EQUAL(pushTags.size(), 2);
}
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_same)
{
AssemblyItems blocks{
AssemblyItem(Tag, 1),
u256(42),
AssemblyItem{AssignImmutable, 0x1234},
Instruction::JUMP,
AssemblyItem(Tag, 2),
u256(42),
AssemblyItem{AssignImmutable, 0x1234},
Instruction::JUMP
};
AssemblyItems input = AssemblyItems{
AssemblyItem(PushTag, 2),
AssemblyItem(PushTag, 1),
} + blocks;
AssemblyItems output = AssemblyItems{
AssemblyItem(PushTag, 1),
AssemblyItem(PushTag, 1),
} + blocks;
BlockDeduplicator dedup(input);
dedup.deduplicate();
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
}
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_value)
{
AssemblyItems input{
AssemblyItem(PushTag, 2),
AssemblyItem(PushTag, 1),
AssemblyItem(Tag, 1),
u256(42),
AssemblyItem{AssignImmutable, 0x1234},
Instruction::JUMP,
AssemblyItem(Tag, 2),
u256(23),
AssemblyItem{AssignImmutable, 0x1234},
Instruction::JUMP
};
BlockDeduplicator dedup(input);
BOOST_CHECK(!dedup.deduplicate());
}
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_hash)
{
AssemblyItems input{
AssemblyItem(PushTag, 2),
AssemblyItem(PushTag, 1),
AssemblyItem(Tag, 1),
u256(42),
AssemblyItem{AssignImmutable, 0x1234},
Instruction::JUMP,
AssemblyItem(Tag, 2),
u256(42),
AssemblyItem{AssignImmutable, 0xABCD},
Instruction::JUMP
};
BlockDeduplicator dedup(input);
BOOST_CHECK(!dedup.deduplicate());
}
BOOST_AUTO_TEST_CASE(block_deduplicator_loops)
{
AssemblyItems input{

View File

@ -34,14 +34,14 @@ namespace solidity::frontend::test
class SMTCheckerFramework: public AnalysisFramework
{
protected:
virtual std::pair<SourceUnit const*, ErrorList>
std::pair<SourceUnit const*, ErrorList>
parseAnalyseAndReturnError(
std::string const& _source,
bool _reportWarnings = false,
bool _insertVersionPragma = true,
bool _allowMultipleErrors = false,
bool _allowRecoveryErrors = false
)
) override
{
return AnalysisFramework::parseAnalyseAndReturnError(
"pragma experimental SMTChecker;\n" + _source,

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,7 @@ public:
ExecutionFramework(_evmVersion), m_showMetadata(solidity::test::CommonOptions::get().showMetadata)
{}
virtual bytes const& compileAndRunWithoutCheck(
bytes const& compileAndRunWithoutCheck(
std::string const& _sourceCode,
u256 const& _value = 0,
std::string const& _contractName = "",

View File

@ -52,15 +52,15 @@ public:
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); }
Expression* expression() const { return m_expression; }
private:
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); }
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); }
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); }
bool visit(Assignment& _expression) override { return checkExpression(_expression); }
bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
bool visit(Identifier& _expression) override { return checkExpression(_expression); }
bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
bool visit(Literal& _expression) override { return checkExpression(_expression); }
bool checkExpression(Expression& _expression)
{
if (m_expression == nullptr)
@ -119,13 +119,9 @@ bytes compileFirstExpression(
NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), scopes, errorReporter);
resolver.registerDeclarations(*sourceUnit);
vector<ContractDefinition const*> inheritanceHierarchy;
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
BOOST_REQUIRE_MESSAGE(resolver.resolveNamesAndTypes(*contract), "Resolving names failed");
inheritanceHierarchy = vector<ContractDefinition const*>(1, contract);
}
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
@ -144,7 +140,7 @@ bytes compileFirstExpression(
RevertStrings::Default
);
context.resetVisitedNodes(contract);
context.setInheritanceHierarchy(inheritanceHierarchy);
context.setMostDerivedContract(*contract);
unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack
context.adjustStackOffset(parametersSize);
for (vector<string> const& variable: _localVariables)

View File

@ -672,7 +672,7 @@ BOOST_AUTO_TEST_CASE(inline_asm_end_location)
{
public:
bool visited = false;
virtual bool visit(InlineAssembly const& _inlineAsm)
bool visit(InlineAssembly const& _inlineAsm) override
{
auto loc = _inlineAsm.location();
auto asmStr = loc.source->source().substr(loc.start, loc.end - loc.start);

View File

@ -0,0 +1,11 @@
contract C {
function f(bytes calldata data)
external
pure
returns (uint256, bytes memory r)
{
return abi.decode(data, (uint256, bytes));
}
}
// ----
// f(bytes): 0x20, 0x80, 0x21, 0x40, 0x7, "abcdefg" -> 0x21, 0x40, 0x7, "abcdefg"

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