mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6381 from ethereum/develop
Merge develop into release for 0.5.7
This commit is contained in:
commit
6da8b019e4
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.5.6")
|
||||
set(PROJECT_VERSION "0.5.7")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
|
||||
|
||||
option(LLL "Build LLL" OFF)
|
||||
|
34
Changelog.md
34
Changelog.md
@ -1,3 +1,35 @@
|
||||
### 0.5.7 (2019-03-26)
|
||||
|
||||
Important Bugfixes:
|
||||
* ABIEncoderV2: Fix bugs related to loading short value types from storage when encoding an array or struct from storage.
|
||||
* ABIEncoderV2: Fix buffer overflow problem when encoding packed array from storage.
|
||||
* Optimizer: Fix wrong ordering of arguments in byte optimization rule for constants.
|
||||
|
||||
|
||||
Language Features:
|
||||
* Function calls with named arguments now work with overloaded functions.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does).
|
||||
* Standard JSON Interface: Support "Yul" as input language.
|
||||
* SMTChecker: Show callstack together with model if applicable.
|
||||
* SMTChecker: Support modifiers.
|
||||
* Yul Optimizer: Enable stack allocation optimization by default if Yul optimizer is active (disable in ``yulDetails``).
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Code Generator: Defensively pad memory for ``type(Contract).name`` to multiples of 32.
|
||||
* Type System: Detect and disallow internal function pointers as parameters for public/external library functions, even when they are nested/wrapped in structs, arrays or other types.
|
||||
* Yul Optimizer: Properly determine whether a variable can be eliminated during stack compression pass.
|
||||
* Yul / Inline Assembly Parser: Disallow more than one case statement with the same label inside a switch based on the label's integer value.
|
||||
|
||||
|
||||
Build System:
|
||||
* Install scripts: Fix boost repository URL for CentOS 6.
|
||||
* Soltest: Fix hex string update in soltest.
|
||||
|
||||
|
||||
### 0.5.6 (2019-03-13)
|
||||
|
||||
Important Bugfixes:
|
||||
@ -29,6 +61,7 @@ Bugfixes:
|
||||
Build System:
|
||||
* Soltest: Add support for arrays in function signatures.
|
||||
* Soltest: Add support for struct arrays in function signatures.
|
||||
* Soltest: Add support for left-aligned, unpadded hex string literals.
|
||||
|
||||
### 0.5.5 (2019-03-05)
|
||||
|
||||
@ -70,7 +103,6 @@ Bugfixes:
|
||||
|
||||
Build System:
|
||||
* Soltest: Add support for left-aligned, padded hex literals.
|
||||
* Soltest: Add support for left-aligned, unpadded hex string literals.
|
||||
* Soltest: Add support for right-aligned, padded boolean literals.
|
||||
|
||||
### 0.5.4 (2019-02-12)
|
||||
|
@ -22,11 +22,10 @@
|
||||
- [ ] Thank voluntary contributors in the Github release page (use ``git shortlog -s -n -e origin/release..origin/develop``).
|
||||
- [ ] Create a pull request from ``develop`` to ``release``, wait for the tests, then merge it.
|
||||
- [ ] Make a final check that there are no platform-dependency issues in the ``solidity-test-bytecode`` repository.
|
||||
- [ ] Wait for the tests for the commit on ``release``, create a release in Github, creating the tag.
|
||||
- [ ] Wait for the tests for the commit on ``release``, create a release in Github, creating the tag (click the `PUBLISH RELEASE` button on the release page.)
|
||||
- [ ] Wait for the CI runs on the tag itself (travis and appveyor should push artifacts onto the Github release page).
|
||||
- [ ] Run ``scripts/create_source_tarball.sh`` while being on the tag to create the source tarball. Make sure to create ``prerelease.txt`` before: (``echo -n > prerelease.txt``). This will create the tarball in a directory called ``upload``.
|
||||
- [ ] Take the tarball from the upload directory (its name should be ``solidity_x.x.x.tar.gz``, otherwise ``prerelease.txt`` was missing in the step before) and upload the source tarball to the release page.
|
||||
- [ ] Click the `PUBLISH RELEASE` button on the release page.
|
||||
|
||||
### PPA
|
||||
- [ ] Change ``scripts/release_ppa.sh`` to match your key's email and key id.
|
||||
|
@ -1,4 +1,26 @@
|
||||
[
|
||||
{
|
||||
"name": "ABIEncoderV2PackedStorage",
|
||||
"summary": "Storage structs and arrays with types shorter than 32 bytes can cause data corruption if encoded directly from storage using the experimental ABIEncoderV2.",
|
||||
"description": "Elements of structs and arrays that are shorter than 32 bytes are not properly decoded from storage when encoded directly (i.e. not via a memory type) using ABIEncoderV2. This can cause corruption in the values themselves but can also overwrite other parts of the encoded data.",
|
||||
"introduced": "0.4.19",
|
||||
"fixed": "0.5.7",
|
||||
"severity": "low",
|
||||
"conditions": {
|
||||
"ABIEncoderV2": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "IncorrectByteInstructionOptimization",
|
||||
"summary": "The optimizer incorrectly handles byte opcodes whose second argument is 31 or a constant expression that evaluates to 31. This can result in unexpected values.",
|
||||
"description": "The optimizer incorrectly handles byte opcodes that use the constant 31 as second argument. This can happen when performing index access on bytesNN types with a compile-time constant value (not index) of 31 or when using the byte opcode in inline assembly.",
|
||||
"introduced": "0.5.5",
|
||||
"fixed": "0.5.7",
|
||||
"severity": "very low",
|
||||
"conditions": {
|
||||
"optimizer": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DoubleShiftSizeOverflow",
|
||||
"summary": "Double bitwise shifts by large constants whose sum overflows 256 bits can result in unexpected values.",
|
||||
|
@ -456,6 +456,7 @@
|
||||
},
|
||||
"0.4.19": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder"
|
||||
@ -479,6 +480,7 @@
|
||||
},
|
||||
"0.4.20": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder"
|
||||
@ -487,6 +489,7 @@
|
||||
},
|
||||
"0.4.21": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder"
|
||||
@ -495,6 +498,7 @@
|
||||
},
|
||||
"0.4.22": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"OneOfTwoConstructorsSkipped"
|
||||
@ -503,6 +507,7 @@
|
||||
},
|
||||
"0.4.23": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData"
|
||||
],
|
||||
@ -510,13 +515,16 @@
|
||||
},
|
||||
"0.4.24": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData"
|
||||
],
|
||||
"released": "2018-05-16"
|
||||
},
|
||||
"0.4.25": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2018-09-12"
|
||||
},
|
||||
"0.4.3": {
|
||||
@ -610,33 +618,52 @@
|
||||
"released": "2017-01-31"
|
||||
},
|
||||
"0.5.0": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2018-11-13"
|
||||
},
|
||||
"0.5.1": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2018-12-03"
|
||||
},
|
||||
"0.5.2": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2018-12-19"
|
||||
},
|
||||
"0.5.3": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2019-01-22"
|
||||
},
|
||||
"0.5.4": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2019-02-12"
|
||||
},
|
||||
"0.5.5": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"IncorrectByteInstructionOptimization",
|
||||
"DoubleShiftSizeOverflow"
|
||||
],
|
||||
"released": "2019-03-05"
|
||||
},
|
||||
"0.5.6": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"IncorrectByteInstructionOptimization"
|
||||
],
|
||||
"released": "2019-03-13"
|
||||
},
|
||||
"0.5.7": {
|
||||
"bugs": [],
|
||||
"released": "2019-03-26"
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ In this tutorial, we will sign messages in the browser using `web3.js <https://g
|
||||
as it provides a number of other security benefits.
|
||||
|
||||
::
|
||||
|
||||
/// Hashing first makes things easier
|
||||
var hash = web3.utils.sha3("message to sign");
|
||||
web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); });
|
||||
|
@ -126,6 +126,8 @@ currently not supported.
|
||||
brew tap ethereum/ethereum
|
||||
brew install solidity
|
||||
|
||||
To install the most recent 0.4.x version of Solidity you can also use ``brew install solidity@4``.
|
||||
|
||||
If you need a specific version of Solidity you can install a
|
||||
Homebrew formula directly from Github.
|
||||
|
||||
@ -140,7 +142,7 @@ Install it using ``brew``:
|
||||
.. code-block:: bash
|
||||
|
||||
brew unlink solidity
|
||||
# Install 0.4.8
|
||||
# eg. Install 0.4.8
|
||||
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
|
||||
|
||||
Gentoo Linux also provides a solidity package that can be installed using ``emerge``:
|
||||
|
@ -12,8 +12,8 @@ Let us begin with a basic example that sets the value of a variable and exposes
|
||||
it for other contracts to access. It is fine if you do not understand
|
||||
everything right now, we will go into more detail later.
|
||||
|
||||
Storage
|
||||
=======
|
||||
Storage Example
|
||||
===============
|
||||
|
||||
::
|
||||
|
||||
@ -33,7 +33,7 @@ Storage
|
||||
|
||||
The first line simply tells that the source code is written for
|
||||
Solidity version 0.4.0 or anything newer that does not break functionality
|
||||
(up to, but not including, version 0.6.0). This is to ensure that the
|
||||
(up to, but not including, version 0.7.0). This is to ensure that the
|
||||
contract is not compilable with a new (breaking) compiler version, where it could behave differently.
|
||||
:ref:`Pragmas<pragma>` are common instructions for compilers about how to treat the
|
||||
source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_).
|
||||
@ -387,8 +387,10 @@ paragraphs.
|
||||
Each account has a data area called **storage**, which is persistent between function calls
|
||||
and transactions.
|
||||
Storage is a key-value store that maps 256-bit words to 256-bit words.
|
||||
It is not possible to enumerate storage from within a contract and it is
|
||||
comparatively costly to read, and even more to modify storage.
|
||||
It is not possible to enumerate storage from within a contract, it is
|
||||
comparatively costly to read, and even more to initialise and modify storage. Because of this cost,
|
||||
you should minimize what you store in persistent storage to what the contract needs to run.
|
||||
Store data like derived calculations, caching, and aggregates outside of the contract.
|
||||
A contract can neither read nor write to any storage apart from its own.
|
||||
|
||||
The second data area is called **memory**, of which a contract obtains
|
||||
|
@ -336,12 +336,12 @@ The following is the order of precedence for operators, listed in order of evalu
|
||||
| *13* | Logical OR | ``||`` |
|
||||
+------------+-------------------------------------+--------------------------------------------+
|
||||
| *14* | Ternary operator | ``<conditional> ? <if-true> : <if-false>`` |
|
||||
+------------+-------------------------------------+--------------------------------------------+
|
||||
| *15* | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
|
||||
+ +-------------------------------------+--------------------------------------------+
|
||||
| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
|
||||
| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, |
|
||||
| | | ``%=`` |
|
||||
+------------+-------------------------------------+--------------------------------------------+
|
||||
| *16* | Comma operator | ``,`` |
|
||||
| *15* | Comma operator | ``,`` |
|
||||
+------------+-------------------------------------+--------------------------------------------+
|
||||
|
||||
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
|
||||
|
@ -27,12 +27,15 @@ Solidity Integrations
|
||||
* `Remix <https://remix.ethereum.org/>`_
|
||||
Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components.
|
||||
|
||||
* `Solium <https://github.com/duaraghav8/Solium/>`_
|
||||
Linter to identify and fix style and security issues in Solidity.
|
||||
|
||||
* `Solhint <https://github.com/protofire/solhint>`_
|
||||
Solidity linter that provides security, style guide and best practice rules for smart contract validation.
|
||||
|
||||
* `Solidity IDE <https://github.com/System-Glitch/Solidity-IDE>`_
|
||||
Browser-based IDE with integrated compiler, Ganache and local file system support.
|
||||
|
||||
* `Solium <https://github.com/duaraghav8/Solium/>`_
|
||||
Linter to identify and fix style and security issues in Solidity.
|
||||
|
||||
* `Superblocks Lab <https://lab.superblocks.com/>`_
|
||||
Browser-based IDE. Built-in browser-based VM and Metamask integration (one click deployment to Testnet/Mainnet).
|
||||
|
||||
@ -121,6 +124,9 @@ Solidity Tools
|
||||
* `EVM Lab <https://github.com/ethereum/evmlab/>`_
|
||||
Rich tool package to interact with the EVM. Includes a VM, Etherchain API, and a trace-viewer with gas cost display.
|
||||
|
||||
* `Universal Mutator <https://github.com/agroce/universalmutator>`_
|
||||
A tool for mutation generation, with configurable rules and support for Solidity and Vyper.
|
||||
|
||||
.. note::
|
||||
Information like variable names, comments, and source code formatting is lost in the compilation process and it is not possible to completely recover the original source code. Decompiling smart contracts to view the original source code might not be possible, or the end result that useful.
|
||||
|
||||
|
@ -147,7 +147,7 @@ Input Description
|
||||
.. code-block:: none
|
||||
|
||||
{
|
||||
// Required: Source code language, such as "Solidity", "Vyper", "lll", "assembly", etc.
|
||||
// Required: Source code language. Currently supported are "Solidity" and "Yul".
|
||||
"language": "Solidity",
|
||||
// Required
|
||||
"sources":
|
||||
@ -216,8 +216,12 @@ Input Description
|
||||
// It can only be activated through the details here.
|
||||
// This feature is still considered experimental.
|
||||
"yul": false,
|
||||
// Future tuning options, currently unused.
|
||||
"yulDetails": {}
|
||||
// Tuning options for the Yul optimizer.
|
||||
"yulDetails": {
|
||||
// Improve allocation of stack slots for variables, can free up stack slots early.
|
||||
// Activated by default if the Yul optimizer is activated.
|
||||
"stackAllocation": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"evmVersion": "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg
|
||||
@ -259,8 +263,9 @@ Input Description
|
||||
// devdoc - Developer documentation (natspec)
|
||||
// userdoc - User documentation (natspec)
|
||||
// metadata - Metadata
|
||||
// ir - New assembly format before desugaring
|
||||
// evm.assembly - New assembly format after desugaring
|
||||
// ir - Yul intermediate representation of the code before optimization
|
||||
// irOptimized - Intermediate representation after optimization
|
||||
// evm.assembly - New assembly format
|
||||
// evm.legacyAssembly - Old-style assembly format in JSON
|
||||
// evm.bytecode.object - Bytecode object
|
||||
// evm.bytecode.opcodes - Opcodes list
|
||||
@ -269,8 +274,8 @@ Input Description
|
||||
// evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode)
|
||||
// evm.methodIdentifiers - The list of function hashes
|
||||
// evm.gasEstimates - Function gas estimates
|
||||
// ewasm.wast - eWASM S-expressions format (not supported atm)
|
||||
// ewasm.wasm - eWASM binary format (not supported atm)
|
||||
// ewasm.wast - eWASM S-expressions format (not supported at the moment)
|
||||
// ewasm.wasm - eWASM binary format (not supported at the moment)
|
||||
//
|
||||
// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
|
||||
// target part of that output. Additionally, `*` can be used as a wildcard to request everything.
|
||||
|
131
docs/yul.rst
131
docs/yul.rst
@ -15,6 +15,23 @@ It can already be used for "inline assembly" inside Solidity and
|
||||
future versions of the Solidity compiler will even use Yul as intermediate
|
||||
language. It should also be easy to build high-level optimizer stages for Yul.
|
||||
|
||||
In its flavour of inline-assembly, Yul can be used as a language setting
|
||||
for the :ref:`standard-json interface <compiler-api>`:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"language": "Yul",
|
||||
"sources": { "input.yul": { "content": "{ sstore(0, 1) }" } },
|
||||
"settings": {
|
||||
"outputSelection": { "*": { "*": ["*"], "": [ "*" ] } },
|
||||
"optimizer": { "enabled": true, "details": { "yul": true } }
|
||||
}
|
||||
}
|
||||
|
||||
Furthermore, the commandline interface can be switched to Yul mode
|
||||
using ``solc --strict-assembly``.
|
||||
|
||||
.. note::
|
||||
|
||||
Note that the flavour used for "inline assembly" does not have types
|
||||
@ -334,83 +351,83 @@ The following functions must be available:
|
||||
+---------------------------------------------------------------------------------------------------------------+
|
||||
| *Logic* |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| not(x:bool) -> z:bool | logical not |
|
||||
| not(x:bool) ‑> z:bool | logical not |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| and(x:bool, y:bool) -> z:bool | logical and |
|
||||
| and(x:bool, y:bool) ‑> z:bool | logical and |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| or(x:bool, y:bool) -> z:bool | logical or |
|
||||
| or(x:bool, y:bool) ‑> z:bool | logical or |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| xor(x:bool, y:bool) -> z:bool | xor |
|
||||
| xor(x:bool, y:bool) ‑> z:bool | xor |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| *Arithmetic* |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| addu256(x:u256, y:u256) -> z:u256 | x + y |
|
||||
| addu256(x:u256, y:u256) ‑> z:u256 | x + y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| subu256(x:u256, y:u256) -> z:u256 | x - y |
|
||||
| subu256(x:u256, y:u256) ‑> z:u256 | x - y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| mulu256(x:u256, y:u256) -> z:u256 | x * y |
|
||||
| mulu256(x:u256, y:u256) ‑> z:u256 | x * y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| divu256(x:u256, y:u256) -> z:u256 | x / y |
|
||||
| divu256(x:u256, y:u256) ‑> z:u256 | x / y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| divs256(x:s256, y:s256) -> z:s256 | x / y, for signed numbers in two's complement |
|
||||
| divs256(x:s256, y:s256) ‑> z:s256 | x / y, for signed numbers in two's complement |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| modu256(x:u256, y:u256) -> z:u256 | x % y |
|
||||
| modu256(x:u256, y:u256) ‑> z:u256 | x % y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| mods256(x:s256, y:s256) -> z:s256 | x % y, for signed numbers in two's complement |
|
||||
| mods256(x:s256, y:s256) ‑> z:s256 | x % y, for signed numbers in two's complement |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| signextendu256(i:u256, x:u256) -> z:u256 | sign extend from (i*8+7)th bit counting from least significant |
|
||||
| signextendu256(i:u256, x:u256) ‑> z:u256 | sign extend from (i*8+7)th bit counting from least significant |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| expu256(x:u256, y:u256) -> z:u256 | x to the power of y |
|
||||
| expu256(x:u256, y:u256) ‑> z:u256 | x to the power of y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| addmodu256(x:u256, y:u256, m:u256) -> z:u256| (x + y) % m with arbitrary precision arithmetic |
|
||||
| addmodu256(x:u256, y:u256, m:u256) ‑> z:u256| (x + y) % m with arbitrary precision arithmetic |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| mulmodu256(x:u256, y:u256, m:u256) -> z:u256| (x * y) % m with arbitrary precision arithmetic |
|
||||
| mulmodu256(x:u256, y:u256, m:u256) ‑> z:u256| (x * y) % m with arbitrary precision arithmetic |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| ltu256(x:u256, y:u256) -> z:bool | true if x < y, false otherwise |
|
||||
| ltu256(x:u256, y:u256) ‑> z:bool | true if x < y, false otherwise |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| gtu256(x:u256, y:u256) -> z:bool | true if x > y, false otherwise |
|
||||
| gtu256(x:u256, y:u256) ‑> z:bool | true if x > y, false otherwise |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| lts256(x:s256, y:s256) -> z:bool | true if x < y, false otherwise |
|
||||
| lts256(x:s256, y:s256) ‑> z:bool | true if x < y, false otherwise |
|
||||
| | (for signed numbers in two's complement) |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| gts256(x:s256, y:s256) -> z:bool | true if x > y, false otherwise |
|
||||
| gts256(x:s256, y:s256) ‑> z:bool | true if x > y, false otherwise |
|
||||
| | (for signed numbers in two's complement) |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| equ256(x:u256, y:u256) -> z:bool | true if x == y, false otherwise |
|
||||
| equ256(x:u256, y:u256) ‑> z:bool | true if x == y, false otherwise |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| iszerou256(x:u256) -> z:bool | true if x == 0, false otherwise |
|
||||
| iszerou256(x:u256) ‑> z:bool | true if x == 0, false otherwise |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| notu256(x:u256) -> z:u256 | ~x, every bit of x is negated |
|
||||
| notu256(x:u256) ‑> z:u256 | ~x, every bit of x is negated |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| andu256(x:u256, y:u256) -> z:u256 | bitwise and of x and y |
|
||||
| andu256(x:u256, y:u256) ‑> z:u256 | bitwise and of x and y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| oru256(x:u256, y:u256) -> z:u256 | bitwise or of x and y |
|
||||
| oru256(x:u256, y:u256) ‑> z:u256 | bitwise or of x and y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| xoru256(x:u256, y:u256) -> z:u256 | bitwise xor of x and y |
|
||||
| xoru256(x:u256, y:u256) ‑> z:u256 | bitwise xor of x and y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| shlu256(x:u256, y:u256) -> z:u256 | logical left shift of x by y |
|
||||
| shlu256(x:u256, y:u256) ‑> z:u256 | logical left shift of x by y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| shru256(x:u256, y:u256) -> z:u256 | logical right shift of x by y |
|
||||
| shru256(x:u256, y:u256) ‑> z:u256 | logical right shift of x by y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| sars256(x:s256, y:u256) -> z:u256 | arithmetic right shift of x by y |
|
||||
| sars256(x:s256, y:u256) ‑> z:u256 | arithmetic right shift of x by y |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| byte(n:u256, x:u256) -> v:u256 | nth byte of x, where the most significant byte is the 0th byte |
|
||||
| byte(n:u256, x:u256) ‑> v:u256 | nth byte of x, where the most significant byte is the 0th byte |
|
||||
| | Cannot this be just replaced by and256(shr256(n, x), 0xff) and |
|
||||
| | let it be optimised out by the EVM backend? |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| *Memory and storage* |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| mload(p:u256) -> v:u256 | mem[p..(p+32)) |
|
||||
| mload(p:u256) ‑> v:u256 | mem[p..(p+32)) |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| mstore(p:u256, v:u256) | mem[p..(p+32)) := v |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| mstore8(p:u256, v:u256) | mem[p] := v & 0xff - only modifies a single byte |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| sload(p:u256) -> v:u256 | storage[p] |
|
||||
| sload(p:u256) ‑> v:u256 | storage[p] |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| sstore(p:u256, v:u256) | storage[p] := v |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| msize() -> size:u256 | size of memory, i.e. largest accessed memory index, albeit due |
|
||||
| msize() ‑> size:u256 | size of memory, i.e. largest accessed memory index, albeit due |
|
||||
| | due to the memory extension function, which extends by words, |
|
||||
| | this will always be a multiple of 32 bytes |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
@ -428,15 +445,15 @@ The following functions must be available:
|
||||
| call(g:u256, a:u256, v:u256, in:u256, | call contract at address a with input mem[in..(in+insize)) |
|
||||
| insize:u256, out:u256, | providing g gas and v wei and output area |
|
||||
| outsize:u256) | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) |
|
||||
| -> r:u256 | and 1 on success |
|
||||
| ‑> r:u256 | and 1 on success |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| callcode(g:u256, a:u256, v:u256, in:u256, | identical to ``call`` but only use the code from a |
|
||||
| insize:u256, out:u256, | and stay in the context of the |
|
||||
| outsize:u256) -> r:u256 | current contract otherwise |
|
||||
| outsize:u256) ‑> r:u256 | current contract otherwise |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| delegatecall(g:u256, a:u256, in:u256, | identical to ``callcode``, |
|
||||
| insize:u256, out:u256, | but also keep ``caller`` |
|
||||
| outsize:u256) -> r:u256 | and ``callvalue`` |
|
||||
| outsize:u256) ‑> r:u256 | and ``callvalue`` |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| abort() | abort (equals to invalid instruction on EVM) |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
@ -460,43 +477,43 @@ The following functions must be available:
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| *State queries* |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| blockcoinbase() -> address:u256 | current mining beneficiary |
|
||||
| blockcoinbase() ‑> address:u256 | current mining beneficiary |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| blockdifficulty() -> difficulty:u256 | difficulty of the current block |
|
||||
| blockdifficulty() ‑> difficulty:u256 | difficulty of the current block |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| blockgaslimit() -> limit:u256 | block gas limit of the current block |
|
||||
| blockgaslimit() ‑> limit:u256 | block gas limit of the current block |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| blockhash(b:u256) -> hash:u256 | hash of block nr b - only for last 256 blocks excluding current |
|
||||
| blockhash(b:u256) ‑> hash:u256 | hash of block nr b - only for last 256 blocks excluding current |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| blocknumber() -> block:u256 | current block number |
|
||||
| blocknumber() ‑> block:u256 | current block number |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| blocktimestamp() -> timestamp:u256 | timestamp of the current block in seconds since the epoch |
|
||||
| blocktimestamp() ‑> timestamp:u256 | timestamp of the current block in seconds since the epoch |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| txorigin() -> address:u256 | transaction sender |
|
||||
| txorigin() ‑> address:u256 | transaction sender |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| txgasprice() -> price:u256 | gas price of the transaction |
|
||||
| txgasprice() ‑> price:u256 | gas price of the transaction |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| gasleft() -> gas:u256 | gas still available to execution |
|
||||
| gasleft() ‑> gas:u256 | gas still available to execution |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| balance(a:u256) -> v:u256 | wei balance at address a |
|
||||
| balance(a:u256) ‑> v:u256 | wei balance at address a |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| this() -> address:u256 | address of the current contract / execution context |
|
||||
| this() ‑> address:u256 | address of the current contract / execution context |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| caller() -> address:u256 | call sender (excluding delegatecall) |
|
||||
| caller() ‑> address:u256 | call sender (excluding delegatecall) |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| callvalue() -> v:u256 | wei sent together with the current call |
|
||||
| callvalue() ‑> v:u256 | wei sent together with the current call |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| calldataload(p:u256) -> v:u256 | call data starting from position p (32 bytes) |
|
||||
| calldataload(p:u256) ‑> v:u256 | call data starting from position p (32 bytes) |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| calldatasize() -> v:u256 | size of call data in bytes |
|
||||
| calldatasize() ‑> v:u256 | size of call data in bytes |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| calldatacopy(t:u256, f:u256, s:u256) | copy s bytes from calldata at position f to mem at position t |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| codesize() -> size:u256 | size of the code of the current contract / execution context |
|
||||
| codesize() ‑> size:u256 | size of the code of the current contract / execution context |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| codecopy(t:u256, f:u256, s:u256) | copy s bytes from code at position f to mem at position t |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| extcodesize(a:u256) -> size:u256 | size of the code at address a |
|
||||
| extcodesize(a:u256) ‑> size:u256 | size of the code at address a |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| extcodecopy(a:u256, t:u256, f:u256, s:u256) | like codecopy(t, f, s) but take code at address a |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
@ -508,19 +525,19 @@ The following functions must be available:
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| discardu256(unused:u256) | discard value |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| splitu256tou64(x:u256) -> (x1:u64, x2:u64, | split u256 to four u64's |
|
||||
| splitu256tou64(x:u256) ‑> (x1:u64, x2:u64, | split u256 to four u64's |
|
||||
| x3:u64, x4:u64) | |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| combineu64tou256(x1:u64, x2:u64, x3:u64, | combine four u64's into a single u256 |
|
||||
| x4:u64) -> (x:u256) | |
|
||||
| x4:u64) ‑> (x:u256) | |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| keccak256(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) |
|
||||
| keccak256(p:u256, s:u256) ‑> v:u256 | keccak(mem[p...(p+s))) |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| *Object access* | |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| datasize(name:string) -> size:u256 | size of the data object in bytes, name has to be string literal |
|
||||
| datasize(name:string) ‑> size:u256 | size of the data object in bytes, name has to be string literal |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| dataoffset(name:string) -> offset:u256 | offset of the data object inside the data area in bytes, |
|
||||
| dataoffset(name:string) ‑> offset:u256 | offset of the data object inside the data area in bytes, |
|
||||
| | name has to be string literal |
|
||||
+---------------------------------------------+-----------------------------------------------------------------+
|
||||
| datacopy(dst:u256, src:u256, len:u256) | copy len bytes from the data area starting at offset src bytes |
|
||||
|
@ -34,6 +34,54 @@
|
||||
#include <set>
|
||||
#include <functional>
|
||||
|
||||
/// Operators need to stay in the global namespace.
|
||||
|
||||
/// Concatenate the contents of a container onto a vector
|
||||
template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U const& _b)
|
||||
{
|
||||
for (auto const& i: _b)
|
||||
_a.push_back(i);
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a vector, move variant.
|
||||
template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U&& _b)
|
||||
{
|
||||
std::move(_b.begin(), _b.end(), std::back_inserter(_a));
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a set
|
||||
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U const& _b)
|
||||
{
|
||||
_a.insert(_b.begin(), _b.end());
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a set, move variant.
|
||||
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U&& _b)
|
||||
{
|
||||
for (auto&& x: _b)
|
||||
_a.insert(std::move(x));
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate two vectors of elements.
|
||||
template <class T>
|
||||
inline std::vector<T> operator+(std::vector<T> const& _a, std::vector<T> const& _b)
|
||||
{
|
||||
std::vector<T> ret(_a);
|
||||
ret += _b;
|
||||
return ret;
|
||||
}
|
||||
/// Concatenate two vectors of elements, moving them.
|
||||
template <class T>
|
||||
inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b)
|
||||
{
|
||||
std::vector<T> ret(std::move(_a));
|
||||
if (&_a == &_b)
|
||||
ret += ret;
|
||||
else
|
||||
ret += std::move(_b);
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
||||
@ -190,52 +238,12 @@ inline unsigned bytesRequired(T _i)
|
||||
for (; _i != 0; ++i, _i >>= 8) {}
|
||||
return i;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a vector
|
||||
template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U const& _b)
|
||||
{
|
||||
for (auto const& i: _b)
|
||||
_a.push_back(i);
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a vector, move variant.
|
||||
template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U&& _b)
|
||||
{
|
||||
std::move(_b.begin(), _b.end(), std::back_inserter(_a));
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a set
|
||||
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U const& _b)
|
||||
{
|
||||
_a.insert(_b.begin(), _b.end());
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate two vectors of elements.
|
||||
template <class T>
|
||||
inline std::vector<T> operator+(std::vector<T> const& _a, std::vector<T> const& _b)
|
||||
{
|
||||
std::vector<T> ret(_a);
|
||||
ret += _b;
|
||||
return ret;
|
||||
}
|
||||
/// Concatenate two vectors of elements, moving them.
|
||||
template <class T>
|
||||
inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b)
|
||||
{
|
||||
std::vector<T> ret(std::move(_a));
|
||||
if (&_a == &_b)
|
||||
ret += ret;
|
||||
else
|
||||
ret += std::move(_b);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class T, class V>
|
||||
bool contains(T const& _t, V const& _v)
|
||||
{
|
||||
return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v);
|
||||
}
|
||||
|
||||
|
||||
/// Function that iterates over a vector, calling a function on each of its
|
||||
/// elements. If that function returns a vector, the element is replaced by
|
||||
/// the returned vector. During the iteration, the original vector is only valid
|
||||
|
@ -30,7 +30,7 @@ namespace dev
|
||||
/// Result<bool> check()
|
||||
/// {
|
||||
/// if (false)
|
||||
/// return Result<bool>("Error message.")
|
||||
/// return Result<bool>::err("Error message.")
|
||||
/// return true;
|
||||
/// }
|
||||
///
|
||||
@ -39,8 +39,17 @@ template <class ResultType>
|
||||
class Result
|
||||
{
|
||||
public:
|
||||
/// Constructs a result with _value and an empty message.
|
||||
/// This is meant to be called with valid results. Please use
|
||||
/// the static err() member function to signal an error.
|
||||
Result(ResultType _value): Result(_value, std::string{}) {}
|
||||
Result(std::string _message): Result(ResultType{}, std::move(_message)) {}
|
||||
|
||||
/// Constructs a result with a default-constructed value and an
|
||||
/// error message.
|
||||
static Result<ResultType> err(std::string _message)
|
||||
{
|
||||
return Result{ResultType{}, std::move(_message)};
|
||||
}
|
||||
|
||||
/// @{
|
||||
/// @name Wrapper functions
|
||||
@ -53,6 +62,16 @@ public:
|
||||
/// @returns the error message (can be empty).
|
||||
std::string const& message() const { return m_message; }
|
||||
|
||||
/// Merges _other into this using the _merger
|
||||
/// and appends the error messages. Meant to be called
|
||||
/// with logical operators like logical_and, etc.
|
||||
template<typename F>
|
||||
void merge(Result<ResultType> const& _other, F _merger)
|
||||
{
|
||||
m_value = _merger(m_value, _other.get());
|
||||
m_message += _other.message();
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Result(ResultType _value, std::string _message):
|
||||
m_value(std::move(_value)),
|
||||
|
@ -196,13 +196,16 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr)
|
||||
if (auto match = rules.findFirstMatch(_expr, *this))
|
||||
{
|
||||
// Debug info
|
||||
//cout << "Simplifying " << *_expr.item << "(";
|
||||
//for (Id arg: _expr.arguments)
|
||||
// cout << fullDAGToString(arg) << ", ";
|
||||
//cout << ")" << endl;
|
||||
//cout << "with rule " << match->first.toString() << endl;
|
||||
//ExpressionTemplate t(match->second());
|
||||
//cout << "to " << match->second().toString() << endl;
|
||||
if (false)
|
||||
{
|
||||
cout << "Simplifying " << *_expr.item << "(";
|
||||
for (Id arg: _expr.arguments)
|
||||
cout << fullDAGToString(arg) << ", ";
|
||||
cout << ")" << endl;
|
||||
cout << "with rule " << match->pattern.toString() << endl;
|
||||
cout << "to " << match->action().toString() << endl;
|
||||
}
|
||||
|
||||
return rebuildExpression(ExpressionTemplate(match->action(), _expr.item->location()));
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2(
|
||||
{{Instruction::GT, {0, X}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::LT, {X, 0}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::AND, {{Instruction::BYTE, {X, Y}}, {u256(0xff)}}}, [=]() -> Pattern { return {Instruction::BYTE, {X, Y}}; }, false},
|
||||
{{Instruction::BYTE, {X, 31}}, [=]() -> Pattern { return {Instruction::AND, {X, u256(0xff)}}; }, false}
|
||||
{{Instruction::BYTE, {31, X}}, [=]() -> Pattern { return {Instruction::AND, {X, u256(0xff)}}; }, false}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -36,9 +36,22 @@ namespace solidity
|
||||
template <class Pattern>
|
||||
struct SimplificationRule
|
||||
{
|
||||
SimplificationRule(
|
||||
Pattern _pattern,
|
||||
std::function<Pattern()> _action,
|
||||
bool _removesNonConstants,
|
||||
std::function<bool()> _feasible = {}
|
||||
):
|
||||
pattern(std::move(_pattern)),
|
||||
action(std::move(_action)),
|
||||
removesNonConstants(_removesNonConstants),
|
||||
feasible(std::move(_feasible))
|
||||
{}
|
||||
|
||||
Pattern pattern;
|
||||
std::function<Pattern()> action;
|
||||
bool removesNonConstants;
|
||||
std::function<bool()> feasible;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -51,7 +51,9 @@ SimplificationRule<Pattern> const* Rules::findFirstMatch(
|
||||
for (auto const& rule: m_rules[uint8_t(_expr.item->instruction())])
|
||||
{
|
||||
if (rule.pattern.matches(_expr, _classes))
|
||||
return &rule;
|
||||
if (!rule.feasible || rule.feasible())
|
||||
return &rule;
|
||||
|
||||
resetMatchGroups();
|
||||
}
|
||||
return nullptr;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <memory>
|
||||
#include <libdevcore/Exceptions.h>
|
||||
#include <libdevcore/Assertions.h>
|
||||
#include <libdevcore/CommonData.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
|
||||
namespace langutil
|
||||
@ -108,6 +109,11 @@ public:
|
||||
infos.emplace_back(_errMsg, _sourceLocation);
|
||||
return *this;
|
||||
}
|
||||
SecondarySourceLocation& append(SecondarySourceLocation&& _other)
|
||||
{
|
||||
infos += std::move(_other.infos);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Limits the number of secondary source locations to 32 and appends a notice to the
|
||||
/// error message.
|
||||
|
@ -213,7 +213,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
|
||||
for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes())
|
||||
{
|
||||
solAssert(t->annotation().type, "Type not set for parameter.");
|
||||
if (!t->annotation().type->canBeUsedExternally(false))
|
||||
if (!t->annotation().type->interfaceType(false).get())
|
||||
{
|
||||
fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
|
||||
return;
|
||||
|
@ -355,8 +355,16 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (!type(var)->canLiveOutsideStorage() && _function.isPublic())
|
||||
m_errorReporter.typeError(var.location(), "Type is required to live outside storage.");
|
||||
if (_function.isPublic() && !(type(var)->interfaceType(isLibraryFunction)))
|
||||
m_errorReporter.fatalTypeError(var.location(), "Internal or recursive type is not allowed for public or external functions.");
|
||||
if (_function.isPublic())
|
||||
{
|
||||
auto iType = type(var)->interfaceType(isLibraryFunction);
|
||||
|
||||
if (!iType.get())
|
||||
{
|
||||
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
||||
m_errorReporter.fatalTypeError(var.location(), iType.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
_function.isPublic() &&
|
||||
@ -576,7 +584,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
|
||||
numIndexed++;
|
||||
if (!type(*var)->canLiveOutsideStorage())
|
||||
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
||||
if (!type(*var)->interfaceType(false))
|
||||
if (!type(*var)->interfaceType(false).get())
|
||||
m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
||||
if (
|
||||
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
||||
@ -599,7 +607,7 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
||||
{
|
||||
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
|
||||
if (fun.kind() == FunctionType::Kind::External)
|
||||
solAssert(fun.canBeUsedExternally(false), "External function type uses internal types.");
|
||||
solAssert(fun.interfaceType(false).get(), "External function type uses internal types.");
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
@ -1807,13 +1815,16 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
argumentsArePure = false;
|
||||
}
|
||||
|
||||
// For positional calls only, store argument types
|
||||
if (_functionCall.names().empty())
|
||||
// Store argument types - and names if given - for overload resolution
|
||||
{
|
||||
shared_ptr<TypePointers> argumentTypes = make_shared<TypePointers>();
|
||||
FuncCallArguments funcCallArgs;
|
||||
|
||||
funcCallArgs.names = _functionCall.names();
|
||||
|
||||
for (ASTPointer<Expression const> const& argument: arguments)
|
||||
argumentTypes->push_back(type(*argument));
|
||||
_functionCall.expression().annotation().argumentTypes = move(argumentTypes);
|
||||
funcCallArgs.types.push_back(type(*argument));
|
||||
|
||||
_functionCall.expression().annotation().arguments = std::move(funcCallArgs);
|
||||
}
|
||||
|
||||
_functionCall.expression().accept(*this);
|
||||
@ -2010,16 +2021,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
ASTString const& memberName = _memberAccess.memberName();
|
||||
|
||||
// Retrieve the types of the arguments if this is used to call a function.
|
||||
auto const& argumentTypes = _memberAccess.annotation().argumentTypes;
|
||||
auto const& arguments = _memberAccess.annotation().arguments;
|
||||
MemberList::MemberMap possibleMembers = exprType->members(m_scope).membersByName(memberName);
|
||||
size_t const initialMemberCount = possibleMembers.size();
|
||||
if (initialMemberCount > 1 && argumentTypes)
|
||||
if (initialMemberCount > 1 && arguments)
|
||||
{
|
||||
// do overload resolution
|
||||
for (auto it = possibleMembers.begin(); it != possibleMembers.end();)
|
||||
if (
|
||||
it->type->category() == Type::Category::Function &&
|
||||
!dynamic_cast<FunctionType const&>(*it->type).canTakeArguments(*argumentTypes, exprType)
|
||||
!dynamic_cast<FunctionType const&>(*it->type).canTakeArguments(*arguments, exprType)
|
||||
)
|
||||
it = possibleMembers.erase(it);
|
||||
else
|
||||
@ -2274,7 +2285,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
IdentifierAnnotation& annotation = _identifier.annotation();
|
||||
if (!annotation.referencedDeclaration)
|
||||
{
|
||||
if (!annotation.argumentTypes)
|
||||
if (!annotation.arguments)
|
||||
{
|
||||
// The identifier should be a public state variable shadowing other functions
|
||||
vector<Declaration const*> candidates;
|
||||
@ -2303,7 +2314,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
{
|
||||
FunctionTypePointer functionType = declaration->functionType(true);
|
||||
solAssert(!!functionType, "Requested type not present.");
|
||||
if (functionType->canTakeArguments(*annotation.argumentTypes))
|
||||
if (functionType->canTakeArguments(*annotation.arguments))
|
||||
candidates.push_back(declaration);
|
||||
}
|
||||
if (candidates.empty())
|
||||
|
@ -112,6 +112,8 @@ private:
|
||||
{
|
||||
if (eth::SemanticInformation::invalidInViewFunctions(_instruction))
|
||||
m_reportMutability(StateMutability::NonPayable, _location);
|
||||
else if (_instruction == Instruction::CALLVALUE)
|
||||
m_reportMutability(StateMutability::Payable, _location);
|
||||
else if (eth::SemanticInformation::invalidInPureFunctions(_instruction))
|
||||
m_reportMutability(StateMutability::View, _location);
|
||||
}
|
||||
@ -270,13 +272,13 @@ void ViewPureChecker::reportMutability(
|
||||
if (_nestedLocation)
|
||||
m_errorReporter.typeError(
|
||||
_location,
|
||||
SecondarySourceLocation().append("\"msg.value\" appears here inside the modifier.", *_nestedLocation),
|
||||
"This modifier uses \"msg.value\" and thus the function has to be payable or internal."
|
||||
SecondarySourceLocation().append("\"msg.value\" or \"callvalue()\" appear here inside the modifier.", *_nestedLocation),
|
||||
"This modifier uses \"msg.value\" or \"callvalue()\" and thus the function has to be payable or internal."
|
||||
);
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
_location,
|
||||
"\"msg.value\" can only be used in payable public functions. Make the function "
|
||||
"\"msg.value\" and \"callvalue()\" can only be used in payable public functions. Make the function "
|
||||
"\"payable\" or use an internal function to avoid this error."
|
||||
);
|
||||
m_errors = true;
|
||||
|
@ -23,8 +23,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/ASTEnums.h>
|
||||
#include <libsolidity/ast/ExperimentalFeatures.h>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@ -176,9 +179,10 @@ struct ExpressionAnnotation: ASTAnnotation
|
||||
bool isLValue = false;
|
||||
/// Whether the expression is used in a context where the LValue is actually required.
|
||||
bool lValueRequested = false;
|
||||
/// Types of arguments if the expression is a function that is called - used
|
||||
/// for overload resolution.
|
||||
std::shared_ptr<std::vector<TypePointer>> argumentTypes;
|
||||
|
||||
/// Types and - if given - names of arguments if the expr. is a function
|
||||
/// that is called, used for overload resoultion
|
||||
boost::optional<FuncCallArguments> arguments;
|
||||
};
|
||||
|
||||
struct IdentifierAnnotation: ExpressionAnnotation
|
||||
|
@ -22,6 +22,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -50,5 +51,20 @@ inline std::string stateMutabilityToString(StateMutability const& _stateMutabili
|
||||
}
|
||||
}
|
||||
|
||||
class Type;
|
||||
|
||||
/// Container for function call parameter types & names
|
||||
struct FuncCallArguments
|
||||
{
|
||||
/// Types of arguments
|
||||
std::vector<std::shared_ptr<Type const>> types;
|
||||
/// Names of the arguments if given, otherwise unset
|
||||
std::vector<ASTPointer<ASTString>> names;
|
||||
|
||||
size_t numArguments() const { return types.size(); }
|
||||
size_t numNames() const { return names.size(); }
|
||||
bool hasNamedArguments() const { return !names.empty(); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -144,12 +144,12 @@ Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp, bool _short)
|
||||
return typeDescriptions;
|
||||
|
||||
}
|
||||
Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps)
|
||||
Json::Value ASTJsonConverter::typePointerToJson(boost::optional<FuncCallArguments> const& _tps)
|
||||
{
|
||||
if (_tps)
|
||||
{
|
||||
Json::Value arguments(Json::arrayValue);
|
||||
for (auto const& tp: *_tps)
|
||||
for (auto const& tp: _tps->types)
|
||||
appendMove(arguments, typePointerToJson(tp));
|
||||
return arguments;
|
||||
}
|
||||
@ -168,7 +168,7 @@ void ASTJsonConverter::appendExpressionAttributes(
|
||||
make_pair("isPure", _annotation.isPure),
|
||||
make_pair("isLValue", _annotation.isLValue),
|
||||
make_pair("lValueRequested", _annotation.lValueRequested),
|
||||
make_pair("argumentTypes", typePointerToJson(_annotation.argumentTypes))
|
||||
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
|
||||
};
|
||||
_attributes += exprAttributes;
|
||||
}
|
||||
@ -701,7 +701,7 @@ bool ASTJsonConverter::visit(Identifier const& _node)
|
||||
make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)),
|
||||
make_pair("overloadedDeclarations", overloads),
|
||||
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)),
|
||||
make_pair("argumentTypes", typePointerToJson(_node.annotation().argumentTypes))
|
||||
make_pair("argumentTypes", typePointerToJson(_node.annotation().arguments))
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ private:
|
||||
return tmp;
|
||||
}
|
||||
static Json::Value typePointerToJson(TypePointer _tp, bool _short = false);
|
||||
static Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps);
|
||||
static Json::Value typePointerToJson(boost::optional<FuncCallArguments> const& _tps);
|
||||
void appendExpressionAttributes(
|
||||
std::vector<std::pair<std::string, Json::Value>> &_attributes,
|
||||
ExpressionAnnotation const& _annotation
|
||||
|
@ -129,10 +129,10 @@ bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2)
|
||||
BoolResult fitsIntegerType(bigint const& _value, IntegerType const& _type)
|
||||
{
|
||||
if (_value < 0 && !_type.isSigned())
|
||||
return BoolResult{std::string("Cannot implicitly convert signed literal to unsigned type.")};
|
||||
return BoolResult::err("Cannot implicitly convert signed literal to unsigned type.");
|
||||
|
||||
if (_type.minValue() > _value || _value > _type.maxValue())
|
||||
return BoolResult{"Literal is too large to fit in " + _type.toString(false) + "."};
|
||||
return BoolResult::err("Literal is too large to fit in " + _type.toString(false) + ".");
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -535,7 +535,7 @@ TypeResult AddressType::unaryOperatorResult(Token _operator) const
|
||||
TypeResult AddressType::binaryOperatorResult(Token _operator, TypePointer const& _other) const
|
||||
{
|
||||
if (!TokenTraits::isCompareOp(_operator))
|
||||
return TypeResult{"Arithmetic operations on addresses are not supported. Convert to integer first before using them."};
|
||||
return TypeResult::err("Arithmetic operations on addresses are not supported. Convert to integer first before using them.");
|
||||
|
||||
return Type::commonType(shared_from_this(), _other);
|
||||
}
|
||||
@ -638,7 +638,7 @@ TypeResult IntegerType::unaryOperatorResult(Token _operator) const
|
||||
_operator == Token::Dec || _operator == Token::BitNot)
|
||||
return TypeResult{shared_from_this()};
|
||||
else
|
||||
return TypeResult{""};
|
||||
return TypeResult::err("");
|
||||
}
|
||||
|
||||
bool IntegerType::operator==(Type const& _other) const
|
||||
@ -700,7 +700,7 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, TypePointer const&
|
||||
if (auto intType = dynamic_pointer_cast<IntegerType const>(commonType))
|
||||
{
|
||||
if (Token::Exp == _operator && intType->isSigned())
|
||||
return TypeResult{"Exponentiation is not allowed for signed integer types."};
|
||||
return TypeResult::err("Exponentiation is not allowed for signed integer types.");
|
||||
}
|
||||
else if (auto fixType = dynamic_pointer_cast<FixedPointType const>(commonType))
|
||||
if (Token::Exp == _operator)
|
||||
@ -729,7 +729,7 @@ BoolResult FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) con
|
||||
{
|
||||
FixedPointType const& convertTo = dynamic_cast<FixedPointType const&>(_convertTo);
|
||||
if (convertTo.fractionalDigits() < m_fractionalDigits)
|
||||
return BoolResult{std::string("Too many fractional digits.")};
|
||||
return BoolResult::err("Too many fractional digits.");
|
||||
if (convertTo.numBits() < m_totalBits)
|
||||
return false;
|
||||
else
|
||||
@ -1145,7 +1145,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer
|
||||
uint32_t absExp = bigint(abs(exp)).convert_to<uint32_t>();
|
||||
|
||||
if (!fitsPrecisionExp(abs(m_value.numerator()), absExp) || !fitsPrecisionExp(abs(m_value.denominator()), absExp))
|
||||
return TypeResult{"Precision of rational constants is limited to 4096 bits."};
|
||||
return TypeResult::err("Precision of rational constants is limited to 4096 bits.");
|
||||
|
||||
static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint {
|
||||
if (_base == 1)
|
||||
@ -1226,7 +1226,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer
|
||||
|
||||
// verify that numerator and denominator fit into 4096 bit after every operation
|
||||
if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096)
|
||||
return TypeResult{"Precision of rational constants is limited to 4096 bits."};
|
||||
return TypeResult::err("Precision of rational constants is limited to 4096 bits.");
|
||||
|
||||
return TypeResult(make_shared<RationalNumberType>(value));
|
||||
}
|
||||
@ -1869,35 +1869,37 @@ TypePointer ArrayType::decodingType() const
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
TypePointer ArrayType::interfaceType(bool _inLibrary) const
|
||||
TypeResult ArrayType::interfaceType(bool _inLibrary) const
|
||||
{
|
||||
// Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary)
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
return shared_from_this();
|
||||
if (_inLibrary && m_interfaceType_library.is_initialized())
|
||||
return *m_interfaceType_library;
|
||||
|
||||
if (m_arrayKind != ArrayKind::Ordinary)
|
||||
return this->copyForLocation(DataLocation::Memory, true);
|
||||
TypePointer baseExt = m_baseType->interfaceType(_inLibrary);
|
||||
if (!baseExt)
|
||||
return TypePointer();
|
||||
if (!_inLibrary && m_interfaceType.is_initialized())
|
||||
return *m_interfaceType;
|
||||
|
||||
if (isDynamicallySized())
|
||||
return make_shared<ArrayType>(DataLocation::Memory, baseExt);
|
||||
else
|
||||
return make_shared<ArrayType>(DataLocation::Memory, baseExt, m_length);
|
||||
}
|
||||
TypeResult result{TypePointer{}};
|
||||
TypeResult baseInterfaceType = m_baseType->interfaceType(_inLibrary);
|
||||
|
||||
bool ArrayType::canBeUsedExternally(bool _inLibrary) const
|
||||
{
|
||||
// Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary)
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
return true;
|
||||
if (!baseInterfaceType.get())
|
||||
{
|
||||
solAssert(!baseInterfaceType.message().empty(), "Expected detailed error message!");
|
||||
result = baseInterfaceType;
|
||||
}
|
||||
else if (_inLibrary && location() == DataLocation::Storage)
|
||||
result = shared_from_this();
|
||||
else if (m_arrayKind != ArrayKind::Ordinary)
|
||||
return true;
|
||||
else if (!m_baseType->canBeUsedExternally(_inLibrary))
|
||||
return false;
|
||||
result = this->copyForLocation(DataLocation::Memory, true);
|
||||
else if (isDynamicallySized())
|
||||
result = TypePointer{make_shared<ArrayType>(DataLocation::Memory, baseInterfaceType)};
|
||||
else
|
||||
return true;
|
||||
result = TypePointer{make_shared<ArrayType>(DataLocation::Memory, baseInterfaceType, m_length)};
|
||||
|
||||
if (_inLibrary)
|
||||
m_interfaceType_library = result;
|
||||
else
|
||||
m_interfaceType = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
u256 ArrayType::memorySize() const
|
||||
@ -2081,7 +2083,7 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const
|
||||
|
||||
bool StructType::isDynamicallyEncoded() const
|
||||
{
|
||||
solAssert(!recursive(), "");
|
||||
solAssert(interfaceType(false).get(), "");
|
||||
for (auto t: memoryMemberTypes())
|
||||
{
|
||||
solAssert(t, "Parameter should have external type.");
|
||||
@ -2132,44 +2134,95 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
||||
return members;
|
||||
}
|
||||
|
||||
TypePointer StructType::interfaceType(bool _inLibrary) const
|
||||
TypeResult StructType::interfaceType(bool _inLibrary) const
|
||||
{
|
||||
if (!canBeUsedExternally(_inLibrary))
|
||||
return TypePointer();
|
||||
if (_inLibrary && m_interfaceType_library.is_initialized())
|
||||
return *m_interfaceType_library;
|
||||
|
||||
// Has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary)
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
return shared_from_this();
|
||||
else
|
||||
return copyForLocation(DataLocation::Memory, true);
|
||||
}
|
||||
if (!_inLibrary && m_interfaceType.is_initialized())
|
||||
return *m_interfaceType;
|
||||
|
||||
bool StructType::canBeUsedExternally(bool _inLibrary) const
|
||||
{
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
return true;
|
||||
else if (recursive())
|
||||
return false;
|
||||
else
|
||||
TypeResult result{TypePointer{}};
|
||||
|
||||
m_recursive = false;
|
||||
|
||||
auto visitor = [&](
|
||||
StructDefinition const& _struct,
|
||||
CycleDetector<StructDefinition>& _cycleDetector,
|
||||
size_t /*_depth*/
|
||||
)
|
||||
{
|
||||
// Check that all members have interface types.
|
||||
// We pass "false" to canBeUsedExternally (_inLibrary), because this struct will be
|
||||
// passed by value and thus the encoding does not differ, but it will disallow
|
||||
// mappings.
|
||||
// Also return false if at least one struct member does not have a type.
|
||||
// This might happen, for example, if the type of the member does not exist,
|
||||
// which is reported as an error.
|
||||
for (auto const& var: m_struct.members())
|
||||
// Return an error if at least one struct member does not have a type.
|
||||
// This might happen, for example, if the type of the member does not exist.
|
||||
for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
|
||||
{
|
||||
// If the struct member does not have a type return false.
|
||||
// A TypeError is expected in this case.
|
||||
if (!var->annotation().type)
|
||||
return false;
|
||||
if (!var->annotation().type->canBeUsedExternally(false))
|
||||
return false;
|
||||
if (!variable->annotation().type)
|
||||
{
|
||||
result = TypeResult::err("Invalid type!");
|
||||
return;
|
||||
}
|
||||
|
||||
Type const* memberType = variable->annotation().type.get();
|
||||
|
||||
while (dynamic_cast<ArrayType const*>(memberType))
|
||||
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get();
|
||||
|
||||
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
|
||||
if (
|
||||
innerStruct->m_recursive == true ||
|
||||
_cycleDetector.run(innerStruct->structDefinition())
|
||||
)
|
||||
{
|
||||
m_recursive = true;
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
continue;
|
||||
else
|
||||
{
|
||||
result = TypeResult::err("Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto iType = memberType->interfaceType(_inLibrary);
|
||||
if (!iType.get())
|
||||
{
|
||||
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
||||
result = iType;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m_recursive = m_recursive.get() || (CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr);
|
||||
|
||||
std::string const recursiveErrMsg = "Recursive type not allowed for public or external contract functions.";
|
||||
|
||||
if (_inLibrary)
|
||||
{
|
||||
if (!result.message().empty())
|
||||
m_interfaceType_library = result;
|
||||
else if (location() == DataLocation::Storage)
|
||||
m_interfaceType_library = shared_from_this();
|
||||
else
|
||||
m_interfaceType_library = copyForLocation(DataLocation::Memory, true);
|
||||
|
||||
if (m_recursive.get())
|
||||
m_interfaceType = TypeResult::err(recursiveErrMsg);
|
||||
|
||||
return *m_interfaceType_library;
|
||||
}
|
||||
return true;
|
||||
|
||||
if (m_recursive.get())
|
||||
m_interfaceType = TypeResult::err(recursiveErrMsg);
|
||||
else if (!result.message().empty())
|
||||
m_interfaceType = result;
|
||||
else
|
||||
m_interfaceType = copyForLocation(DataLocation::Memory, true);
|
||||
|
||||
return *m_interfaceType;
|
||||
}
|
||||
|
||||
TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const
|
||||
@ -2190,8 +2243,8 @@ string StructType::signatureInExternalFunction(bool _structsByName) const
|
||||
{
|
||||
solAssert(_t, "Parameter should have external type.");
|
||||
auto t = _t->interfaceType(_structsByName);
|
||||
solAssert(t, "");
|
||||
return t->signatureInExternalFunction(_structsByName);
|
||||
solAssert(t.get(), "");
|
||||
return t.get()->signatureInExternalFunction(_structsByName);
|
||||
});
|
||||
return "(" + boost::algorithm::join(memberTypeStrings, ",") + ")";
|
||||
}
|
||||
@ -2259,27 +2312,6 @@ set<string> StructType::membersMissingInMemory() const
|
||||
return missing;
|
||||
}
|
||||
|
||||
bool StructType::recursive() const
|
||||
{
|
||||
if (!m_recursive.is_initialized())
|
||||
{
|
||||
auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector, size_t /*_depth*/)
|
||||
{
|
||||
for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
|
||||
{
|
||||
Type const* memberType = variable->annotation().type.get();
|
||||
while (dynamic_cast<ArrayType const*>(memberType))
|
||||
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get();
|
||||
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
|
||||
if (_cycleDetector.run(innerStruct->structDefinition()))
|
||||
return;
|
||||
}
|
||||
};
|
||||
m_recursive = (CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr);
|
||||
}
|
||||
return *m_recursive;
|
||||
}
|
||||
|
||||
TypeResult EnumType::unaryOperatorResult(Token _operator) const
|
||||
{
|
||||
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
|
||||
@ -2560,7 +2592,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName):
|
||||
solAssert(t->annotation().type, "Type not set for parameter.");
|
||||
if (m_kind == Kind::External)
|
||||
solAssert(
|
||||
t->annotation().type->canBeUsedExternally(false),
|
||||
t->annotation().type->interfaceType(false).get(),
|
||||
"Internal type used as parameter for external function."
|
||||
);
|
||||
m_parameterTypes.push_back(t->annotation().type);
|
||||
@ -2570,7 +2602,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName):
|
||||
solAssert(t->annotation().type, "Type not set for return parameter.");
|
||||
if (m_kind == Kind::External)
|
||||
solAssert(
|
||||
t->annotation().type->canBeUsedExternally(false),
|
||||
t->annotation().type->interfaceType(false).get(),
|
||||
"Internal type used as return parameter for external function."
|
||||
);
|
||||
m_returnParameterTypes.push_back(t->annotation().type);
|
||||
@ -2810,6 +2842,14 @@ u256 FunctionType::storageSize() const
|
||||
solAssert(false, "Storage size of non-storable function type requested.");
|
||||
}
|
||||
|
||||
bool FunctionType::leftAligned() const
|
||||
{
|
||||
if (m_kind == Kind::External)
|
||||
return true;
|
||||
else
|
||||
solAssert(false, "Alignment property of non-exportable function type requested.");
|
||||
}
|
||||
|
||||
unsigned FunctionType::storageBytes() const
|
||||
{
|
||||
if (m_kind == Kind::External)
|
||||
@ -2871,14 +2911,14 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
|
||||
|
||||
for (auto type: m_parameterTypes)
|
||||
{
|
||||
if (auto ext = type->interfaceType(isLibraryFunction))
|
||||
if (auto ext = type->interfaceType(isLibraryFunction).get())
|
||||
paramTypes.push_back(ext);
|
||||
else
|
||||
return FunctionTypePointer();
|
||||
}
|
||||
for (auto type: m_returnParameterTypes)
|
||||
{
|
||||
if (auto ext = type->interfaceType(isLibraryFunction))
|
||||
if (auto ext = type->interfaceType(isLibraryFunction).get())
|
||||
retParamTypes.push_back(ext);
|
||||
else
|
||||
return FunctionTypePointer();
|
||||
@ -2964,34 +3004,61 @@ TypePointer FunctionType::encodingType() const
|
||||
return TypePointer();
|
||||
}
|
||||
|
||||
TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const
|
||||
TypeResult FunctionType::interfaceType(bool /*_inLibrary*/) const
|
||||
{
|
||||
if (m_kind == Kind::External)
|
||||
return shared_from_this();
|
||||
else
|
||||
return TypePointer();
|
||||
return TypeResult::err("Internal type is not allowed for public or external functions.");
|
||||
}
|
||||
|
||||
bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const
|
||||
bool FunctionType::canTakeArguments(
|
||||
FuncCallArguments const& _arguments,
|
||||
TypePointer const& _selfType
|
||||
) const
|
||||
{
|
||||
solAssert(!bound() || _selfType, "");
|
||||
if (bound() && !_selfType->isImplicitlyConvertibleTo(*selfType()))
|
||||
return false;
|
||||
TypePointers paramTypes = parameterTypes();
|
||||
std::vector<std::string> const paramNames = parameterNames();
|
||||
|
||||
if (takesArbitraryParameters())
|
||||
return true;
|
||||
else if (_argumentTypes.size() != paramTypes.size())
|
||||
else if (_arguments.numArguments() != paramTypes.size())
|
||||
return false;
|
||||
else
|
||||
else if (!_arguments.hasNamedArguments())
|
||||
return equal(
|
||||
_argumentTypes.cbegin(),
|
||||
_argumentTypes.cend(),
|
||||
_arguments.types.cbegin(),
|
||||
_arguments.types.cend(),
|
||||
paramTypes.cbegin(),
|
||||
[](TypePointer const& argumentType, TypePointer const& parameterType)
|
||||
{
|
||||
return argumentType->isImplicitlyConvertibleTo(*parameterType);
|
||||
}
|
||||
);
|
||||
else if (paramNames.size() != _arguments.numNames())
|
||||
return false;
|
||||
else
|
||||
{
|
||||
solAssert(_arguments.numArguments() == _arguments.numNames(), "Expected equal sized type & name vectors");
|
||||
|
||||
size_t matchedNames = 0;
|
||||
|
||||
for (auto const& argName: _arguments.names)
|
||||
for (size_t i = 0; i < paramNames.size(); i++)
|
||||
if (*argName == paramNames[i])
|
||||
{
|
||||
matchedNames++;
|
||||
if (!_arguments.types[i]->isImplicitlyConvertibleTo(*paramTypes[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matchedNames == _arguments.numNames())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const
|
||||
@ -3236,6 +3303,26 @@ string MappingType::canonicalName() const
|
||||
return "mapping(" + keyType()->canonicalName() + " => " + valueType()->canonicalName() + ")";
|
||||
}
|
||||
|
||||
TypeResult MappingType::interfaceType(bool _inLibrary) const
|
||||
{
|
||||
solAssert(keyType()->interfaceType(_inLibrary).get(), "Must be an elementary type!");
|
||||
|
||||
if (_inLibrary)
|
||||
{
|
||||
auto iType = valueType()->interfaceType(_inLibrary);
|
||||
|
||||
if (!iType.get())
|
||||
{
|
||||
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
||||
return iType;
|
||||
}
|
||||
}
|
||||
else
|
||||
return TypeResult::err("Only libraries are allowed to use the mapping type in public or external functions.");
|
||||
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
string TypeType::richIdentifier() const
|
||||
{
|
||||
return "t_type" + identifierList(actualType());
|
||||
|
@ -236,6 +236,13 @@ public:
|
||||
/// In order to avoid computation at runtime of whether such moving is necessary, structs and
|
||||
/// array data (not each element) always start a new slot.
|
||||
virtual unsigned storageBytes() const { return 32; }
|
||||
/// Returns true if the type is a value type that is left-aligned on the stack with a size of
|
||||
/// storageBytes() bytes. Returns false if the type is a value type that is right-aligned on
|
||||
/// the stack with a size of storageBytes() bytes. Asserts if it is not a value type or the
|
||||
/// encoding is more complicated.
|
||||
/// Signed integers are not considered "more complicated" even though they need to be
|
||||
/// sign-extended.
|
||||
virtual bool leftAligned() const { solAssert(false, "Alignment property of non-value type requested."); }
|
||||
/// Returns true if the type can be stored in storage.
|
||||
virtual bool canBeStored() const { return true; }
|
||||
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
|
||||
@ -304,10 +311,7 @@ public:
|
||||
/// If there is no such type, returns an empty shared pointer.
|
||||
/// @param _inLibrary if set, returns types as used in a library, e.g. struct and contract types
|
||||
/// are returned without modification.
|
||||
virtual TypePointer interfaceType(bool /*_inLibrary*/) const { return TypePointer(); }
|
||||
/// @returns true iff this type can be passed on via calls (to libraries if _inLibrary is true),
|
||||
/// should be have identical to !!interfaceType(_inLibrary) but might do optimizations.
|
||||
virtual bool canBeUsedExternally(bool _inLibrary) const { return !!interfaceType(_inLibrary); }
|
||||
virtual TypeResult interfaceType(bool /*_inLibrary*/) const { return TypePointer(); }
|
||||
|
||||
private:
|
||||
/// @returns a member list containing all members added to this type by `using for` directives.
|
||||
@ -348,6 +352,7 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; }
|
||||
unsigned storageBytes() const override { return 160 / 8; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||
@ -358,7 +363,7 @@ public:
|
||||
u256 literalValue(Literal const* _literal) const override;
|
||||
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypePointer interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
|
||||
StateMutability stateMutability(void) const { return m_stateMutability; }
|
||||
|
||||
@ -393,12 +398,13 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; }
|
||||
unsigned storageBytes() const override { return m_bits / 8; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypePointer interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
|
||||
unsigned numBits() const { return m_bits; }
|
||||
bool isSigned() const { return m_modifier == Modifier::Signed; }
|
||||
@ -435,12 +441,13 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_totalBits / 8; }
|
||||
unsigned storageBytes() const override { return m_totalBits / 8; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypePointer interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
|
||||
/// Number of bits used for this type in total.
|
||||
unsigned numBits() const { return m_totalBits; }
|
||||
@ -581,12 +588,13 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; }
|
||||
unsigned storageBytes() const override { return m_bytes; }
|
||||
bool leftAligned() const override { return true; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypePointer interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
|
||||
unsigned numBytes() const { return m_bytes; }
|
||||
|
||||
@ -607,12 +615,13 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; }
|
||||
unsigned storageBytes() const override { return 1; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool) const override { return "bool"; }
|
||||
u256 literalValue(Literal const* _literal) const override;
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypePointer interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -719,8 +728,7 @@ public:
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
TypePointer encodingType() const override;
|
||||
TypePointer decodingType() const override;
|
||||
TypePointer interfaceType(bool _inLibrary) const override;
|
||||
bool canBeUsedExternally(bool _inLibrary) const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
|
||||
/// @returns true if this is valid to be stored in calldata
|
||||
bool validForCalldata() const;
|
||||
@ -753,6 +761,8 @@ private:
|
||||
TypePointer m_baseType;
|
||||
bool m_hasDynamicLength = true;
|
||||
u256 m_length;
|
||||
mutable boost::optional<TypeResult> m_interfaceType;
|
||||
mutable boost::optional<TypeResult> m_interfaceType_library;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -777,6 +787,7 @@ public:
|
||||
return encodingType()->calldataEncodedSize(_padded);
|
||||
}
|
||||
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
|
||||
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
|
||||
bool canLiveOutsideStorage() const override { return !isSuper(); }
|
||||
unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
|
||||
bool isValueType() const override { return !isSuper(); }
|
||||
@ -790,13 +801,14 @@ public:
|
||||
return TypePointer{};
|
||||
return std::make_shared<AddressType>(isPayable() ? StateMutability::Payable : StateMutability::NonPayable);
|
||||
}
|
||||
TypePointer interfaceType(bool _inLibrary) const override
|
||||
TypeResult interfaceType(bool _inLibrary) const override
|
||||
{
|
||||
if (isSuper())
|
||||
return TypePointer{};
|
||||
return _inLibrary ? shared_from_this() : encodingType();
|
||||
}
|
||||
|
||||
/// See documentation of m_super
|
||||
bool isSuper() const { return m_super; }
|
||||
|
||||
// @returns true if and only if the contract has a payable fallback function
|
||||
@ -813,8 +825,7 @@ public:
|
||||
|
||||
private:
|
||||
ContractDefinition const& m_contract;
|
||||
/// If true, it is the "super" type of the current contract, i.e. it contains only inherited
|
||||
/// members.
|
||||
/// If true, this is a special "super" type of m_contract containing only members that m_contract inherited
|
||||
bool m_super = false;
|
||||
/// Type of the constructor, @see constructorType. Lazily initialized.
|
||||
mutable FunctionTypePointer m_constructorType;
|
||||
@ -844,8 +855,17 @@ public:
|
||||
{
|
||||
return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : shared_from_this();
|
||||
}
|
||||
TypePointer interfaceType(bool _inLibrary) const override;
|
||||
bool canBeUsedExternally(bool _inLibrary) const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
|
||||
bool recursive() const
|
||||
{
|
||||
if (m_recursive.is_initialized())
|
||||
return m_recursive.get();
|
||||
|
||||
interfaceType(false);
|
||||
|
||||
return m_recursive.get();
|
||||
}
|
||||
|
||||
TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
|
||||
@ -866,14 +886,11 @@ public:
|
||||
TypePointers memoryMemberTypes() const;
|
||||
/// @returns the set of all members that are removed in the memory version (typically mappings).
|
||||
std::set<std::string> membersMissingInMemory() const;
|
||||
|
||||
/// @returns true if the same struct is used recursively in one of its members. Only
|
||||
/// analyses the "memory" representation, i.e. mappings are ignored in all structs.
|
||||
bool recursive() const;
|
||||
|
||||
private:
|
||||
StructDefinition const& m_struct;
|
||||
/// Cache for the recursive() function.
|
||||
// Caches for interfaceType(bool)
|
||||
mutable boost::optional<TypeResult> m_interfaceType;
|
||||
mutable boost::optional<TypeResult> m_interfaceType_library;
|
||||
mutable boost::optional<bool> m_recursive;
|
||||
};
|
||||
|
||||
@ -893,6 +910,7 @@ public:
|
||||
return encodingType()->calldataEncodedSize(_padded);
|
||||
}
|
||||
unsigned storageBytes() const override;
|
||||
bool leftAligned() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
std::string toString(bool _short) const override;
|
||||
std::string canonicalName() const override;
|
||||
@ -903,7 +921,7 @@ public:
|
||||
{
|
||||
return std::make_shared<IntegerType>(8 * int(storageBytes()));
|
||||
}
|
||||
TypePointer interfaceType(bool _inLibrary) const override
|
||||
TypeResult interfaceType(bool _inLibrary) const override
|
||||
{
|
||||
return _inLibrary ? shared_from_this() : encodingType();
|
||||
}
|
||||
@ -1092,6 +1110,7 @@ public:
|
||||
unsigned calldataEncodedSize(bool _padded) const override;
|
||||
bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||
u256 storageSize() const override;
|
||||
bool leftAligned() const override;
|
||||
unsigned storageBytes() const override;
|
||||
bool isValueType() const override { return true; }
|
||||
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||
@ -1099,7 +1118,7 @@ public:
|
||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
TypePointer encodingType() const override;
|
||||
TypePointer interfaceType(bool _inLibrary) const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
|
||||
/// @returns TypePointer of a new FunctionType object. All input/return parameters are an
|
||||
/// appropriate external types (i.e. the interfaceType()s) of input/return parameters of
|
||||
@ -1108,11 +1127,15 @@ public:
|
||||
/// external type.
|
||||
FunctionTypePointer interfaceFunctionType() const;
|
||||
|
||||
/// @returns true if this function can take the given argument types (possibly
|
||||
/// @returns true if this function can take the given arguments (possibly
|
||||
/// after implicit conversion).
|
||||
/// @param _selfType if the function is bound, this has to be supplied and is the type of the
|
||||
/// expression the function is called on.
|
||||
bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const;
|
||||
bool canTakeArguments(
|
||||
FuncCallArguments const& _arguments,
|
||||
TypePointer const& _selfType = TypePointer()
|
||||
) const;
|
||||
|
||||
/// @returns true if the types of parameters are equal (does not check return parameter types)
|
||||
bool hasEqualParameterTypes(FunctionType const& _other) const;
|
||||
/// @returns true iff the return types are equal (does not check parameter types)
|
||||
@ -1220,10 +1243,7 @@ public:
|
||||
{
|
||||
return std::make_shared<IntegerType>(256);
|
||||
}
|
||||
TypePointer interfaceType(bool _inLibrary) const override
|
||||
{
|
||||
return _inLibrary ? shared_from_this() : TypePointer();
|
||||
}
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
|
||||
/// Cannot be stored in memory, but just in case.
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
|
@ -63,7 +63,6 @@ string ABIFunctions::tupleEncoder(
|
||||
templ("functionName", functionName);
|
||||
size_t const headSize_ = headSize(_targetTypes);
|
||||
templ("headSize", to_string(headSize_));
|
||||
string valueParams;
|
||||
string encodeElements;
|
||||
size_t headPos = 0;
|
||||
size_t stackPos = 0;
|
||||
@ -72,13 +71,6 @@ string ABIFunctions::tupleEncoder(
|
||||
solAssert(_givenTypes[i], "");
|
||||
solAssert(_targetTypes[i], "");
|
||||
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
|
||||
string valueNames = "";
|
||||
for (size_t j = 0; j < sizeOnStack; j++)
|
||||
{
|
||||
valueNames += "value" + to_string(stackPos) + ", ";
|
||||
valueParams = ", value" + to_string(stackPos) + valueParams;
|
||||
stackPos++;
|
||||
}
|
||||
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
|
||||
Whiskers elementTempl(
|
||||
dynamic ?
|
||||
@ -90,14 +82,17 @@ string ABIFunctions::tupleEncoder(
|
||||
<abiEncode>(<values> add(headStart, <pos>))
|
||||
)")
|
||||
);
|
||||
elementTempl("values", valueNames);
|
||||
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
elementTempl("values", values.empty() ? "" : values + ", ");
|
||||
elementTempl("pos", to_string(headPos));
|
||||
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
|
||||
encodeElements += elementTempl.render();
|
||||
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
|
||||
stackPos += sizeOnStack;
|
||||
}
|
||||
solAssert(headPos == headSize_, "");
|
||||
templ("valueParams", valueParams);
|
||||
string valueParams = suffixedVariableNameList("value", stackPos, 0);
|
||||
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
|
||||
templ("encodeElements", encodeElements);
|
||||
|
||||
return templ.render();
|
||||
@ -134,7 +129,6 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
string valueParams;
|
||||
string encodeElements;
|
||||
size_t stackPos = 0;
|
||||
for (size_t i = 0; i < _givenTypes.size(); ++i)
|
||||
@ -142,13 +136,6 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
solAssert(_givenTypes[i], "");
|
||||
solAssert(_targetTypes[i], "");
|
||||
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
|
||||
string valueNames = "";
|
||||
for (size_t j = 0; j < sizeOnStack; j++)
|
||||
{
|
||||
valueNames += "value" + to_string(stackPos) + ", ";
|
||||
valueParams = ", value" + to_string(stackPos) + valueParams;
|
||||
stackPos++;
|
||||
}
|
||||
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
|
||||
Whiskers elementTempl(
|
||||
dynamic ?
|
||||
@ -160,13 +147,16 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
pos := add(pos, <calldataEncodedSize>)
|
||||
)")
|
||||
);
|
||||
elementTempl("values", valueNames);
|
||||
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
elementTempl("values", values.empty() ? "" : values + ", ");
|
||||
if (!dynamic)
|
||||
elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
|
||||
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
|
||||
encodeElements += elementTempl.render();
|
||||
stackPos += sizeOnStack;
|
||||
}
|
||||
templ("valueParams", valueParams);
|
||||
string valueParams = suffixedVariableNameList("value", stackPos, 0);
|
||||
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
|
||||
templ("encodeElements", encodeElements);
|
||||
|
||||
return templ.render();
|
||||
@ -356,6 +346,39 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
||||
return createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
|
||||
unsigned storageBytes = _type.storageBytes();
|
||||
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
|
||||
if (type->isSigned() && storageBytes != 32)
|
||||
{
|
||||
templ("body", "cleaned := signextend(" + to_string(storageBytes - 1) + ", value)");
|
||||
return templ.render();
|
||||
}
|
||||
|
||||
if (storageBytes == 32)
|
||||
templ("body", "cleaned := value");
|
||||
else if (_type.leftAligned())
|
||||
templ("body", "cleaned := " + m_utils.shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
|
||||
else
|
||||
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
{
|
||||
string functionName =
|
||||
@ -636,29 +659,32 @@ string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
|
||||
_targetType.identifier() +
|
||||
_options.toFunctionNameSuffix();
|
||||
return createFunction(functionName, [&]() {
|
||||
string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options));
|
||||
string encoder = abiEncodingFunction(_givenType, _targetType, _options);
|
||||
if (_targetType.isDynamicallyEncoded())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value, pos) -> updatedPos {
|
||||
updatedPos := <encode>(value, pos)
|
||||
function <functionName>(<values>, pos) -> updatedPos {
|
||||
updatedPos := <encode>(<values>, pos)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("encode", encoder)
|
||||
("values", values)
|
||||
.render();
|
||||
else
|
||||
{
|
||||
unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded);
|
||||
solAssert(encodedSize != 0, "Invalid encoded size.");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value, pos) -> updatedPos {
|
||||
<encode>(value, pos)
|
||||
function <functionName>(<values>, pos) -> updatedPos {
|
||||
<encode>(<values>, pos)
|
||||
updatedPos := add(pos, <encodedSize>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("encode", encoder)
|
||||
("encodedSize", toCompactHexWithPrefix(encodedSize))
|
||||
("values", values)
|
||||
.render();
|
||||
}
|
||||
});
|
||||
@ -785,7 +811,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
subOptions.padded = true;
|
||||
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
|
||||
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||
if (inMemory)
|
||||
templ("arrayElementAccess", "mload(srcPtr)");
|
||||
else if (_from.baseType()->isValueType())
|
||||
{
|
||||
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
|
||||
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
|
||||
}
|
||||
else
|
||||
templ("arrayElementAccess", "srcPtr");
|
||||
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
|
||||
return templ.render();
|
||||
});
|
||||
@ -893,8 +927,9 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
size_t storageBytes = _from.baseType()->storageBytes();
|
||||
size_t itemsPerSlot = 32 / storageBytes;
|
||||
// This always writes full slot contents to memory, which might be
|
||||
// more than desired, i.e. it always writes beyond the end of memory.
|
||||
solAssert(itemsPerSlot > 0, "");
|
||||
// The number of elements we need to handle manually after the loop.
|
||||
size_t spill = size_t(_from.length() % itemsPerSlot);
|
||||
Whiskers templ(
|
||||
R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
@ -903,16 +938,31 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
pos := <storeLength>(pos, length)
|
||||
let originalPos := pos
|
||||
let srcPtr := <dataArea>(value)
|
||||
for { let i := 0 } lt(i, length) { i := add(i, <itemsPerSlot>) }
|
||||
{
|
||||
let itemCounter := 0
|
||||
if <useLoop> {
|
||||
// Run the loop over all full slots
|
||||
for { } lt(add(itemCounter, sub(<itemsPerSlot>, 1)), length)
|
||||
{ itemCounter := add(itemCounter, <itemsPerSlot>) }
|
||||
{
|
||||
let data := sload(srcPtr)
|
||||
<#items>
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
</items>
|
||||
srcPtr := add(srcPtr, 1)
|
||||
}
|
||||
}
|
||||
// Handle the last (not necessarily full) slot specially
|
||||
if <useSpill> {
|
||||
let data := sload(srcPtr)
|
||||
<#items>
|
||||
<encodeToMemoryFun>(<shiftRightFun>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
if <inRange> {
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
itemCounter := add(itemCounter, 1)
|
||||
}
|
||||
</items>
|
||||
srcPtr := add(srcPtr, 1)
|
||||
}
|
||||
pos := add(originalPos, mul(length, <elementEncodedSize>))
|
||||
<assignEnd>
|
||||
}
|
||||
)"
|
||||
@ -925,6 +975,15 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("dataArea", m_utils.arrayDataAreaFunction(_from));
|
||||
// We skip the loop for arrays that fit a single slot.
|
||||
if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot)
|
||||
templ("useLoop", "1");
|
||||
else
|
||||
templ("useLoop", "0");
|
||||
if (_from.isDynamicallySized() || spill != 0)
|
||||
templ("useSpill", "1");
|
||||
else
|
||||
templ("useSpill", "0");
|
||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||
// We use padded size because array elements are always padded.
|
||||
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
||||
@ -941,7 +1000,15 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("encodeToMemoryFun", encodeToMemoryFun);
|
||||
std::vector<std::map<std::string, std::string>> items(itemsPerSlot);
|
||||
for (size_t i = 0; i < itemsPerSlot; ++i)
|
||||
items[i]["shiftRightFun"] = m_utils.shiftRightFunction(i * storageBytes * 8);
|
||||
{
|
||||
if (_from.isDynamicallySized())
|
||||
items[i]["inRange"] = "lt(itemCounter, length)";
|
||||
else if (i < spill)
|
||||
items[i]["inRange"] = "1";
|
||||
else
|
||||
items[i]["inRange"] = "0";
|
||||
items[i]["extractFromSlot"] = extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
|
||||
}
|
||||
templ("items", items);
|
||||
return templ.render();
|
||||
}
|
||||
@ -1027,7 +1094,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
members.back()["retrieveValue"] = m_utils.shiftRightFunction(intraSlotOffset * 8) + "(slotValue)";
|
||||
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1516,6 +1583,52 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
string functionName =
|
||||
"read_from_storage_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
solAssert(_type.sizeOnStack() == 1, "");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> value {
|
||||
value := <extract>(sload(slot))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("extract", extractFromStorageValue(_type, _offset, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName =
|
||||
"extract_from_storage_value_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot_value) -> value {
|
||||
value := <cleanupStorage>(<shr>(slot_value))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr", m_utils.shiftRightFunction(_offset * 8))
|
||||
("cleanupStorage", cleanupFromStorageFunction(_type, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
|
||||
{
|
||||
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
|
||||
@ -1566,3 +1679,28 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
||||
return headSize;
|
||||
}
|
||||
|
||||
string ABIFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
|
||||
{
|
||||
string result;
|
||||
if (_startSuffix < _endSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_startSuffix++);
|
||||
while (_startSuffix < _endSuffix)
|
||||
result += ", " + _baseName + to_string(_startSuffix++);
|
||||
}
|
||||
else if (_endSuffix < _startSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_endSuffix++);
|
||||
while (_endSuffix < _startSuffix)
|
||||
result = _baseName + to_string(_endSuffix++) + ", " + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions const& _options)
|
||||
{
|
||||
if (_type.category() == Type::Category::Function && !_options.encodeFunctionFromStack)
|
||||
return 1;
|
||||
else
|
||||
return _type.sizeOnStack();
|
||||
}
|
||||
|
@ -134,6 +134,15 @@ private:
|
||||
/// otherwise an assertion failure.
|
||||
std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false);
|
||||
|
||||
/// Performs cleanup after reading from a potentially compressed storage slot.
|
||||
/// The function does not perform any validation, it just masks or sign-extends
|
||||
/// higher order bytes or left-aligns (in case of bytesNN).
|
||||
/// The storage cleanup expects the value to be right-aligned with potentially
|
||||
/// dirty higher order bytes.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of the 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
|
||||
/// (i.e. "clean"). Asserts on failure.
|
||||
@ -233,6 +242,19 @@ private:
|
||||
/// Part of @a abiDecodingFunction for array types.
|
||||
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
|
||||
|
||||
/// @returns a function that reads a value type from storage.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns a function that extracts a value type from storage slot that has been
|
||||
/// retrieved already.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of a function used during encoding that stores the length
|
||||
/// if the array is dynamically sized (and the options do not request in-place encoding).
|
||||
/// It returns the new encoding position.
|
||||
@ -253,6 +275,18 @@ private:
|
||||
/// @returns the size of the static part of the encoding of the given types.
|
||||
static size_t headSize(TypePointers const& _targetTypes);
|
||||
|
||||
/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
|
||||
/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
|
||||
/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.
|
||||
/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
|
||||
static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix);
|
||||
|
||||
/// @returns the number of variables needed to store a type.
|
||||
/// This is one for almost all types. The exception being dynamically sized calldata arrays or
|
||||
/// external function types (if we are encoding from stack, i.e. _options.encodeFunctionFromStack
|
||||
/// is true), for which it is two.
|
||||
static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options);
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
|
@ -331,7 +331,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
vector<string> const& _localVariables,
|
||||
set<string> const& _externallyUsedFunctions,
|
||||
bool _system,
|
||||
bool _optimise
|
||||
OptimiserSettings const& _optimiserSettings
|
||||
)
|
||||
{
|
||||
int startStackHeight = stackHeight();
|
||||
@ -422,12 +422,13 @@ void CompilerContext::appendInlineAssembly(
|
||||
|
||||
// Several optimizer steps cannot handle externally supplied stack variables,
|
||||
// so we essentially only optimize the ABI functions.
|
||||
if (_optimise && _localVariables.empty())
|
||||
if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
|
||||
{
|
||||
yul::OptimiserSuite::run(
|
||||
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
|
||||
*parserResult,
|
||||
analysisInfo,
|
||||
_optimiserSettings.optimizeStackAllocation,
|
||||
externallyUsedIdentifiers
|
||||
);
|
||||
analysisInfo = yul::AsmAnalysisInfo{};
|
||||
@ -439,13 +440,25 @@ void CompilerContext::appendInlineAssembly(
|
||||
identifierAccess.resolve
|
||||
).analyze(*parserResult))
|
||||
reportError("Optimizer introduced error into inline assembly.");
|
||||
#ifdef SOL_OUTPUT_ASM
|
||||
cout << "After optimizer: " << endl;
|
||||
cout << yul::AsmPrinter()(*parserResult) << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!errorReporter.errors().empty())
|
||||
reportError("Failed to analyze inline assembly block.");
|
||||
|
||||
solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block.");
|
||||
yul::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, m_evmVersion, identifierAccess, _system, _optimise);
|
||||
yul::CodeGenerator::assemble(
|
||||
*parserResult,
|
||||
analysisInfo,
|
||||
*m_asm,
|
||||
m_evmVersion,
|
||||
identifierAccess,
|
||||
_system,
|
||||
_optimiserSettings.optimizeStackAllocation
|
||||
);
|
||||
|
||||
// Reset the source location to the one of the node (instead of the CODEGEN source location)
|
||||
updateSourceLocation();
|
||||
|
@ -217,7 +217,7 @@ public:
|
||||
std::vector<std::string> const& _localVariables = std::vector<std::string>(),
|
||||
std::set<std::string> const& _externallyUsedFunctions = std::set<std::string>(),
|
||||
bool _system = false,
|
||||
bool _optimise = false
|
||||
OptimiserSettings const& _optimiserSettings = OptimiserSettings::none()
|
||||
);
|
||||
|
||||
/// Appends arbitrary data to the end of the bytecode.
|
||||
|
@ -41,7 +41,6 @@ unsigned const CompilerUtils::dataStartOffset = 4;
|
||||
size_t const CompilerUtils::freeMemoryPointer = 64;
|
||||
size_t const CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32;
|
||||
size_t const CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32;
|
||||
unsigned const CompilerUtils::identityContractAddress = 4;
|
||||
|
||||
static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area.");
|
||||
static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer.");
|
||||
|
@ -298,9 +298,6 @@ public:
|
||||
static size_t const generalPurposeMemoryStart;
|
||||
|
||||
private:
|
||||
/// Address of the precompiled identity contract.
|
||||
static unsigned const identityContractAddress;
|
||||
|
||||
/// Appends code that cleans higher-order bits for integer types.
|
||||
void cleanHigherOrderBits(IntegerType const& _typeOnStack);
|
||||
|
||||
|
@ -720,7 +720,9 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
*_inlineAssembly.annotation().analysisInfo,
|
||||
*m_context.assemblyPtr(),
|
||||
m_context.evmVersion(),
|
||||
identifierAccess
|
||||
identifierAccess,
|
||||
false,
|
||||
m_optimiserSettings.optimizeStackAllocation
|
||||
);
|
||||
m_context.setStackOffset(startStackHeight);
|
||||
return false;
|
||||
@ -983,7 +985,7 @@ void ContractCompiler::appendMissingFunctions()
|
||||
{},
|
||||
abiFunctions.second,
|
||||
true,
|
||||
m_optimiserSettings.runYulOptimiser
|
||||
m_optimiserSettings
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1394,7 +1394,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
|
||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
|
||||
utils().allocateMemory(contract.name().length() + 32);
|
||||
utils().allocateMemory(((contract.name().length() + 31) / 32) * 32 + 32);
|
||||
// store string length
|
||||
m_context << u256(contract.name().length()) << Instruction::DUP2 << Instruction::MSTORE;
|
||||
// adjust pointer
|
||||
|
@ -68,6 +68,7 @@ public:
|
||||
|
||||
std::string shiftLeftFunction(size_t _numBits);
|
||||
std::string shiftRightFunction(size_t _numBits);
|
||||
|
||||
/// @returns the name of a function that rounds its input to the next multiple
|
||||
/// of 32 or the input if it is a multiple of 32.
|
||||
std::string roundUpFunction();
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <libdevcore/StringUtils.h>
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include <boost/range/adaptors.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
using namespace std;
|
||||
@ -92,19 +93,26 @@ void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
|
||||
assignment(_varDecl, *_varDecl.value(), _varDecl.location());
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(ModifierDefinition const&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (!_function.modifiers().empty() || _function.isConstructor())
|
||||
if (_function.isConstructor())
|
||||
m_errorReporter.warning(
|
||||
_function.location(),
|
||||
"Assertion checker does not yet support constructors and functions with modifiers."
|
||||
"Assertion checker does not yet support constructors."
|
||||
);
|
||||
m_functionPath.push_back(&_function);
|
||||
m_modifierDepthStack.push_back(-1);
|
||||
// Not visited by a function call
|
||||
if (isRootFunction())
|
||||
{
|
||||
m_interface->reset();
|
||||
m_pathConditions.clear();
|
||||
m_callStack.clear();
|
||||
m_expressions.clear();
|
||||
m_globalContext.clear();
|
||||
m_uninterpretedTerms.clear();
|
||||
@ -114,7 +122,54 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
|
||||
m_loopExecutionHappened = false;
|
||||
m_arrayAssignmentHappened = false;
|
||||
}
|
||||
_function.parameterList().accept(*this);
|
||||
if (_function.returnParameterList())
|
||||
_function.returnParameterList()->accept(*this);
|
||||
visitFunctionOrModifier();
|
||||
return false;
|
||||
}
|
||||
|
||||
void SMTChecker::visitFunctionOrModifier()
|
||||
{
|
||||
solAssert(!m_functionPath.empty(), "");
|
||||
solAssert(!m_modifierDepthStack.empty(), "");
|
||||
|
||||
++m_modifierDepthStack.back();
|
||||
FunctionDefinition const& function = *m_functionPath.back();
|
||||
|
||||
if (m_modifierDepthStack.back() == int(function.modifiers().size()))
|
||||
{
|
||||
if (function.isImplemented())
|
||||
function.body().accept(*this);
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(m_modifierDepthStack.back() < int(function.modifiers().size()), "");
|
||||
ASTPointer<ModifierInvocation> const& modifierInvocation = function.modifiers()[m_modifierDepthStack.back()];
|
||||
solAssert(modifierInvocation, "");
|
||||
modifierInvocation->accept(*this);
|
||||
auto const& modifierDef = dynamic_cast<ModifierDefinition const&>(
|
||||
*modifierInvocation->name()->annotation().referencedDeclaration
|
||||
);
|
||||
vector<smt::Expression> modifierArgsExpr;
|
||||
if (modifierInvocation->arguments())
|
||||
for (auto arg: *modifierInvocation->arguments())
|
||||
modifierArgsExpr.push_back(expr(*arg));
|
||||
initializeFunctionCallParameters(modifierDef, modifierArgsExpr);
|
||||
pushCallStack(modifierInvocation.get());
|
||||
modifierDef.body().accept(*this);
|
||||
popCallStack();
|
||||
}
|
||||
|
||||
--m_modifierDepthStack.back();
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(PlaceholderStatement const&)
|
||||
{
|
||||
solAssert(!m_functionPath.empty(), "");
|
||||
ASTNode const* lastCall = popCallStack();
|
||||
visitFunctionOrModifier();
|
||||
pushCallStack(lastCall);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -129,8 +184,11 @@ void SMTChecker::endVisit(FunctionDefinition const&)
|
||||
{
|
||||
checkUnderOverflow();
|
||||
removeLocalVariables();
|
||||
solAssert(m_callStack.empty(), "");
|
||||
}
|
||||
m_functionPath.pop_back();
|
||||
solAssert(m_modifierDepthStack.back() == -1, "");
|
||||
m_modifierDepthStack.pop_back();
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(IfStatement const& _node)
|
||||
@ -331,7 +389,8 @@ void SMTChecker::addOverflowTarget(
|
||||
std::move(_intType),
|
||||
std::move(_value),
|
||||
currentPathConditions(),
|
||||
_location
|
||||
_location,
|
||||
m_callStack
|
||||
);
|
||||
}
|
||||
|
||||
@ -339,10 +398,12 @@ void SMTChecker::checkUnderOverflow()
|
||||
{
|
||||
for (auto& target: m_overflowTargets)
|
||||
{
|
||||
swap(m_callStack, target.callStack);
|
||||
if (target.type != OverflowTarget::Type::Overflow)
|
||||
checkUnderflow(target);
|
||||
if (target.type != OverflowTarget::Type::Underflow)
|
||||
checkOverflow(target);
|
||||
swap(m_callStack, target.callStack);
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,7 +556,9 @@ void SMTChecker::endVisit(FunctionCall const& _funCall)
|
||||
visitGasLeft(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Internal:
|
||||
pushCallStack(&_funCall);
|
||||
inlineFunctionCall(_funCall);
|
||||
popCallStack();
|
||||
break;
|
||||
case FunctionType::Kind::External:
|
||||
resetStateVariables();
|
||||
@ -731,7 +794,7 @@ void SMTChecker::endVisit(Literal const& _literal)
|
||||
|
||||
void SMTChecker::endVisit(Return const& _return)
|
||||
{
|
||||
if (knownExpr(*_return.expression()))
|
||||
if (_return.expression() && knownExpr(*_return.expression()))
|
||||
{
|
||||
auto returnParams = m_functionPath.back()->returnParameters();
|
||||
if (returnParams.size() > 1)
|
||||
@ -1165,18 +1228,21 @@ void SMTChecker::checkCondition(
|
||||
vector<string> values;
|
||||
tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate);
|
||||
|
||||
string loopComment;
|
||||
string extraComment;
|
||||
if (m_loopExecutionHappened)
|
||||
loopComment =
|
||||
extraComment =
|
||||
"\nNote that some information is erased after the execution of loops.\n"
|
||||
"You can re-introduce information using require().";
|
||||
if (m_arrayAssignmentHappened)
|
||||
loopComment +=
|
||||
extraComment +=
|
||||
"\nNote that array aliasing is not supported,"
|
||||
" therefore all mapping information is erased after"
|
||||
" a mapping local variable/parameter is assigned.\n"
|
||||
"You can re-introduce information using require().";
|
||||
|
||||
SecondarySourceLocation secondaryLocation{};
|
||||
secondaryLocation.append(extraComment, SourceLocation{});
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case smt::CheckResult::SATISFIABLE:
|
||||
@ -1195,19 +1261,25 @@ void SMTChecker::checkCondition(
|
||||
|
||||
for (auto const& eval: sortedModel)
|
||||
modelMessage << " " << eval.first << " = " << eval.second << "\n";
|
||||
m_errorReporter.warning(_location, message.str(), SecondarySourceLocation().append(modelMessage.str(), SourceLocation()).append(loopComment, SourceLocation()));
|
||||
m_errorReporter.warning(
|
||||
_location,
|
||||
message.str(),
|
||||
SecondarySourceLocation().append(modelMessage.str(), SourceLocation{})
|
||||
.append(currentCallStack())
|
||||
.append(move(secondaryLocation))
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
message << ".";
|
||||
m_errorReporter.warning(_location, message.str(), SecondarySourceLocation().append(loopComment, SourceLocation()));
|
||||
m_errorReporter.warning(_location, message.str(), secondaryLocation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case smt::CheckResult::UNSATISFIABLE:
|
||||
break;
|
||||
case smt::CheckResult::UNKNOWN:
|
||||
m_errorReporter.warning(_location, _description + " might happen here.", SecondarySourceLocation().append(loopComment, SourceLocation()));
|
||||
m_errorReporter.warning(_location, _description + " might happen here.", secondaryLocation);
|
||||
break;
|
||||
case smt::CheckResult::CONFLICTING:
|
||||
m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
@ -1248,7 +1320,7 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co
|
||||
// can't do anything.
|
||||
}
|
||||
else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE)
|
||||
m_errorReporter.warning(_condition.location(), "Condition unreachable.");
|
||||
m_errorReporter.warning(_condition.location(), "Condition unreachable.", currentCallStack());
|
||||
else
|
||||
{
|
||||
string value;
|
||||
@ -1263,7 +1335,11 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co
|
||||
solAssert(negatedResult == smt::CheckResult::SATISFIABLE, "");
|
||||
value = "false";
|
||||
}
|
||||
m_errorReporter.warning(_condition.location(), boost::algorithm::replace_all_copy(_description, "$VALUE", value));
|
||||
m_errorReporter.warning(
|
||||
_condition.location(),
|
||||
boost::algorithm::replace_all_copy(_description, "$VALUE", value),
|
||||
currentCallStack()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1303,7 +1379,7 @@ smt::CheckResult SMTChecker::checkSatisfiable()
|
||||
return checkSatisfiableAndGenerateModel({}).first;
|
||||
}
|
||||
|
||||
void SMTChecker::initializeFunctionCallParameters(FunctionDefinition const& _function, vector<smt::Expression> const& _callArgs)
|
||||
void SMTChecker::initializeFunctionCallParameters(CallableDeclaration const& _function, vector<smt::Expression> const& _callArgs)
|
||||
{
|
||||
auto const& funParams = _function.parameters();
|
||||
solAssert(funParams.size() == _callArgs.size(), "");
|
||||
@ -1540,6 +1616,30 @@ smt::Expression SMTChecker::currentPathConditions()
|
||||
return m_pathConditions.back();
|
||||
}
|
||||
|
||||
SecondarySourceLocation SMTChecker::currentCallStack()
|
||||
{
|
||||
SecondarySourceLocation callStackLocation;
|
||||
if (m_callStack.empty())
|
||||
return callStackLocation;
|
||||
callStackLocation.append("Callstack: ", SourceLocation());
|
||||
for (auto const& call: m_callStack | boost::adaptors::reversed)
|
||||
callStackLocation.append("", call->location());
|
||||
return callStackLocation;
|
||||
}
|
||||
|
||||
ASTNode const* SMTChecker::popCallStack()
|
||||
{
|
||||
solAssert(!m_callStack.empty(), "");
|
||||
ASTNode const* lastCalled = m_callStack.back();
|
||||
m_callStack.pop_back();
|
||||
return lastCalled;
|
||||
}
|
||||
|
||||
void SMTChecker::pushCallStack(ASTNode const* _node)
|
||||
{
|
||||
m_callStack.push_back(_node);
|
||||
}
|
||||
|
||||
void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e)
|
||||
{
|
||||
m_interface->addAssertion(currentPathConditions() && _e);
|
||||
|
@ -63,8 +63,10 @@ private:
|
||||
bool visit(ContractDefinition const& _node) override;
|
||||
void endVisit(ContractDefinition const& _node) override;
|
||||
void endVisit(VariableDeclaration const& _node) override;
|
||||
bool visit(ModifierDefinition const& _node) override;
|
||||
bool visit(FunctionDefinition const& _node) override;
|
||||
void endVisit(FunctionDefinition const& _node) override;
|
||||
bool visit(PlaceholderStatement const& _node) override;
|
||||
bool visit(IfStatement const& _node) override;
|
||||
bool visit(WhileStatement const& _node) override;
|
||||
bool visit(ForStatement const& _node) override;
|
||||
@ -100,6 +102,10 @@ private:
|
||||
void abstractFunctionCall(FunctionCall const& _funCall);
|
||||
void visitFunctionIdentifier(Identifier const& _identifier);
|
||||
|
||||
/// Encodes a modifier or function body according to the modifier
|
||||
/// visit depth.
|
||||
void visitFunctionOrModifier();
|
||||
|
||||
void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false);
|
||||
void defineGlobalFunction(std::string const& _name, Expression const& _expr);
|
||||
/// Handles the side effects of assignment
|
||||
@ -148,13 +154,15 @@ private:
|
||||
smt::Expression value;
|
||||
smt::Expression path;
|
||||
langutil::SourceLocation const& location;
|
||||
std::vector<ASTNode const*> callStack;
|
||||
|
||||
OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location):
|
||||
OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<ASTNode const*> _callStack):
|
||||
type(_type),
|
||||
intType(_intType),
|
||||
value(_value),
|
||||
path(_path),
|
||||
location(_location)
|
||||
location(_location),
|
||||
callStack(move(_callStack))
|
||||
{
|
||||
solAssert(dynamic_cast<IntegerType const*>(intType.get()), "");
|
||||
}
|
||||
@ -174,7 +182,7 @@ private:
|
||||
smt::CheckResult checkSatisfiable();
|
||||
|
||||
void initializeLocalVariables(FunctionDefinition const& _function);
|
||||
void initializeFunctionCallParameters(FunctionDefinition const& _function, std::vector<smt::Expression> const& _callArgs);
|
||||
void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smt::Expression> const& _callArgs);
|
||||
void resetVariable(VariableDeclaration const& _variable);
|
||||
void resetStateVariables();
|
||||
void resetStorageReferences();
|
||||
@ -229,6 +237,12 @@ private:
|
||||
void popPathCondition();
|
||||
/// Returns the conjunction of all path conditions or True if empty
|
||||
smt::Expression currentPathConditions();
|
||||
/// Returns the current callstack. Used for models.
|
||||
langutil::SecondarySourceLocation currentCallStack();
|
||||
/// Copies and pops the last called node.
|
||||
ASTNode const* popCallStack();
|
||||
/// Adds @param _node to the callstack.
|
||||
void pushCallStack(ASTNode const* _node);
|
||||
/// Conjoin the current path conditions with the given parameter and add to the solver
|
||||
void addPathConjoinedExpression(smt::Expression const& _e);
|
||||
/// Add to the solver: the given expression implied by the current path conditions
|
||||
@ -269,6 +283,8 @@ private:
|
||||
|
||||
/// Stores the current path of function calls.
|
||||
std::vector<FunctionDefinition const*> m_functionPath;
|
||||
/// Stores the current call/invocation path.
|
||||
std::vector<ASTNode const*> m_callStack;
|
||||
/// Returns true if the current function was not visited by
|
||||
/// a function call.
|
||||
bool isRootFunction();
|
||||
@ -276,6 +292,12 @@ private:
|
||||
bool visitedFunction(FunctionDefinition const* _funDef);
|
||||
|
||||
std::vector<OverflowTarget> m_overflowTargets;
|
||||
|
||||
/// Depth of visit to modifiers.
|
||||
/// When m_modifierDepth == #modifiers the function can be visited
|
||||
/// when placeholder is visited.
|
||||
/// Needs to be a stack because of function calls.
|
||||
std::vector<int> m_modifierDepthStack;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -107,9 +107,9 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
for (auto const& p: it->parameters())
|
||||
{
|
||||
auto type = p->annotation().type->interfaceType(false);
|
||||
solAssert(type, "");
|
||||
solAssert(type.get(), "");
|
||||
Json::Value input;
|
||||
auto param = formatType(p->name(), *type, false);
|
||||
auto param = formatType(p->name(), *type.get(), false);
|
||||
param["indexed"] = p->isIndexed();
|
||||
params.append(param);
|
||||
}
|
||||
@ -173,8 +173,8 @@ Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLib
|
||||
{
|
||||
solAssert(member.type, "");
|
||||
auto t = member.type->interfaceType(_forLibrary);
|
||||
solAssert(t, "");
|
||||
ret["components"].append(formatType(member.name, *t, _forLibrary));
|
||||
solAssert(t.get(), "");
|
||||
ret["components"].append(formatType(member.name, *t.get(), _forLibrary));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -109,7 +109,7 @@ void CompilerStack::setLibraries(std::map<std::string, h160> const& _libraries)
|
||||
|
||||
void CompilerStack::setOptimiserSettings(bool _optimize, unsigned _runs)
|
||||
{
|
||||
OptimiserSettings settings = _optimize ? OptimiserSettings::enabled() : OptimiserSettings::minimal();
|
||||
OptimiserSettings settings = _optimize ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
||||
settings.expectedExecutionsPerDeployment = _runs;
|
||||
setOptimiserSettings(std::move(settings));
|
||||
}
|
||||
@ -160,12 +160,11 @@ void CompilerStack::reset(bool _keepSources)
|
||||
m_errorReporter.clear();
|
||||
}
|
||||
|
||||
bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary)
|
||||
bool CompilerStack::addSource(string const& _name, string const& _content)
|
||||
{
|
||||
bool existed = m_sources.count(_name) != 0;
|
||||
reset(true);
|
||||
m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content, _name));
|
||||
m_sources[_name].isLibrary = _isLibrary;
|
||||
m_stackState = SourcesSet;
|
||||
return existed;
|
||||
}
|
||||
@ -180,6 +179,12 @@ bool CompilerStack::parse()
|
||||
if (SemVerVersion{string(VersionString)}.isPrerelease())
|
||||
m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production.");
|
||||
|
||||
if (m_optimiserSettings.runYulOptimiser)
|
||||
m_errorReporter.warning(
|
||||
"The Yul optimiser is still experimental. "
|
||||
"Do not use it in production unless correctness of generated code is verified with extensive tests."
|
||||
);
|
||||
|
||||
vector<string> sourcesToParse;
|
||||
for (auto const& s: m_sources)
|
||||
sourcesToParse.push_back(s.first);
|
||||
@ -828,8 +833,7 @@ void CompilerStack::resolveImports()
|
||||
};
|
||||
|
||||
for (auto const& sourcePair: m_sources)
|
||||
if (!sourcePair.second.isLibrary)
|
||||
toposort(&sourcePair.second);
|
||||
toposort(&sourcePair.second);
|
||||
|
||||
swap(m_sourceOrder, sourceOrder);
|
||||
}
|
||||
@ -980,7 +984,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
settingsWithoutRuns.expectedExecutionsPerDeployment = OptimiserSettings::minimal().expectedExecutionsPerDeployment;
|
||||
if (settingsWithoutRuns == OptimiserSettings::minimal())
|
||||
meta["settings"]["optimizer"]["enabled"] = false;
|
||||
else if (settingsWithoutRuns == OptimiserSettings::enabled())
|
||||
else if (settingsWithoutRuns == OptimiserSettings::standard())
|
||||
meta["settings"]["optimizer"]["enabled"] = true;
|
||||
else
|
||||
{
|
||||
@ -993,7 +997,11 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
details["cse"] = m_optimiserSettings.runCSE;
|
||||
details["constantOptimizer"] = m_optimiserSettings.runConstantOptimiser;
|
||||
details["yul"] = m_optimiserSettings.runYulOptimiser;
|
||||
details["yulDetails"] = Json::objectValue;
|
||||
if (m_optimiserSettings.runYulOptimiser)
|
||||
{
|
||||
details["yulDetails"] = Json::objectValue;
|
||||
details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation;
|
||||
}
|
||||
|
||||
meta["settings"]["optimizer"]["details"] = std::move(details);
|
||||
}
|
||||
@ -1020,29 +1028,96 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
return jsonCompactPrint(meta);
|
||||
}
|
||||
|
||||
class MetadataCBOREncoder
|
||||
{
|
||||
public:
|
||||
void pushBytes(string const& key, bytes const& value)
|
||||
{
|
||||
m_entryCount++;
|
||||
pushTextString(key);
|
||||
pushByteString(value);
|
||||
}
|
||||
|
||||
void pushString(string const& key, string const& value)
|
||||
{
|
||||
m_entryCount++;
|
||||
pushTextString(key);
|
||||
pushTextString(value);
|
||||
}
|
||||
|
||||
void pushBool(string const& key, bool value)
|
||||
{
|
||||
m_entryCount++;
|
||||
pushTextString(key);
|
||||
pushBool(value);
|
||||
}
|
||||
|
||||
bytes serialise() const
|
||||
{
|
||||
unsigned size = m_data.size() + 1;
|
||||
solAssert(size <= 0xffff, "Metadata too large.");
|
||||
solAssert(m_entryCount <= 0x1f, "Too many map entries.");
|
||||
|
||||
// CBOR fixed-length map
|
||||
bytes ret{static_cast<unsigned char>(0xa0 + m_entryCount)};
|
||||
// The already encoded key-value pairs
|
||||
ret += m_data;
|
||||
// 16-bit big endian length
|
||||
ret += toCompactBigEndian(size, 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
void pushTextString(string const& key)
|
||||
{
|
||||
unsigned length = key.size();
|
||||
if (length < 24)
|
||||
{
|
||||
m_data += bytes{static_cast<unsigned char>(0x60 + length)};
|
||||
m_data += key;
|
||||
}
|
||||
else if (length <= 256)
|
||||
{
|
||||
m_data += bytes{0x78, static_cast<unsigned char>(length)};
|
||||
m_data += key;
|
||||
}
|
||||
else
|
||||
solAssert(false, "Text string too large.");
|
||||
}
|
||||
void pushByteString(bytes const& key)
|
||||
{
|
||||
unsigned length = key.size();
|
||||
if (length < 24)
|
||||
{
|
||||
m_data += bytes{static_cast<unsigned char>(0x40 + length)};
|
||||
m_data += key;
|
||||
}
|
||||
else if (length <= 256)
|
||||
{
|
||||
m_data += bytes{0x58, static_cast<unsigned char>(length)};
|
||||
m_data += key;
|
||||
}
|
||||
else
|
||||
solAssert(false, "Byte string too large.");
|
||||
}
|
||||
void pushBool(bool value)
|
||||
{
|
||||
if (value)
|
||||
m_data += bytes{0xf5};
|
||||
else
|
||||
m_data += bytes{0xf4};
|
||||
}
|
||||
unsigned m_entryCount = 0;
|
||||
bytes m_data;
|
||||
};
|
||||
|
||||
bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimentalMode)
|
||||
{
|
||||
bytes cborEncodedHash =
|
||||
// CBOR-encoding of the key "bzzr0"
|
||||
bytes{0x65, 'b', 'z', 'z', 'r', '0'}+
|
||||
// CBOR-encoding of the hash
|
||||
bytes{0x58, 0x20} + dev::swarmHash(_metadata).asBytes();
|
||||
bytes cborEncodedMetadata;
|
||||
MetadataCBOREncoder encoder;
|
||||
encoder.pushBytes("bzzr0", dev::swarmHash(_metadata).asBytes());
|
||||
if (_experimentalMode)
|
||||
cborEncodedMetadata =
|
||||
// CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true}
|
||||
bytes{0xa2} +
|
||||
cborEncodedHash +
|
||||
bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5};
|
||||
else
|
||||
cborEncodedMetadata =
|
||||
// CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)}
|
||||
bytes{0xa1} +
|
||||
cborEncodedHash;
|
||||
solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large");
|
||||
// 16-bit big endian length
|
||||
cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2);
|
||||
return cborEncodedMetadata;
|
||||
encoder.pushBool("experimental", true);
|
||||
return encoder.serialise();
|
||||
}
|
||||
|
||||
string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) const
|
||||
|
@ -150,7 +150,7 @@ public:
|
||||
|
||||
/// Adds a source object (e.g. file) to the parser. After this, parse has to be called again.
|
||||
/// @returns true if a source object by the name already existed and was replaced.
|
||||
bool addSource(std::string const& _name, std::string const& _content, bool _isLibrary = false);
|
||||
bool addSource(std::string const& _name, std::string const& _content);
|
||||
|
||||
/// Adds a response to an SMTLib2 query (identified by the hash of the query input).
|
||||
/// Must be set before parsing.
|
||||
@ -261,7 +261,6 @@ private:
|
||||
{
|
||||
std::shared_ptr<langutil::Scanner> scanner;
|
||||
std::shared_ptr<SourceUnit> ast;
|
||||
bool isLibrary = false;
|
||||
h256 mutable keccak256HashCached;
|
||||
h256 mutable swarmHashCached;
|
||||
void reset() { *this = Source(); }
|
||||
|
@ -45,7 +45,7 @@ struct OptimiserSettings
|
||||
return s;
|
||||
}
|
||||
/// Standard optimisations.
|
||||
static OptimiserSettings enabled()
|
||||
static OptimiserSettings standard()
|
||||
{
|
||||
OptimiserSettings s;
|
||||
s.runOrderLiterals = true;
|
||||
@ -54,15 +54,17 @@ struct OptimiserSettings
|
||||
s.runDeduplicate = true;
|
||||
s.runCSE = true;
|
||||
s.runConstantOptimiser = true;
|
||||
// The only disabled one
|
||||
// The only disabled ones
|
||||
s.optimizeStackAllocation = false;
|
||||
s.runYulOptimiser = false;
|
||||
s.expectedExecutionsPerDeployment = 200;
|
||||
return s;
|
||||
}
|
||||
/// Standard optimisations plus yul optimiser.
|
||||
/// Standard optimisations plus yul and stack optimiser.
|
||||
static OptimiserSettings full()
|
||||
{
|
||||
OptimiserSettings s = enabled();
|
||||
OptimiserSettings s = standard();
|
||||
s.optimizeStackAllocation = true;
|
||||
s.runYulOptimiser = true;
|
||||
return s;
|
||||
}
|
||||
@ -76,6 +78,7 @@ struct OptimiserSettings
|
||||
runDeduplicate == _other.runDeduplicate &&
|
||||
runCSE == _other.runCSE &&
|
||||
runConstantOptimiser == _other.runConstantOptimiser &&
|
||||
optimizeStackAllocation == _other.optimizeStackAllocation &&
|
||||
runYulOptimiser == _other.runYulOptimiser &&
|
||||
expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment;
|
||||
}
|
||||
@ -94,6 +97,8 @@ struct OptimiserSettings
|
||||
/// Constant optimizer, which tries to find better representations that satisfy the given
|
||||
/// size/cost-trade-off.
|
||||
bool runConstantOptimiser = false;
|
||||
/// Perform more efficient stack allocation for variables during code generation from Yul to bytecode.
|
||||
bool optimizeStackAllocation = false;
|
||||
/// Yul optimiser with default settings. Will only run on certain parts of the code for now.
|
||||
bool runYulOptimiser = false;
|
||||
/// This specifies an estimate on how often each opcode in this assembly will be executed,
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
|
||||
#include <libsolidity/ast/ASTJsonConverter.h>
|
||||
#include <libyul/AssemblyStack.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libdevcore/JSON.h>
|
||||
@ -37,6 +38,7 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace langutil;
|
||||
using namespace dev::solidity;
|
||||
using namespace yul;
|
||||
|
||||
namespace {
|
||||
|
||||
@ -354,7 +356,6 @@ boost::optional<Json::Value> checkOutputSelection(Json::Value const& _outputSele
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
/// Validates the optimizer settings and returns them in a parsed object.
|
||||
/// On error returns the json-formatted error message.
|
||||
boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value const& _jsonInput)
|
||||
@ -369,7 +370,7 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
|
||||
if (!_jsonInput["enabled"].isBool())
|
||||
return formatFatalError("JSONError", "The \"enabled\" setting must be a Boolean.");
|
||||
|
||||
settings = _jsonInput["enabled"].asBool() ? OptimiserSettings::enabled() : OptimiserSettings::minimal();
|
||||
settings = _jsonInput["enabled"].asBool() ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
||||
}
|
||||
|
||||
if (_jsonInput.isMember("runs"))
|
||||
@ -399,12 +400,17 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
|
||||
return *error;
|
||||
if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser))
|
||||
return *error;
|
||||
if (settings.runYulOptimiser)
|
||||
settings.optimizeStackAllocation = true;
|
||||
if (details.isMember("yulDetails"))
|
||||
{
|
||||
if (!_jsonInput["yulDetails"].isObject())
|
||||
return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting has to be a JSON object.");
|
||||
if (!_jsonInput["yulDetails"].getMemberNames().empty())
|
||||
return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet.");
|
||||
if (!settings.runYulOptimiser)
|
||||
return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled.");
|
||||
|
||||
if (auto result = checkKeys(details["yulDetails"], {"stackAllocation"}, "settings.optimizer.details.yulDetails"))
|
||||
return *result;
|
||||
if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation))
|
||||
return *error;
|
||||
}
|
||||
}
|
||||
return std::move(settings);
|
||||
@ -422,8 +428,7 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
|
||||
if (auto result = checkRootKeys(_input))
|
||||
return *result;
|
||||
|
||||
if (_input["language"] != "Solidity")
|
||||
return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language.");
|
||||
ret.language = _input["language"].asString();
|
||||
|
||||
Json::Value const& sources = _input["sources"];
|
||||
|
||||
@ -845,15 +850,95 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
|
||||
{
|
||||
if (_inputsAndSettings.sources.size() != 1)
|
||||
return formatFatalError("JSONError", "Yul mode only supports exactly one input file.");
|
||||
if (!_inputsAndSettings.smtLib2Responses.empty())
|
||||
return formatFatalError("JSONError", "Yul mode does not support smtlib2responses.");
|
||||
if (!_inputsAndSettings.remappings.empty())
|
||||
return formatFatalError("JSONError", "Field \"settings.remappings\" cannot be used for Yul.");
|
||||
if (!_inputsAndSettings.libraries.empty())
|
||||
return formatFatalError("JSONError", "Field \"settings.libraries\" cannot be used for Yul.");
|
||||
|
||||
Json::Value output = Json::objectValue;
|
||||
|
||||
AssemblyStack stack(_inputsAndSettings.evmVersion, AssemblyStack::Language::StrictAssembly);
|
||||
string const& sourceName = _inputsAndSettings.sources.begin()->first;
|
||||
string const& sourceContents = _inputsAndSettings.sources.begin()->second;
|
||||
|
||||
// Inconsistent state - stop here to receive error reports from users
|
||||
if (!stack.parseAndAnalyze(sourceName, sourceContents) && stack.errors().empty())
|
||||
return formatFatalError("InternalCompilerError", "No error reported, but compilation failed.");
|
||||
|
||||
if (!stack.errors().empty())
|
||||
{
|
||||
Json::Value errors = Json::arrayValue;
|
||||
for (auto const& error: stack.errors())
|
||||
{
|
||||
auto err = dynamic_pointer_cast<Error const>(error);
|
||||
|
||||
errors.append(formatErrorWithException(
|
||||
*error,
|
||||
err->type() == Error::Type::Warning,
|
||||
err->typeName(),
|
||||
"general",
|
||||
""
|
||||
));
|
||||
}
|
||||
output["errors"] = errors;
|
||||
return output;
|
||||
}
|
||||
|
||||
// TODO: move this warning to AssemblyStack
|
||||
output["errors"] = Json::arrayValue;
|
||||
output["errors"].append(formatError(true, "Warning", "general", "Yul is still experimental. Please use the output with care."));
|
||||
|
||||
string contractName = stack.parserResult()->name.str();
|
||||
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir"))
|
||||
output["contracts"][sourceName][contractName]["ir"] = stack.print();
|
||||
|
||||
if (_inputsAndSettings.optimiserSettings.runYulOptimiser)
|
||||
stack.optimize();
|
||||
|
||||
MachineAssemblyObject object = stack.assemble(
|
||||
AssemblyStack::Machine::EVM,
|
||||
_inputsAndSettings.optimiserSettings.optimizeStackAllocation
|
||||
);
|
||||
|
||||
if (isArtifactRequested(
|
||||
_inputsAndSettings.outputSelection,
|
||||
sourceName,
|
||||
contractName,
|
||||
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }
|
||||
))
|
||||
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr);
|
||||
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized"))
|
||||
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly"))
|
||||
output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
auto parsed = parseInput(_input);
|
||||
if (parsed.type() == typeid(InputsAndSettings))
|
||||
return compileSolidity(boost::get<InputsAndSettings>(std::move(parsed)));
|
||||
else
|
||||
if (parsed.type() == typeid(Json::Value))
|
||||
return boost::get<Json::Value>(std::move(parsed));
|
||||
InputsAndSettings settings = boost::get<InputsAndSettings>(std::move(parsed));
|
||||
if (settings.language == "Solidity")
|
||||
return compileSolidity(std::move(settings));
|
||||
else if (settings.language == "Yul")
|
||||
return compileYul(std::move(settings));
|
||||
else
|
||||
return formatFatalError("JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language.");
|
||||
}
|
||||
catch (Json::LogicError const& _exception)
|
||||
{
|
||||
|
@ -58,6 +58,7 @@ public:
|
||||
private:
|
||||
struct InputsAndSettings
|
||||
{
|
||||
std::string language;
|
||||
Json::Value errors;
|
||||
std::map<std::string, std::string> sources;
|
||||
std::map<h256, std::string> smtLib2Responses;
|
||||
@ -74,6 +75,7 @@ private:
|
||||
boost::variant<InputsAndSettings, Json::Value> parseInput(Json::Value const& _input);
|
||||
|
||||
Json::Value compileSolidity(InputsAndSettings _inputsAndSettings);
|
||||
Json::Value compileYul(InputsAndSettings _inputsAndSettings);
|
||||
|
||||
ReadCallback::Callback m_readFile;
|
||||
};
|
||||
|
@ -65,6 +65,8 @@ public:
|
||||
return make_shared<NodeType>(m_location, std::forward<Args>(_args)...);
|
||||
}
|
||||
|
||||
SourceLocation const& location() const noexcept { return m_location; }
|
||||
|
||||
private:
|
||||
Parser const& m_parser;
|
||||
SourceLocation m_location;
|
||||
@ -108,14 +110,15 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::parsePragmaVersion(vector<Token> const& tokens, vector<string> const& literals)
|
||||
void Parser::parsePragmaVersion(SourceLocation const& _location, vector<Token> const& _tokens, vector<string> const& _literals)
|
||||
{
|
||||
SemVerMatchExpressionParser parser(tokens, literals);
|
||||
SemVerMatchExpressionParser parser(_tokens, _literals);
|
||||
auto matchExpression = parser.parse();
|
||||
static SemVerVersion const currentVersion{string(VersionString)};
|
||||
// FIXME: only match for major version incompatibility
|
||||
if (!matchExpression.matches(currentVersion))
|
||||
fatalParserError(
|
||||
m_errorReporter.fatalParserError(
|
||||
_location,
|
||||
"Source file requires different compiler version (current compiler is " +
|
||||
string(VersionString) + " - note that nightly builds are considered to be "
|
||||
"strictly less than the released version"
|
||||
@ -154,6 +157,7 @@ ASTPointer<PragmaDirective> Parser::parsePragmaDirective()
|
||||
if (literals.size() >= 2 && literals[0] == "solidity")
|
||||
{
|
||||
parsePragmaVersion(
|
||||
nodeFactory.location(),
|
||||
vector<Token>(tokens.begin() + 1, tokens.end()),
|
||||
vector<string>(literals.begin() + 1, literals.end())
|
||||
);
|
||||
|
@ -73,7 +73,7 @@ private:
|
||||
|
||||
///@{
|
||||
///@name Parsing functions for the AST nodes
|
||||
void parsePragmaVersion(std::vector<Token> const& tokens, std::vector<std::string> const& literals);
|
||||
void parsePragmaVersion(langutil::SourceLocation const& _location, std::vector<Token> const& _tokens, std::vector<std::string> const& _literals);
|
||||
ASTPointer<PragmaDirective> parsePragmaDirective();
|
||||
ASTPointer<ImportDirective> parseImportDirective();
|
||||
ContractDefinition::ContractKind parseContractKind();
|
||||
|
@ -436,7 +436,7 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
|
||||
);
|
||||
}
|
||||
|
||||
set<Literal const*, Less<Literal*>> cases;
|
||||
set<u256> cases;
|
||||
for (auto const& _case: _switch.cases)
|
||||
{
|
||||
if (_case.value)
|
||||
@ -450,7 +450,7 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
|
||||
m_stackHeight--;
|
||||
|
||||
/// Note: the parser ensures there is only one default case
|
||||
if (!cases.insert(_case.value.get()).second)
|
||||
if (!cases.insert(valueOfLiteral(*_case.value)).second)
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
_case.location,
|
||||
|
@ -135,7 +135,10 @@ void AssemblyStack::optimize(Object& _object)
|
||||
for (auto& subNode: _object.subObjects)
|
||||
if (auto subObject = dynamic_cast<Object*>(subNode.get()))
|
||||
optimize(*subObject);
|
||||
OptimiserSuite::run(languageToDialect(m_language, m_evmVersion), *_object.code, *_object.analysisInfo);
|
||||
// TODO: Store this as setting - it should be the same as the flag passed to
|
||||
// ::assemble(...)
|
||||
bool optimizeStackAllocation = false;
|
||||
OptimiserSuite::run(languageToDialect(m_language, m_evmVersion), *_object.code, *_object.analysisInfo, optimizeStackAllocation);
|
||||
}
|
||||
|
||||
MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) const
|
||||
|
@ -74,7 +74,7 @@ public:
|
||||
|
||||
/// Run the assembly step (should only be called after parseAndAnalyze).
|
||||
/// @param _optimize does not run the optimizer but performs optimized code generation.
|
||||
MachineAssemblyObject assemble(Machine _machine, bool _optimize = false) const;
|
||||
MachineAssemblyObject assemble(Machine _machine, bool _optimize) const;
|
||||
|
||||
/// @returns the errors generated during parsing, analysis (and potentially assembly).
|
||||
langutil::ErrorList const& errors() const { return m_errors; }
|
||||
|
@ -33,7 +33,11 @@ using namespace yul;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
std::map<YulString, int> CompilabilityChecker::run(std::shared_ptr<Dialect> _dialect, Block const& _ast)
|
||||
map<YulString, int> CompilabilityChecker::run(
|
||||
shared_ptr<Dialect> _dialect,
|
||||
Block const& _ast,
|
||||
bool _optimizeStackAllocation
|
||||
)
|
||||
{
|
||||
if (_dialect->flavour == AsmFlavour::Yul)
|
||||
return {};
|
||||
@ -43,12 +47,11 @@ std::map<YulString, int> CompilabilityChecker::run(std::shared_ptr<Dialect> _dia
|
||||
solAssert(dynamic_cast<EVMDialect const*>(_dialect.get()), "");
|
||||
shared_ptr<NoOutputEVMDialect> noOutputDialect = make_shared<NoOutputEVMDialect>(dynamic_pointer_cast<EVMDialect>(_dialect));
|
||||
|
||||
bool optimize = true;
|
||||
yul::AsmAnalysisInfo analysisInfo =
|
||||
yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast);
|
||||
|
||||
NoOutputAssembly assembly;
|
||||
CodeTransform transform(assembly, analysisInfo, _ast, *noOutputDialect, optimize);
|
||||
CodeTransform transform(assembly, analysisInfo, _ast, *noOutputDialect, _optimizeStackAllocation);
|
||||
try
|
||||
{
|
||||
transform(_ast);
|
||||
|
@ -39,7 +39,11 @@ namespace yul
|
||||
class CompilabilityChecker
|
||||
{
|
||||
public:
|
||||
static std::map<YulString, int> run(std::shared_ptr<Dialect> _dialect, Block const& _ast);
|
||||
static std::map<YulString, int> run(
|
||||
std::shared_ptr<Dialect> _dialect,
|
||||
Block const& _ast,
|
||||
bool _optimizeStackAllocation
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ void CodeGenerator::assemble(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
ExternalIdentifierAccess const& _identifierAccess,
|
||||
bool _useNamedLabelsForFunctions,
|
||||
bool _optimize
|
||||
bool _optimizeStackAllocation
|
||||
)
|
||||
{
|
||||
EthAssemblyAdapter assemblyAdapter(_assembly);
|
||||
@ -190,7 +190,7 @@ void CodeGenerator::assemble(
|
||||
_analysisInfo,
|
||||
_parsedData,
|
||||
*dialect,
|
||||
_optimize,
|
||||
_optimizeStackAllocation,
|
||||
false,
|
||||
_identifierAccess,
|
||||
_useNamedLabelsForFunctions
|
||||
|
@ -82,7 +82,7 @@ public:
|
||||
langutil::EVMVersion _evmVersion,
|
||||
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess(),
|
||||
bool _useNamedLabelsForFunctions = false,
|
||||
bool _optimize = false
|
||||
bool _optimizeStackAllocation = false
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -56,7 +56,10 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVer
|
||||
if (m_currentObject->name == dataName)
|
||||
_assembly.appendAssemblySize();
|
||||
else
|
||||
{
|
||||
yulAssert(m_subIDs.count(dataName) != 0, "Could not find assembly object <" + dataName.str() + ">.");
|
||||
_assembly.appendDataSize(m_subIDs.at(dataName));
|
||||
}
|
||||
});
|
||||
addFunction("dataoffset", 1, 1, true, true, [this](
|
||||
FunctionCall const& _call,
|
||||
@ -70,7 +73,10 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVer
|
||||
if (m_currentObject->name == dataName)
|
||||
_assembly.appendConstant(0);
|
||||
else
|
||||
{
|
||||
yulAssert(m_subIDs.count(dataName) != 0, "Could not find assembly object <" + dataName.str() + ">.");
|
||||
_assembly.appendDataOffset(m_subIDs.at(dataName));
|
||||
}
|
||||
});
|
||||
addFunction("datacopy", 3, 0, false, false, [](
|
||||
FunctionCall const&,
|
||||
|
@ -117,6 +117,9 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
|
||||
for (auto& statement: _for.pre.statements)
|
||||
visit(statement);
|
||||
|
||||
AssignmentsSinceContinue assignmentsSinceCont;
|
||||
assignmentsSinceCont(_for.body);
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_for.body);
|
||||
assignments(_for.post);
|
||||
@ -124,22 +127,13 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
|
||||
|
||||
visit(*_for.condition);
|
||||
(*this)(_for.body);
|
||||
clearValues(assignmentsSinceCont.names());
|
||||
(*this)(_for.post);
|
||||
|
||||
clearValues(assignments.names());
|
||||
|
||||
popScope();
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Break&)
|
||||
{
|
||||
yulAssert(false, "Not implemented yet.");
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Continue&)
|
||||
{
|
||||
yulAssert(false, "Not implemented yet.");
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Block& _block)
|
||||
{
|
||||
size_t numScopes = m_variableScopes.size();
|
||||
|
@ -53,8 +53,6 @@ public:
|
||||
void operator()(Switch& _switch) override;
|
||||
void operator()(FunctionDefinition&) override;
|
||||
void operator()(ForLoop&) override;
|
||||
void operator()(Break& _continue) override;
|
||||
void operator()(Continue& _continue) override;
|
||||
void operator()(Block& _block) override;
|
||||
|
||||
protected:
|
||||
|
@ -79,3 +79,29 @@ void Assignments::operator()(Assignment const& _assignment)
|
||||
for (auto const& var: _assignment.variableNames)
|
||||
m_names.emplace(var.name);
|
||||
}
|
||||
|
||||
|
||||
void AssignmentsSinceContinue::operator()(ForLoop const& _forLoop)
|
||||
{
|
||||
m_forLoopDepth++;
|
||||
ASTWalker::operator()(_forLoop);
|
||||
m_forLoopDepth--;
|
||||
}
|
||||
|
||||
void AssignmentsSinceContinue::operator()(Continue const&)
|
||||
{
|
||||
if (m_forLoopDepth == 0)
|
||||
m_continueFound = true;
|
||||
}
|
||||
|
||||
void AssignmentsSinceContinue::operator()(Assignment const& _assignment)
|
||||
{
|
||||
if (m_continueFound)
|
||||
for (auto const& var: _assignment.variableNames)
|
||||
m_names.emplace(var.name);
|
||||
}
|
||||
|
||||
void AssignmentsSinceContinue::operator()(FunctionDefinition const&)
|
||||
{
|
||||
yulAssert(false, "");
|
||||
}
|
||||
|
@ -81,4 +81,29 @@ private:
|
||||
std::set<YulString> m_names;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collects all names from a given continue statement on onwards.
|
||||
*
|
||||
* It makes only sense to be invoked from within a body of an outer for loop, that is,
|
||||
* it will only collect all names from the beginning of the first continue statement
|
||||
* of the outer-most ForLoop.
|
||||
*/
|
||||
class AssignmentsSinceContinue: public ASTWalker
|
||||
{
|
||||
public:
|
||||
using ASTWalker::operator();
|
||||
void operator()(ForLoop const& _forLoop) override;
|
||||
void operator()(Continue const&) override;
|
||||
void operator()(Assignment const& _assignment) override;
|
||||
void operator()(FunctionDefinition const& _funDef) override;
|
||||
|
||||
std::set<YulString> const& names() const { return m_names; }
|
||||
bool empty() const noexcept { return m_names.empty(); }
|
||||
|
||||
private:
|
||||
size_t m_forLoopDepth = 0;
|
||||
bool m_continueFound = false;
|
||||
std::set<YulString> m_names;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -61,41 +61,44 @@ void RedundantAssignEliminator::operator()(If const& _if)
|
||||
{
|
||||
visit(*_if.condition);
|
||||
|
||||
RedundantAssignEliminator branch{*this};
|
||||
branch(_if.body);
|
||||
TrackedAssignments skipBranch{m_assignments};
|
||||
(*this)(_if.body);
|
||||
|
||||
join(branch);
|
||||
merge(m_assignments, move(skipBranch));
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::operator()(Switch const& _switch)
|
||||
{
|
||||
visit(*_switch.expression);
|
||||
|
||||
TrackedAssignments const preState{m_assignments};
|
||||
|
||||
bool hasDefault = false;
|
||||
vector<RedundantAssignEliminator> branches;
|
||||
vector<TrackedAssignments> branches;
|
||||
for (auto const& c: _switch.cases)
|
||||
{
|
||||
if (!c.value)
|
||||
hasDefault = true;
|
||||
branches.emplace_back(*this);
|
||||
branches.back()(c.body);
|
||||
(*this)(c.body);
|
||||
branches.emplace_back(move(m_assignments));
|
||||
m_assignments = preState;
|
||||
}
|
||||
|
||||
if (hasDefault)
|
||||
{
|
||||
*this = std::move(branches.back());
|
||||
m_assignments = move(branches.back());
|
||||
branches.pop_back();
|
||||
}
|
||||
for (auto& branch: branches)
|
||||
join(branch);
|
||||
merge(m_assignments, move(branch));
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDefinition)
|
||||
{
|
||||
std::set<YulString> declaredVariables;
|
||||
std::map<YulString, std::map<Assignment const*, State>> assignments;
|
||||
swap(m_declaredVariables, declaredVariables);
|
||||
swap(m_assignments, assignments);
|
||||
std::set<YulString> outerDeclaredVariables;
|
||||
TrackedAssignments outerAssignments;
|
||||
swap(m_declaredVariables, outerDeclaredVariables);
|
||||
swap(m_assignments, outerAssignments);
|
||||
|
||||
(*this)(_functionDefinition.body);
|
||||
|
||||
@ -110,8 +113,8 @@ void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDe
|
||||
finalize(retParam.name);
|
||||
}
|
||||
|
||||
swap(m_declaredVariables, declaredVariables);
|
||||
swap(m_assignments, assignments);
|
||||
swap(m_declaredVariables, outerDeclaredVariables);
|
||||
swap(m_assignments, outerAssignments);
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::operator()(ForLoop const& _forLoop)
|
||||
@ -130,14 +133,14 @@ void RedundantAssignEliminator::operator()(ForLoop const& _forLoop)
|
||||
|
||||
visit(*_forLoop.condition);
|
||||
|
||||
RedundantAssignEliminator zeroRuns{*this};
|
||||
TrackedAssignments zeroRuns{m_assignments};
|
||||
|
||||
(*this)(_forLoop.body);
|
||||
(*this)(_forLoop.post);
|
||||
|
||||
visit(*_forLoop.condition);
|
||||
|
||||
RedundantAssignEliminator oneRun{*this};
|
||||
TrackedAssignments oneRun{m_assignments};
|
||||
|
||||
(*this)(_forLoop.body);
|
||||
(*this)(_forLoop.post);
|
||||
@ -145,8 +148,8 @@ void RedundantAssignEliminator::operator()(ForLoop const& _forLoop)
|
||||
visit(*_forLoop.condition);
|
||||
|
||||
// Order does not matter because "max" is commutative and associative.
|
||||
join(oneRun);
|
||||
join(zeroRuns);
|
||||
merge(m_assignments, move(oneRun));
|
||||
merge(m_assignments, move(zeroRuns));
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::operator()(Break const&)
|
||||
@ -173,7 +176,7 @@ void RedundantAssignEliminator::run(Dialect const& _dialect, Block& _ast)
|
||||
RedundantAssignEliminator rae{_dialect};
|
||||
rae(_ast);
|
||||
|
||||
AssignmentRemover remover{rae.m_assignmentsToRemove};
|
||||
AssignmentRemover remover{rae.m_pendingRemovals};
|
||||
remover(_ast);
|
||||
}
|
||||
|
||||
@ -204,16 +207,14 @@ void joinMap(std::map<K, V>& _a, std::map<K, V>&& _b, F _conflictSolver)
|
||||
}
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::join(RedundantAssignEliminator& _other)
|
||||
void RedundantAssignEliminator::merge(TrackedAssignments& _target, TrackedAssignments&& _other)
|
||||
{
|
||||
m_assignmentsToRemove.insert(begin(_other.m_assignmentsToRemove), end(_other.m_assignmentsToRemove));
|
||||
|
||||
joinMap(m_assignments, std::move(_other.m_assignments), [](
|
||||
joinMap(_target, move(_other), [](
|
||||
map<Assignment const*, State>& _assignmentHere,
|
||||
map<Assignment const*, State>&& _assignmentThere
|
||||
)
|
||||
{
|
||||
return joinMap(_assignmentHere, std::move(_assignmentThere), State::join);
|
||||
return joinMap(_assignmentHere, move(_assignmentThere), State::join);
|
||||
});
|
||||
}
|
||||
|
||||
@ -232,7 +233,7 @@ void RedundantAssignEliminator::finalize(YulString _variable)
|
||||
if (assignment.second == State{State::Unused} && MovableChecker{*m_dialect, *assignment.first->value}.movable())
|
||||
// TODO the only point where we actually need this
|
||||
// to be a set is for the for loop
|
||||
m_assignmentsToRemove.insert(assignment.first);
|
||||
m_pendingRemovals.insert(assignment.first);
|
||||
}
|
||||
m_assignments.erase(_variable);
|
||||
}
|
||||
|
@ -100,8 +100,9 @@ class RedundantAssignEliminator: public ASTWalker
|
||||
{
|
||||
public:
|
||||
explicit RedundantAssignEliminator(Dialect const& _dialect): m_dialect(&_dialect) {}
|
||||
RedundantAssignEliminator(RedundantAssignEliminator const&) = default;
|
||||
RedundantAssignEliminator& operator=(RedundantAssignEliminator const&) = default;
|
||||
RedundantAssignEliminator() = delete;
|
||||
RedundantAssignEliminator(RedundantAssignEliminator const&) = delete;
|
||||
RedundantAssignEliminator& operator=(RedundantAssignEliminator const&) = delete;
|
||||
RedundantAssignEliminator(RedundantAssignEliminator&&) = default;
|
||||
RedundantAssignEliminator& operator=(RedundantAssignEliminator&&) = default;
|
||||
|
||||
@ -119,8 +120,6 @@ public:
|
||||
static void run(Dialect const& _dialect, Block& _ast);
|
||||
|
||||
private:
|
||||
RedundantAssignEliminator() = default;
|
||||
|
||||
class State
|
||||
{
|
||||
public:
|
||||
@ -164,19 +163,21 @@ private:
|
||||
std::set<YulString> m_outerDeclaredVariables;
|
||||
};
|
||||
|
||||
/// Joins the assignment mapping with @a _other according to the rules laid out
|
||||
// TODO check that this does not cause nondeterminism!
|
||||
// This could also be a pseudo-map from state to assignment.
|
||||
using TrackedAssignments = std::map<YulString, std::map<Assignment const*, State>>;
|
||||
|
||||
/// Joins the assignment mapping of @a _source into @a _target according to the rules laid out
|
||||
/// above.
|
||||
/// Will destroy @a _other.
|
||||
void join(RedundantAssignEliminator& _other);
|
||||
/// Will destroy @a _source.
|
||||
static void merge(TrackedAssignments& _target, TrackedAssignments&& _source);
|
||||
void changeUndecidedTo(YulString _variable, State _newState);
|
||||
void finalize(YulString _variable);
|
||||
|
||||
Dialect const* m_dialect;
|
||||
std::set<YulString> m_declaredVariables;
|
||||
// TODO check that this does not cause nondeterminism!
|
||||
// This could also be a pseudo-map from state to assignment.
|
||||
std::map<YulString, std::map<Assignment const*, State>> m_assignments;
|
||||
std::set<Assignment const*> m_assignmentsToRemove;
|
||||
std::set<Assignment const*> m_pendingRemovals;
|
||||
TrackedAssignments m_assignments;
|
||||
};
|
||||
|
||||
class AssignmentRemover: public ASTModifier
|
||||
|
@ -51,7 +51,8 @@ SimplificationRule<Pattern> const* SimplificationRules::findFirstMatch(
|
||||
{
|
||||
rules.resetMatchGroups();
|
||||
if (rule.pattern.matches(_expr, _dialect, _ssaValues))
|
||||
return &rule;
|
||||
if (!rule.feasible || rule.feasible())
|
||||
return &rule;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -39,29 +39,87 @@ using namespace yul;
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* Class that discovers all variables that can be fully eliminated by rematerialization,
|
||||
* and the corresponding approximate costs.
|
||||
*/
|
||||
class RematCandidateSelector: public DataFlowAnalyzer
|
||||
{
|
||||
public:
|
||||
explicit RematCandidateSelector(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {}
|
||||
|
||||
/// @returns a set of pairs of rematerialisation costs and variable to rematerialise.
|
||||
/// Note that this set is sorted by cost.
|
||||
set<pair<size_t, YulString>> candidates()
|
||||
{
|
||||
set<pair<size_t, YulString>> cand;
|
||||
for (auto const& codeCost: m_expressionCodeCost)
|
||||
{
|
||||
size_t numRef = m_numReferences[codeCost.first];
|
||||
cand.emplace(make_pair(codeCost.second * numRef, codeCost.first));
|
||||
}
|
||||
return cand;
|
||||
}
|
||||
|
||||
using DataFlowAnalyzer::operator();
|
||||
void operator()(VariableDeclaration& _varDecl) override
|
||||
{
|
||||
DataFlowAnalyzer::operator()(_varDecl);
|
||||
if (_varDecl.variables.size() == 1)
|
||||
{
|
||||
YulString varName = _varDecl.variables.front().name;
|
||||
if (m_value.count(varName))
|
||||
m_expressionCodeCost[varName] = CodeCost::codeCost(*m_value[varName]);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(Assignment& _assignment) override
|
||||
{
|
||||
for (auto const& var: _assignment.variableNames)
|
||||
rematImpossible(var.name);
|
||||
DataFlowAnalyzer::operator()(_assignment);
|
||||
}
|
||||
|
||||
// We use visit(Expression) because operator()(Identifier) would also
|
||||
// get called on left-hand-sides of assignments.
|
||||
void visit(Expression& _e) override
|
||||
{
|
||||
if (_e.type() == typeid(Identifier))
|
||||
{
|
||||
YulString name = boost::get<Identifier>(_e).name;
|
||||
if (m_expressionCodeCost.count(name))
|
||||
{
|
||||
if (!m_value.count(name))
|
||||
rematImpossible(name);
|
||||
else
|
||||
++m_numReferences[name];
|
||||
}
|
||||
}
|
||||
DataFlowAnalyzer::visit(_e);
|
||||
}
|
||||
|
||||
/// Remove the variable from the candidate set.
|
||||
void rematImpossible(YulString _variable)
|
||||
{
|
||||
m_numReferences.erase(_variable);
|
||||
m_expressionCodeCost.erase(_variable);
|
||||
}
|
||||
|
||||
/// Candidate variables and the code cost of their value.
|
||||
map<YulString, size_t> m_expressionCodeCost;
|
||||
/// Number of references to each candidate variable.
|
||||
map<YulString, size_t> m_numReferences;
|
||||
};
|
||||
|
||||
template <typename ASTNode>
|
||||
void eliminateVariables(shared_ptr<Dialect> const& _dialect, ASTNode& _node, size_t _numVariables)
|
||||
{
|
||||
SSAValueTracker ssaValues;
|
||||
ssaValues(_node);
|
||||
|
||||
map<YulString, size_t> references = ReferencesCounter::countReferences(_node);
|
||||
|
||||
set<pair<size_t, YulString>> rematCosts;
|
||||
for (auto const& ssa: ssaValues.values())
|
||||
{
|
||||
if (!MovableChecker{*_dialect, *ssa.second}.movable())
|
||||
continue;
|
||||
size_t numRef = references[ssa.first];
|
||||
size_t cost = 0;
|
||||
if (numRef > 1)
|
||||
cost = CodeCost::codeCost(*ssa.second) * (numRef - 1);
|
||||
rematCosts.insert(make_pair(cost, ssa.first));
|
||||
}
|
||||
RematCandidateSelector selector{*_dialect};
|
||||
selector(_node);
|
||||
|
||||
// Select at most _numVariables
|
||||
set<YulString> varsToEliminate;
|
||||
for (auto const& costs: rematCosts)
|
||||
for (auto const& costs: selector.candidates())
|
||||
{
|
||||
if (varsToEliminate.size() >= _numVariables)
|
||||
break;
|
||||
@ -74,15 +132,20 @@ void eliminateVariables(shared_ptr<Dialect> const& _dialect, ASTNode& _node, siz
|
||||
|
||||
}
|
||||
|
||||
bool StackCompressor::run(shared_ptr<Dialect> const& _dialect, Block& _ast)
|
||||
bool StackCompressor::run(
|
||||
shared_ptr<Dialect> const& _dialect,
|
||||
Block& _ast,
|
||||
bool _optimizeStackAllocation,
|
||||
size_t _maxIterations
|
||||
)
|
||||
{
|
||||
yulAssert(
|
||||
_ast.statements.size() > 0 && _ast.statements.at(0).type() == typeid(Block),
|
||||
"Need to run the function grouper before the stack compressor."
|
||||
);
|
||||
for (size_t iterations = 0; iterations < 4; iterations++)
|
||||
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
|
||||
{
|
||||
map<YulString, int> stackSurplus = CompilabilityChecker::run(_dialect, _ast);
|
||||
map<YulString, int> stackSurplus = CompilabilityChecker::run(_dialect, _ast, _optimizeStackAllocation);
|
||||
if (stackSurplus.empty())
|
||||
return true;
|
||||
|
||||
|
@ -41,7 +41,12 @@ class StackCompressor
|
||||
public:
|
||||
/// Try to remove local variables until the AST is compilable.
|
||||
/// @returns true if it was successful.
|
||||
static bool run(std::shared_ptr<Dialect> const& _dialect, Block& _ast);
|
||||
static bool run(
|
||||
std::shared_ptr<Dialect> const& _dialect,
|
||||
Block& _ast,
|
||||
bool _optimizeStackAllocation,
|
||||
size_t _maxIterations
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ void OptimiserSuite::run(
|
||||
shared_ptr<Dialect> const& _dialect,
|
||||
Block& _ast,
|
||||
AsmAnalysisInfo const& _analysisInfo,
|
||||
bool _optimizeStackAllocation,
|
||||
set<YulString> const& _externallyUsedIdentifiers
|
||||
)
|
||||
{
|
||||
@ -184,8 +185,12 @@ void OptimiserSuite::run(
|
||||
Rematerialiser::run(*_dialect, ast);
|
||||
UnusedPruner::runUntilStabilised(*_dialect, ast, reservedIdentifiers);
|
||||
|
||||
// This is a tuning parameter, but actually just prevents infinite loops.
|
||||
size_t stackCompressorMaxIterations = 16;
|
||||
FunctionGrouper{}(ast);
|
||||
StackCompressor::run(_dialect, ast);
|
||||
// We ignore the return value because we will get a much better error
|
||||
// message once we perform code generation.
|
||||
StackCompressor::run(_dialect, ast, _optimizeStackAllocation, stackCompressorMaxIterations);
|
||||
BlockFlattener{}(ast);
|
||||
|
||||
VarNameCleaner{ast, *_dialect, reservedIdentifiers}(ast);
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
std::shared_ptr<Dialect> const& _dialect,
|
||||
Block& _ast,
|
||||
AsmAnalysisInfo const& _analysisInfo,
|
||||
bool _optimizeStackAllocation,
|
||||
std::set<YulString> const& _externallyUsedIdentifiers = {}
|
||||
);
|
||||
};
|
||||
|
@ -353,6 +353,7 @@ case $(uname -s) in
|
||||
# needed, but some tweaking/improvements can definitely happen
|
||||
#------------------------------------------------------------------------------
|
||||
CentOS*)
|
||||
echo "Attention: CentOS 7 is currently not supported!";
|
||||
read -p "This script will heavily modify your system in order to allow for compilation of Solidity. Are you sure? [Y/N]" -n 1 -r
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
# Make Sure we have the EPEL repos
|
||||
@ -376,7 +377,7 @@ case $(uname -s) in
|
||||
|
||||
# Get latest boost thanks to this guy: http://vicendominguez.blogspot.de/2014/04/boost-c-library-rpm-packages-for-centos.html
|
||||
sudo yum -y remove boost-devel
|
||||
sudo wget http://repo.enetres.net/enetres.repo -O /etc/yum.repos.d/enetres.repo
|
||||
sudo wget https://bintray.com/vicendominguez/CentOS6/rpm -O /etc/yum.repos.d/bintray-vicendominguez-CentOS6.repo
|
||||
sudo yum install boost-devel
|
||||
else
|
||||
echo "Aborted CentOS Solidity Dependency Installation";
|
||||
|
@ -874,6 +874,10 @@ bool CommandLineInterface::processInput()
|
||||
endl;
|
||||
return false;
|
||||
}
|
||||
serr() <<
|
||||
"Warning: Yul and its optimizer are still experimental. Please use the output with care." <<
|
||||
endl;
|
||||
|
||||
return assemble(inputLanguage, targetMachine, optimize);
|
||||
}
|
||||
if (m_args.count(g_argLink))
|
||||
@ -904,9 +908,10 @@ bool CommandLineInterface::processInput()
|
||||
m_compiler->setEVMVersion(m_evmVersion);
|
||||
// TODO: Perhaps we should not compile unless requested
|
||||
|
||||
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::enabled() : OptimiserSettings::minimal();
|
||||
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
||||
settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as<unsigned>();
|
||||
settings.runYulOptimiser = m_args.count(g_strOptimizeYul);
|
||||
settings.optimizeStackAllocation = settings.runYulOptimiser;
|
||||
m_compiler->setOptimiserSettings(settings);
|
||||
|
||||
bool successful = m_compiler->compile();
|
||||
@ -1294,7 +1299,7 @@ bool CommandLineInterface::assemble(
|
||||
yul::MachineAssemblyObject object;
|
||||
try
|
||||
{
|
||||
object = stack.assemble(_targetMachine);
|
||||
object = stack.assemble(_targetMachine, _optimize);
|
||||
}
|
||||
catch (Exception const& _exception)
|
||||
{
|
||||
|
@ -75,6 +75,7 @@ CommonOptions::CommonOptions(std::string _caption):
|
||||
)
|
||||
{
|
||||
options.add_options()
|
||||
("evm-version", po::value(&evmVersionString), "which evm version to use")
|
||||
("testpath", po::value<fs::path>(&this->testPath)->default_value(dev::test::testPath()), "path to test files")
|
||||
("ipcpath", po::value<fs::path>(&ipcPath)->default_value(IPCEnvOrDefaultPath()), "path to ipc socket")
|
||||
("no-ipc", po::bool_switch(&disableIPC), "disable semantic tests")
|
||||
@ -121,6 +122,20 @@ bool CommonOptions::parse(int argc, char const* const* argv)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
langutil::EVMVersion CommonOptions::evmVersion() const
|
||||
{
|
||||
if (!evmVersionString.empty())
|
||||
{
|
||||
auto version = langutil::EVMVersion::fromString(evmVersionString);
|
||||
if (!version)
|
||||
throw std::runtime_error("Invalid EVM version: " + evmVersionString);
|
||||
return *version;
|
||||
}
|
||||
else
|
||||
return langutil::EVMVersion();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libdevcore/Exceptions.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
@ -39,6 +40,8 @@ struct CommonOptions: boost::noncopyable
|
||||
bool disableIPC = false;
|
||||
bool disableSMT = false;
|
||||
|
||||
langutil::EVMVersion evmVersion() const;
|
||||
|
||||
virtual bool parse(int argc, char const* const* argv);
|
||||
// Throws a ConfigException on error
|
||||
virtual void validate() const;
|
||||
@ -47,6 +50,9 @@ protected:
|
||||
CommonOptions(std::string caption = "");
|
||||
|
||||
boost::program_options::options_description options;
|
||||
|
||||
private:
|
||||
std::string evmVersionString;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -50,14 +50,14 @@ string getIPCSocketPath()
|
||||
}
|
||||
|
||||
ExecutionFramework::ExecutionFramework():
|
||||
ExecutionFramework(getIPCSocketPath())
|
||||
ExecutionFramework(getIPCSocketPath(), dev::test::Options::get().evmVersion())
|
||||
{
|
||||
}
|
||||
|
||||
ExecutionFramework::ExecutionFramework(string const& _ipcPath):
|
||||
ExecutionFramework::ExecutionFramework(string const& _ipcPath, langutil::EVMVersion _evmVersion):
|
||||
m_rpc(RPCSession::instance(_ipcPath)),
|
||||
m_evmVersion(dev::test::Options::get().evmVersion()),
|
||||
m_optimize(dev::test::Options::get().optimize),
|
||||
m_evmVersion(_evmVersion),
|
||||
m_optimiserSettings(dev::test::Options::get().optimize ? solidity::OptimiserSettings::standard() : solidity::OptimiserSettings::minimal()),
|
||||
m_showMessages(dev::test::Options::get().showMessages),
|
||||
m_sender(m_rpc.account(0))
|
||||
{
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include <test/Options.h>
|
||||
#include <test/RPCSession.h>
|
||||
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libdevcore/FixedHash.h>
|
||||
@ -53,7 +55,7 @@ class ExecutionFramework
|
||||
|
||||
public:
|
||||
ExecutionFramework();
|
||||
explicit ExecutionFramework(std::string const& _ipcPath);
|
||||
explicit ExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion _evmVersion);
|
||||
virtual ~ExecutionFramework() = default;
|
||||
|
||||
virtual bytes const& compileAndRunWithoutCheck(
|
||||
@ -265,8 +267,7 @@ protected:
|
||||
};
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
unsigned m_optimizeRuns = 200;
|
||||
bool m_optimize = false;
|
||||
solidity::OptimiserSettings m_optimiserSettings = solidity::OptimiserSettings::minimal();
|
||||
bool m_showMessages = false;
|
||||
bool m_transactionSuccessful = true;
|
||||
Address m_sender;
|
||||
|
@ -53,22 +53,7 @@ Options::Options()
|
||||
options.add_options()
|
||||
("optimize", po::bool_switch(&optimize), "enables optimization")
|
||||
("abiencoderv2", po::bool_switch(&useABIEncoderV2), "enables abi encoder v2")
|
||||
("evm-version", po::value(&evmVersionString), "which evm version to use")
|
||||
("show-messages", po::bool_switch(&showMessages), "enables message output");
|
||||
|
||||
parse(suite.argc, suite.argv);
|
||||
}
|
||||
|
||||
langutil::EVMVersion Options::evmVersion() const
|
||||
{
|
||||
if (!evmVersionString.empty())
|
||||
{
|
||||
// We do this check as opposed to in the constructor because the BOOST_REQUIRE
|
||||
// macros cannot yet be used in the constructor.
|
||||
auto version = langutil::EVMVersion::fromString(evmVersionString);
|
||||
BOOST_REQUIRE_MESSAGE(version, "Invalid EVM version: " + evmVersionString);
|
||||
return *version;
|
||||
}
|
||||
else
|
||||
return langutil::EVMVersion();
|
||||
}
|
||||
|
@ -37,13 +37,9 @@ struct Options: CommonOptions
|
||||
bool showMessages = false;
|
||||
bool useABIEncoderV2 = false;
|
||||
|
||||
langutil::EVMVersion evmVersion() const;
|
||||
|
||||
static Options const& get();
|
||||
|
||||
private:
|
||||
std::string evmVersionString;
|
||||
|
||||
Options();
|
||||
};
|
||||
|
||||
|
@ -166,7 +166,6 @@ RPCSession::TransactionReceipt RPCSession::eth_getTransactionReceipt(string cons
|
||||
{
|
||||
TransactionReceipt receipt;
|
||||
Json::Value const result = rpcCall("eth_getTransactionReceipt", { quote(_transactionHash) });
|
||||
BOOST_REQUIRE(!result.isNull());
|
||||
receipt.gasUsed = result["gasUsed"].asString();
|
||||
receipt.contractAddress = result["contractAddress"].asString();
|
||||
receipt.blockNumber = result["blockNumber"].asString();
|
||||
@ -350,6 +349,10 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector<string> const&
|
||||
|
||||
BOOST_FAIL("Error on JSON-RPC call: " + result["error"]["message"].asString());
|
||||
}
|
||||
|
||||
if (!result.isMember("result") || result["result"].isNull())
|
||||
BOOST_FAIL("Missing result for JSON-RPC call: " + result.toStyledString());
|
||||
|
||||
return result["result"];
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <test/TestCase.h>
|
||||
|
||||
#include <boost/algorithm/cxx11/none_of.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
@ -35,16 +36,57 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename)
|
||||
!boost::starts_with(_filename.string(), ".");
|
||||
}
|
||||
|
||||
bool TestCase::supportedForEVMVersion(langutil::EVMVersion _evmVersion) const
|
||||
{
|
||||
return boost::algorithm::none_of(m_evmVersionRules, [&](auto const& rule) { return !rule(_evmVersion); });
|
||||
}
|
||||
|
||||
string TestCase::parseSource(istream& _stream)
|
||||
{
|
||||
string source;
|
||||
string line;
|
||||
string const delimiter("// ----");
|
||||
static string const delimiter("// ----");
|
||||
static string const evmVersion("// EVMVersion: ");
|
||||
bool isTop = true;
|
||||
while (getline(_stream, line))
|
||||
if (boost::algorithm::starts_with(line, delimiter))
|
||||
break;
|
||||
else
|
||||
{
|
||||
if (isTop && boost::algorithm::starts_with(line, evmVersion))
|
||||
{
|
||||
string versionString = line.substr(evmVersion.size() + 1);
|
||||
auto version = langutil::EVMVersion::fromString(versionString);
|
||||
if (!version)
|
||||
throw runtime_error("Invalid EVM version: \"" + versionString + "\"");
|
||||
switch (line.at(evmVersion.size()))
|
||||
{
|
||||
case '>':
|
||||
m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) {
|
||||
return version < _version;
|
||||
});
|
||||
break;
|
||||
case '<':
|
||||
m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) {
|
||||
return _version < version;
|
||||
});
|
||||
break;
|
||||
case '=':
|
||||
m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) {
|
||||
return _version == version;
|
||||
});
|
||||
break;
|
||||
case '!':
|
||||
m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) {
|
||||
return !(_version == version);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
isTop = false;
|
||||
source += line + "\n";
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
@ -46,6 +50,7 @@ public:
|
||||
{
|
||||
std::string filename;
|
||||
std::string ipcPath;
|
||||
langutil::EVMVersion evmVersion;
|
||||
};
|
||||
|
||||
using TestCaseCreator = std::unique_ptr<TestCase>(*)(Config const&);
|
||||
@ -69,8 +74,11 @@ public:
|
||||
|
||||
static bool isTestFilename(boost::filesystem::path const& _filename);
|
||||
|
||||
/// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise.
|
||||
bool supportedForEVMVersion(langutil::EVMVersion _evmVersion) const;
|
||||
|
||||
protected:
|
||||
static std::string parseSource(std::istream& _file);
|
||||
std::string parseSource(std::istream& _file);
|
||||
static void expect(std::string::iterator& _it, std::string::iterator _end, std::string::value_type _c);
|
||||
|
||||
template<typename IteratorType>
|
||||
@ -86,7 +94,8 @@ protected:
|
||||
while (_it != _end && *_it == '/')
|
||||
++_it;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::function<bool(langutil::EVMVersion)>> m_evmVersionRules;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ int registerTests(
|
||||
{
|
||||
int numTestsAdded = 0;
|
||||
fs::path fullpath = _basepath / _path;
|
||||
TestCase::Config config{fullpath.string(), _ipcPath};
|
||||
TestCase::Config config{fullpath.string(), _ipcPath, dev::test::Options::get().evmVersion()};
|
||||
if (fs::is_directory(fullpath))
|
||||
{
|
||||
test_suite* sub_suite = BOOST_TEST_SUITE(_path.filename().string());
|
||||
@ -104,8 +104,10 @@ int registerTests(
|
||||
try
|
||||
{
|
||||
stringstream errorStream;
|
||||
if (!_testCaseCreator(config)->run(errorStream))
|
||||
BOOST_ERROR("Test expectation mismatch.\n" + errorStream.str());
|
||||
auto testCase = _testCaseCreator(config);
|
||||
if (testCase->supportedForEVMVersion(dev::test::Options::get().evmVersion()))
|
||||
if (!testCase->run(errorStream))
|
||||
BOOST_ERROR("Test expectation mismatch.\n" + errorStream.str());
|
||||
}
|
||||
catch (boost::exception const& _e)
|
||||
{
|
||||
|
@ -125,6 +125,10 @@ function test_solc_behaviour()
|
||||
sed -i -e '/^Warning: This is a pre-release compiler version, please do not use it in production./d' "$stderr_path"
|
||||
sed -i -e 's/ Consider adding "pragma .*$//' "$stderr_path"
|
||||
fi
|
||||
# Remove path to cpp file
|
||||
sed -i -e 's/^\(Exception while assembling:\).*/\1/' "$stderr_path"
|
||||
# Remove exception class name.
|
||||
sed -i -e 's/^\(Dynamic exception type:\).*/\1/' "$stderr_path"
|
||||
|
||||
if [[ $exitCode -ne "$exit_code_expected" ]]
|
||||
then
|
||||
|
@ -1,3 +1,4 @@
|
||||
Warning: The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.
|
||||
gas_test_abiv2_optimize_yul/input.sol:2:1: Warning: Experimental features are turned on. Do not use experimental features on live deployments.
|
||||
pragma experimental ABIEncoderV2;
|
||||
^-------------------------------^
|
||||
|
@ -0,0 +1 @@
|
||||
Warning: Yul and its optimizer are still experimental. Please use the output with care.
|
@ -42,7 +42,6 @@ Text representation:
|
||||
0x00
|
||||
/* "object_compiler/input.sol":265:295 */
|
||||
return
|
||||
/* "object_compiler/input.sol":29:299 */
|
||||
pop
|
||||
stop
|
||||
|
||||
|
17
test/cmdlineTests/standard_optimizer_yul/input.json
Normal file
17
test/cmdlineTests/standard_optimizer_yul/input.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"language": "Solidity",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"details": { "yul": true }
|
||||
}
|
||||
}
|
||||
}
|
1
test/cmdlineTests/standard_optimizer_yul/output.json
Normal file
1
test/cmdlineTests/standard_optimizer_yul/output.json
Normal file
@ -0,0 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"Warning: The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.\n","message":"The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.","severity":"warning","type":"Warning"}],"sources":{"A":{"id":0}}}
|
16
test/cmdlineTests/standard_optimizer_yulDetails/input.json
Normal file
16
test/cmdlineTests/standard_optimizer_yulDetails/input.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"language": "Solidity",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"optimizer": {
|
||||
"details": { "yul": true, "yulDetails": {} }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"Warning: The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.\n","message":"The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.","severity":"warning","type":"Warning"}],"sources":{"A":{"id":0}}}
|
@ -10,7 +10,7 @@
|
||||
"settings":
|
||||
{
|
||||
"optimizer": {
|
||||
"details": { "yulDetails": 7 }
|
||||
"details": { "yul": true, "yulDetails": 7 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"The \"yulDetails\" optimizer setting has to be a JSON object.","message":"The \"yulDetails\" optimizer setting has to be a JSON object.","severity":"error","type":"JSONError"}]}
|
||||
{"errors":[{"component":"general","formattedMessage":"\"settings.optimizer.details.yulDetails\" must be an object","message":"\"settings.optimizer.details.yulDetails\" must be an object","severity":"error","type":"JSONError"}]}
|
||||
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"language": "Solidity",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"optimizer": {
|
||||
"details": { "yulDetails": 7 }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"\"Providing yulDetails requires Yul optimizer to be enabled.","message":"\"Providing yulDetails requires Yul optimizer to be enabled.","severity":"error","type":"JSONError"}]}
|
17
test/cmdlineTests/standard_yul/input.json
Normal file
17
test/cmdlineTests/standard_yul/input.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"language": "Yul",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "{ let x := mload(0) sstore(add(x, 0), 0) }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"outputSelection":
|
||||
{
|
||||
"*": { "*": ["*"], "": [ "*" ] }
|
||||
}
|
||||
}
|
||||
}
|
1
test/cmdlineTests/standard_yul/output.json
Normal file
1
test/cmdlineTests/standard_yul/output.json
Normal file
@ -0,0 +1 @@
|
||||
{"contracts":{"A":{"object":{"evm":{"assembly":" /* \"A\":17:18 */\n 0x00\n /* \"A\":11:19 */\n mload\n /* \"A\":38:39 */\n 0x00\n /* \"A\":34:35 */\n 0x00\n /* \"A\":31:32 */\n dup3\n /* \"A\":27:36 */\n add\n /* \"A\":20:40 */\n sstore\n /* \"A\":0:42 */\n pop\n","bytecode":{"linkReferences":{},"object":"6000516000600082015550","opcodes":"PUSH1 0x0 MLOAD PUSH1 0x0 PUSH1 0x0 DUP3 ADD SSTORE POP ","sourceMap":""}},"ir":"object \"object\" {\n code {\n let x := mload(0)\n sstore(add(x, 0), 0)\n }\n}\n","irOptimized":"object \"object\" {\n code {\n let x := mload(0)\n sstore(add(x, 0), 0)\n }\n}\n"}}},"errors":[{"component":"general","formattedMessage":"Yul is still experimental. Please use the output with care.","message":"Yul is still experimental. Please use the output with care.","severity":"warning","type":"Warning"}]}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"language": "Yul",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "object \"NamedObject\" { code { let x := dataoffset(\"DataName\") sstore(add(x, 0), 0) } data \"DataName\" \"abc\" object \"OtherObject\" { code { revert(0, 0) } } }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"outputSelection":
|
||||
{
|
||||
"A": { "OtherObject": ["*"], "": [ "*" ] }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"Yul is still experimental. Please use the output with care.","message":"Yul is still experimental. Please use the output with care.","severity":"warning","type":"Warning"}]}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"language": "Yul",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "{ let x := mload(0) sstore(add(x, 0), 0) }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"outputSelection":
|
||||
{
|
||||
"A": { "OtherObject": ["*"], "": [ "*" ] }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"Yul is still experimental. Please use the output with care.","message":"Yul is still experimental. Please use the output with care.","severity":"warning","type":"Warning"}]}
|
21
test/cmdlineTests/standard_yul_multiple_files/input.json
Normal file
21
test/cmdlineTests/standard_yul_multiple_files/input.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"language": "Yul",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "{ let x := mload(0) sstore(add(x, 0), 0) }"
|
||||
},
|
||||
"B":
|
||||
{
|
||||
"content": "{ let x := mload(0) sstore(add(x, 0), 0) }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"outputSelection":
|
||||
{
|
||||
"*": { "*": ["*"], "": [ "*" ] }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]}
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"language": "Yul",
|
||||
"sources":
|
||||
{
|
||||
"A":
|
||||
{
|
||||
"content": "{ let x := mload(0) sstore(add(x, 0), 0) }"
|
||||
},
|
||||
"B":
|
||||
{
|
||||
"content": "{ let x := mload(0) sstore(add(x, 0), 0) }"
|
||||
}
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"outputSelection":
|
||||
{
|
||||
"B": { "*": ["*"], "": [ "*" ] }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user