mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
commit
51883958ab
@ -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
|
||||
|
@ -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)
|
||||
|
28
Changelog.md
28
Changelog.md
@ -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)
|
||||
|
@ -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
52
SECURITY.md
Normal 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
|
@ -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
|
||||
|
@ -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.",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
193
docs/grammar.txt
193
docs/grammar.txt
@ -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 )*
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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, "");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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:
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
220
libsolidity/analysis/ImmutableValidator.cpp
Normal file
220
libsolidity/analysis/ImmutableValidator.cpp
Normal 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);
|
||||
}
|
78
libsolidity/analysis/ImmutableValidator.h
Normal file
78
libsolidity/analysis/ImmutableValidator.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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); }
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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>>();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
29
libsolidity/formal/Sorts.cpp
Normal file
29
libsolidity/formal/Sorts.cpp
Normal 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
125
libsolidity/formal/Sorts.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
82
libsolidity/formal/SymbolicState.cpp
Normal file
82
libsolidity/formal/SymbolicState.cpp
Normal 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());
|
||||
}
|
||||
|
71
libsolidity/formal/SymbolicState.h
Normal file
71
libsolidity/formal/SymbolicState.h
Normal 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
|
||||
};
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
|
@ -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) {}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
18
test/cmdlineTests/standard_immutable_references/input.json
Normal file
18
test/cmdlineTests/standard_immutable_references/input.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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}}}
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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\"}"
|
||||
"]}}}"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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
@ -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 = "",
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user