diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b96859fe780..6f5d78383eae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Changelog.md b/Changelog.md index afa1c37623eb..f9755997ed5a 100644 --- a/Changelog.md +++ b/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) diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md index 6aa0ad79c4f5..b2b39e886251 100644 --- a/ReleaseChecklist.md +++ b/ReleaseChecklist.md @@ -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. diff --git a/docs/bugs.json b/docs/bugs.json index 327e54a183aa..55214f7d5f6c 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -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.", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index dd779e0ed19b..fd2ad4b5e3cc 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -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" } } \ No newline at end of file diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst index c7f4a99252ee..a428463bd8b5 100644 --- a/docs/examples/micropayment.rst +++ b/docs/examples/micropayment.rst @@ -38,6 +38,7 @@ In this tutorial, we will sign messages in the browser using `web3.js ` are common instructions for compilers about how to treat the source code (e.g. `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 diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 048283e8d7b7..95cddde36a13 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -336,12 +336,12 @@ The following is the order of precedence for operators, listed in order of evalu | *13* | Logical OR | ``||`` | +------------+-------------------------------------+--------------------------------------------+ | *14* | Ternary operator | `` ? : `` | -+------------+-------------------------------------+--------------------------------------------+ -| *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 diff --git a/docs/resources.rst b/docs/resources.rst index 4dd617fa3227..a3a89113aaf1 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -27,12 +27,15 @@ Solidity Integrations * `Remix `_ Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. - * `Solium `_ - Linter to identify and fix style and security issues in Solidity. - * `Solhint `_ Solidity linter that provides security, style guide and best practice rules for smart contract validation. + * `Solidity IDE `_ + Browser-based IDE with integrated compiler, Ganache and local file system support. + + * `Solium `_ + Linter to identify and fix style and security issues in Solidity. + * `Superblocks Lab `_ 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 `_ Rich tool package to interact with the EVM. Includes a VM, Etherchain API, and a trace-viewer with gas cost display. +* `Universal Mutator `_ + 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. diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index f4b5aceec5a1..4bf8fa73fd7a 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -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. diff --git a/docs/yul.rst b/docs/yul.rst index 627e6e7cd00c..d13af263f8a5 100644 --- a/docs/yul.rst +++ b/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 `: + +:: + + { + "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 | diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index c157583a274c..f587b353377b 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -34,6 +34,54 @@ #include #include +/// Operators need to stay in the global namespace. + +/// Concatenate the contents of a container onto a vector +template std::vector& operator+=(std::vector& _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 std::vector& operator+=(std::vector& _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 std::set& operator+=(std::set& _a, U const& _b) +{ + _a.insert(_b.begin(), _b.end()); + return _a; +} +/// Concatenate the contents of a container onto a set, move variant. +template std::set& operator+=(std::set& _a, U&& _b) +{ + for (auto&& x: _b) + _a.insert(std::move(x)); + return _a; +} +/// Concatenate two vectors of elements. +template +inline std::vector operator+(std::vector const& _a, std::vector const& _b) +{ + std::vector ret(_a); + ret += _b; + return ret; +} +/// Concatenate two vectors of elements, moving them. +template +inline std::vector operator+(std::vector&& _a, std::vector&& _b) +{ + std::vector 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 std::vector& operator+=(std::vector& _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 std::vector& operator+=(std::vector& _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 std::set& operator+=(std::set& _a, U const& _b) -{ - _a.insert(_b.begin(), _b.end()); - return _a; -} -/// Concatenate two vectors of elements. -template -inline std::vector operator+(std::vector const& _a, std::vector const& _b) -{ - std::vector ret(_a); - ret += _b; - return ret; -} -/// Concatenate two vectors of elements, moving them. -template -inline std::vector operator+(std::vector&& _a, std::vector&& _b) -{ - std::vector ret(std::move(_a)); - if (&_a == &_b) - ret += ret; - else - ret += std::move(_b); - return ret; -} - template 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 diff --git a/libdevcore/Result.h b/libdevcore/Result.h index 4f7a063b5cce..ebc0db0e1301 100644 --- a/libdevcore/Result.h +++ b/libdevcore/Result.h @@ -30,7 +30,7 @@ namespace dev /// Result check() /// { /// if (false) -/// return Result("Error message.") +/// return Result::err("Error message.") /// return true; /// } /// @@ -39,8 +39,17 @@ template 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 err(std::string _message) + { + return Result{ResultType{}, std::move(_message)}; + } /// @{ /// @name Wrapper functions @@ -53,6 +62,16 @@ class Result /// @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 + void merge(Result 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)), diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index abbbbc2c57f0..41cf8990e2ca 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -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())); } diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 5f5bb63524b3..c091fc21afa7 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -153,7 +153,7 @@ std::vector> 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} }; } diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index 7b4dea6875e3..fa1d50691f56 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -36,9 +36,22 @@ namespace solidity template struct SimplificationRule { + SimplificationRule( + Pattern _pattern, + std::function _action, + bool _removesNonConstants, + std::function _feasible = {} + ): + pattern(std::move(_pattern)), + action(std::move(_action)), + removesNonConstants(_removesNonConstants), + feasible(std::move(_feasible)) + {} + Pattern pattern; std::function action; bool removesNonConstants; + std::function feasible; }; } diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index b812cecc33d9..9fee79cf4807 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -51,7 +51,9 @@ SimplificationRule 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; diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index 0bd6d681f14c..bffb256e6e04 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace langutil @@ -108,6 +109,11 @@ class SecondarySourceLocation 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. diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index cce1a3031f79..492adb5fe16a 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -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; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 4620d82b0c5c..d8a92097c5a3 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -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(*_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 argumentTypes = make_shared(); + FuncCallArguments funcCallArgs; + + funcCallArgs.names = _functionCall.names(); + for (ASTPointer 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(*it->type).canTakeArguments(*argumentTypes, exprType) + !dynamic_cast(*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 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()) diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 0ece09f16c65..70b907b5bad4 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -112,6 +112,8 @@ class AssemblyViewPureChecker: public boost::static_visitor { 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; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index d1acf90b33ae..93e793ace2ca 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -23,8 +23,11 @@ #pragma once #include +#include #include +#include + #include #include #include @@ -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> argumentTypes; + + /// Types and - if given - names of arguments if the expr. is a function + /// that is called, used for overload resoultion + boost::optional arguments; }; struct IdentifierAnnotation: ExpressionAnnotation diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h index f44c3435324a..a4101eccf7b9 100644 --- a/libsolidity/ast/ASTEnums.h +++ b/libsolidity/ast/ASTEnums.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include @@ -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> types; + /// Names of the arguments if given, otherwise unset + std::vector> names; + + size_t numArguments() const { return types.size(); } + size_t numNames() const { return names.size(); } + bool hasNamedArguments() const { return !names.empty(); } +}; + } } diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 726ef9808e19..36540bc0841b 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -144,12 +144,12 @@ Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp, bool _short) return typeDescriptions; } -Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr> _tps) +Json::Value ASTJsonConverter::typePointerToJson(boost::optional 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; } diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index da3c8605a0bf..abc84f813fb0 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -159,7 +159,7 @@ class ASTJsonConverter: public ASTConstVisitor return tmp; } static Json::Value typePointerToJson(TypePointer _tp, bool _short = false); - static Json::Value typePointerToJson(std::shared_ptr> _tps); + static Json::Value typePointerToJson(boost::optional const& _tps); void appendExpressionAttributes( std::vector> &_attributes, ExpressionAnnotation const& _annotation diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index bcf9b53ff635..ae2cb2966edf 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -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(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(commonType)) if (Token::Exp == _operator) @@ -729,7 +729,7 @@ BoolResult FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) con { FixedPointType const& convertTo = dynamic_cast(_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(); 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(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(DataLocation::Memory, baseExt); - else - return make_shared(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(DataLocation::Memory, baseInterfaceType)}; else - return true; + result = TypePointer{make_shared(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& _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 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(memberType)) + memberType = dynamic_cast(memberType)->baseType().get(); + + if (StructType const* innerStruct = dynamic_cast(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(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 StructType::membersMissingInMemory() const return missing; } -bool StructType::recursive() const -{ - if (!m_recursive.is_initialized()) - { - auto visitor = [&](StructDefinition const& _struct, CycleDetector& _cycleDetector, size_t /*_depth*/) - { - for (ASTPointer const& variable: _struct.members()) - { - Type const* memberType = variable->annotation().type.get(); - while (dynamic_cast(memberType)) - memberType = dynamic_cast(memberType)->baseType().get(); - if (StructType const* innerStruct = dynamic_cast(memberType)) - if (_cycleDetector.run(innerStruct->structDefinition())) - return; - } - }; - m_recursive = (CycleDetector(visitor).run(structDefinition()) != nullptr); - } - return *m_recursive; -} - TypeResult EnumType::unaryOperatorResult(Token _operator) const { return _operator == Token::Delete ? make_shared() : 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 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()); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index f88232c9b9de..5fe1df7d08b4 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -236,6 +236,13 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this 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 @@ class BoolType: public Type 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 @@ class ArrayType: public ReferenceType 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 @@ class ArrayType: public ReferenceType TypePointer m_baseType; bool m_hasDynamicLength = true; u256 m_length; + mutable boost::optional m_interfaceType; + mutable boost::optional m_interfaceType_library; }; /** @@ -777,6 +787,7 @@ class ContractType: public Type 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 @@ class ContractType: public Type return TypePointer{}; return std::make_shared(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 @@ class ContractType: public Type 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 @@ class StructType: public ReferenceType { return location() == DataLocation::Storage ? std::make_shared(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 @@ class StructType: public ReferenceType TypePointers memoryMemberTypes() const; /// @returns the set of all members that are removed in the memory version (typically mappings). std::set 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 m_interfaceType; + mutable boost::optional m_interfaceType_library; mutable boost::optional m_recursive; }; @@ -893,6 +910,7 @@ class EnumType: public Type 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 @@ class EnumType: public Type { return std::make_shared(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 @@ class FunctionType: public Type 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 @@ class FunctionType: public Type 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 @@ class FunctionType: public Type /// 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 @@ class MappingType: public Type { return std::make_shared(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, ""); } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index d78c6e7fb173..47475ecedc1f 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -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( ( add(headStart, )) )") ); - 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, ) )") ); - 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 (value) -> cleaned { + + } + )"); + templ("functionName", functionName); + + unsigned storageBytes = _type.storageBytes(); + if (IntegerType const* type = dynamic_cast(&_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 (value, pos) -> updatedPos { - updatedPos := (value, pos) + function (, pos) -> updatedPos { + updatedPos := (, 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 (value, pos) -> updatedPos { - (value, pos) + function (, pos) -> updatedPos { + (, pos) updatedPos := add(pos, ) } )") ("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"( // -> @@ -903,16 +938,31 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( pos := (pos, length) let originalPos := pos let srcPtr := (value) - for { let i := 0 } lt(i, length) { i := add(i, ) } - { + let itemCounter := 0 + if { + // Run the loop over all full slots + for { } lt(add(itemCounter, sub(, 1)), length) + { itemCounter := add(itemCounter, ) } + { + let data := sload(srcPtr) + <#items> + ((data), pos) + pos := add(pos, ) + + srcPtr := add(srcPtr, 1) + } + } + // Handle the last (not necessarily full) slot specially + if { let data := sload(srcPtr) <#items> - ((data), pos) - pos := add(pos, ) + if { + ((data), pos) + pos := add(pos, ) + itemCounter := add(itemCounter, 1) + } - srcPtr := add(srcPtr, 1) } - pos := add(originalPos, mul(length, )) } )" @@ -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> 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 (slot) -> value { + value := (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 (slot_value) -> value { + value := ((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(); +} diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index c1d88d68dc92..5c5b69637ff6 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -134,6 +134,15 @@ class ABIFunctions /// 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 @@ class ABIFunctions /// 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 @@ class ABIFunctions /// @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 m_functionCollector; std::set m_externallyUsedFunctions; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3053e3f7cd52..ef642becf540 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -331,7 +331,7 @@ void CompilerContext::appendInlineAssembly( vector const& _localVariables, set 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(); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 44f96f5dd0a6..3744913c327a 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -217,7 +217,7 @@ class CompilerContext std::vector const& _localVariables = std::vector(), std::set const& _externallyUsedFunctions = std::set(), bool _system = false, - bool _optimise = false + OptimiserSettings const& _optimiserSettings = OptimiserSettings::none() ); /// Appends arbitrary data to the end of the bytecode. diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index d007aaf7ce69..3d207b8e382c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -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."); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 568d2c9a7a78..988ac389740e 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -298,9 +298,6 @@ class CompilerUtils 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); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index b108827492f6..2e533894218c 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -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 ); } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 3ed4b702fcbb..986d6b8c7fef 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1394,7 +1394,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) { TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); ContractDefinition const& contract = dynamic_cast(*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 diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 1a21ece2b934..92eb5532d738 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -68,6 +68,7 @@ class YulUtilFunctions 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(); diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index d9e2340214d5..7cdaa49f3d6d 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -24,6 +24,7 @@ #include #include +#include #include 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 const& modifierInvocation = function.modifiers()[m_modifierDepthStack.back()]; + solAssert(modifierInvocation, ""); + modifierInvocation->accept(*this); + auto const& modifierDef = dynamic_cast( + *modifierInvocation->name()->annotation().referencedDeclaration + ); + vector 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 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 const& _callArgs) +void SMTChecker::initializeFunctionCallParameters(CallableDeclaration const& _function, vector 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); diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 4039166d7e52..66f4397e8ba6 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -63,8 +63,10 @@ class SMTChecker: private ASTConstVisitor 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 @@ class SMTChecker: private ASTConstVisitor 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 @@ class SMTChecker: private ASTConstVisitor smt::Expression value; smt::Expression path; langutil::SourceLocation const& location; + std::vector 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 _callStack): type(_type), intType(_intType), value(_value), path(_path), - location(_location) + location(_location), + callStack(move(_callStack)) { solAssert(dynamic_cast(intType.get()), ""); } @@ -174,7 +182,7 @@ class SMTChecker: private ASTConstVisitor smt::CheckResult checkSatisfiable(); void initializeLocalVariables(FunctionDefinition const& _function); - void initializeFunctionCallParameters(FunctionDefinition const& _function, std::vector const& _callArgs); + void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector const& _callArgs); void resetVariable(VariableDeclaration const& _variable); void resetStateVariables(); void resetStorageReferences(); @@ -229,6 +237,12 @@ class SMTChecker: private ASTConstVisitor 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 @@ class SMTChecker: private ASTConstVisitor /// Stores the current path of function calls. std::vector m_functionPath; + /// Stores the current call/invocation path. + std::vector m_callStack; /// Returns true if the current function was not visited by /// a function call. bool isRootFunction(); @@ -276,6 +292,12 @@ class SMTChecker: private ASTConstVisitor bool visitedFunction(FunctionDefinition const* _funDef); std::vector 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 m_modifierDepthStack; }; } diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index f8a9c7ec8a6f..e97bd5b65c48 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -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 diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c0dc2ffd7575..81619acb76e2 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -109,7 +109,7 @@ void CompilerStack::setLibraries(std::map 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(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 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(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(0x60 + length)}; + m_data += key; + } + else if (length <= 256) + { + m_data += bytes{0x78, static_cast(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(0x40 + length)}; + m_data += key; + } + else if (length <= 256) + { + m_data += bytes{0x58, static_cast(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 diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index e8eb585c64d3..bbd2401e9a3d 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -150,7 +150,7 @@ class CompilerStack: boost::noncopyable /// 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 @@ class CompilerStack: boost::noncopyable { std::shared_ptr scanner; std::shared_ptr ast; - bool isLibrary = false; h256 mutable keccak256HashCached; h256 mutable swarmHashCached; void reset() { *this = Source(); } diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index aae5fa1ce71e..83203865f746 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -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, diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index c17fdf39a24f..c1f7067901d0 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -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 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 parseOptimizerSettings(Json::Value const& _jsonInput) @@ -369,7 +370,7 @@ boost::variant 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 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 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); + + 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(std::move(parsed))); - else + if (parsed.type() == typeid(Json::Value)) return boost::get(std::move(parsed)); + InputsAndSettings settings = boost::get(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) { diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index 63852fbbd17c..daae7797cc9d 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -58,6 +58,7 @@ class StandardCompiler: boost::noncopyable private: struct InputsAndSettings { + std::string language; Json::Value errors; std::map sources; std::map smtLib2Responses; @@ -74,6 +75,7 @@ class StandardCompiler: boost::noncopyable boost::variant parseInput(Json::Value const& _input); Json::Value compileSolidity(InputsAndSettings _inputsAndSettings); + Json::Value compileYul(InputsAndSettings _inputsAndSettings); ReadCallback::Callback m_readFile; }; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 79ff447bcc92..ba02c048ccbc 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -65,6 +65,8 @@ class Parser::ASTNodeFactory return make_shared(m_location, std::forward(_args)...); } + SourceLocation const& location() const noexcept { return m_location; } + private: Parser const& m_parser; SourceLocation m_location; @@ -108,14 +110,15 @@ ASTPointer Parser::parse(shared_ptr const& _scanner) } } -void Parser::parsePragmaVersion(vector const& tokens, vector const& literals) +void Parser::parsePragmaVersion(SourceLocation const& _location, vector const& _tokens, vector 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 Parser::parsePragmaDirective() if (literals.size() >= 2 && literals[0] == "solidity") { parsePragmaVersion( + nodeFactory.location(), vector(tokens.begin() + 1, tokens.end()), vector(literals.begin() + 1, literals.end()) ); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 00e3f75190c4..2468c86eab3f 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -73,7 +73,7 @@ class Parser: public langutil::ParserBase ///@{ ///@name Parsing functions for the AST nodes - void parsePragmaVersion(std::vector const& tokens, std::vector const& literals); + void parsePragmaVersion(langutil::SourceLocation const& _location, std::vector const& _tokens, std::vector const& _literals); ASTPointer parsePragmaDirective(); ASTPointer parseImportDirective(); ContractDefinition::ContractKind parseContractKind(); diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 77c98d6166d4..33132756f5c8 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -436,7 +436,7 @@ bool AsmAnalyzer::operator()(Switch const& _switch) ); } - set> cases; + set 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, diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index ef2307ec427c..a9d19927940b 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -135,7 +135,10 @@ void AssemblyStack::optimize(Object& _object) for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast(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 diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index 4484c546cc7a..8a30ebfcbeb5 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -74,7 +74,7 @@ class AssemblyStack /// 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; } diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index e20140e27ee3..3b00fc6a2cac 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -33,7 +33,11 @@ using namespace yul; using namespace dev; using namespace dev::solidity; -std::map CompilabilityChecker::run(std::shared_ptr _dialect, Block const& _ast) +map CompilabilityChecker::run( + shared_ptr _dialect, + Block const& _ast, + bool _optimizeStackAllocation +) { if (_dialect->flavour == AsmFlavour::Yul) return {}; @@ -43,12 +47,11 @@ std::map CompilabilityChecker::run(std::shared_ptr _dia solAssert(dynamic_cast(_dialect.get()), ""); shared_ptr noOutputDialect = make_shared(dynamic_pointer_cast(_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); diff --git a/libyul/CompilabilityChecker.h b/libyul/CompilabilityChecker.h index 80c91f73a454..72d13a57d845 100644 --- a/libyul/CompilabilityChecker.h +++ b/libyul/CompilabilityChecker.h @@ -39,7 +39,11 @@ namespace yul class CompilabilityChecker { public: - static std::map run(std::shared_ptr _dialect, Block const& _ast); + static std::map run( + std::shared_ptr _dialect, + Block const& _ast, + bool _optimizeStackAllocation + ); }; } diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index 19c87b77d511..489bfdc19cf3 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -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 diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index 2c09e255683a..9647014ce302 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -82,7 +82,7 @@ class CodeGenerator langutil::EVMVersion _evmVersion, ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess(), bool _useNamedLabelsForFunctions = false, - bool _optimize = false + bool _optimizeStackAllocation = false ); }; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index b779fd301841..6337bf9e356c 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -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&, diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index c34384024c4d..5a812151d833 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -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,20 +127,11 @@ 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."); + popScope(); } void DataFlowAnalyzer::operator()(Block& _block) diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 7a569513ec55..5fb5db958d2f 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -53,8 +53,6 @@ class DataFlowAnalyzer: public ASTModifier 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: diff --git a/libyul/optimiser/NameCollector.cpp b/libyul/optimiser/NameCollector.cpp index 5195b8d8fdc2..04631a86ae73 100644 --- a/libyul/optimiser/NameCollector.cpp +++ b/libyul/optimiser/NameCollector.cpp @@ -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, ""); +} diff --git a/libyul/optimiser/NameCollector.h b/libyul/optimiser/NameCollector.h index 7e21c03f0f3f..b6b4e1e6cb13 100644 --- a/libyul/optimiser/NameCollector.h +++ b/libyul/optimiser/NameCollector.h @@ -81,4 +81,29 @@ class Assignments: public ASTWalker std::set 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 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 m_names; +}; + } diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index 36f63848fecd..48f0e7fba310 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -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 branches; + vector 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 declaredVariables; - std::map> assignments; - swap(m_declaredVariables, declaredVariables); - swap(m_assignments, assignments); + std::set 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& _a, std::map&& _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& _assignmentHere, map&& _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); } diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h index e35c0143e6f1..8ccd37faea90 100644 --- a/libyul/optimiser/RedundantAssignEliminator.h +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -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 @@ class RedundantAssignEliminator: public ASTWalker static void run(Dialect const& _dialect, Block& _ast); private: - RedundantAssignEliminator() = default; - class State { public: @@ -164,19 +163,21 @@ class RedundantAssignEliminator: public ASTWalker std::set 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>; + + /// 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 m_declaredVariables; - // TODO check that this does not cause nondeterminism! - // This could also be a pseudo-map from state to assignment. - std::map> m_assignments; - std::set m_assignmentsToRemove; + std::set m_pendingRemovals; + TrackedAssignments m_assignments; }; class AssignmentRemover: public ASTModifier diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index 1b620b641e71..c70ed2061b42 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -51,7 +51,8 @@ SimplificationRule const* SimplificationRules::findFirstMatch( { rules.resetMatchGroups(); if (rule.pattern.matches(_expr, _dialect, _ssaValues)) - return &rule; + if (!rule.feasible || rule.feasible()) + return &rule; } return nullptr; } diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index bbf34ff61e02..ffd91bf928b3 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -39,29 +39,87 @@ using namespace yul; namespace { -template -void eliminateVariables(shared_ptr const& _dialect, ASTNode& _node, size_t _numVariables) +/** + * Class that discovers all variables that can be fully eliminated by rematerialization, + * and the corresponding approximate costs. + */ +class RematCandidateSelector: public DataFlowAnalyzer { - SSAValueTracker ssaValues; - ssaValues(_node); +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> candidates() + { + set> 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]); + } + } - map references = ReferencesCounter::countReferences(_node); + void operator()(Assignment& _assignment) override + { + for (auto const& var: _assignment.variableNames) + rematImpossible(var.name); + DataFlowAnalyzer::operator()(_assignment); + } - set> rematCosts; - for (auto const& ssa: ssaValues.values()) + // We use visit(Expression) because operator()(Identifier) would also + // get called on left-hand-sides of assignments. + void visit(Expression& _e) override { - 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)); + if (_e.type() == typeid(Identifier)) + { + YulString name = boost::get(_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 m_expressionCodeCost; + /// Number of references to each candidate variable. + map m_numReferences; +}; + +template +void eliminateVariables(shared_ptr const& _dialect, ASTNode& _node, size_t _numVariables) +{ + RematCandidateSelector selector{*_dialect}; + selector(_node); + // Select at most _numVariables set 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 const& _dialect, ASTNode& _node, siz } -bool StackCompressor::run(shared_ptr const& _dialect, Block& _ast) +bool StackCompressor::run( + shared_ptr 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 stackSurplus = CompilabilityChecker::run(_dialect, _ast); + map stackSurplus = CompilabilityChecker::run(_dialect, _ast, _optimizeStackAllocation); if (stackSurplus.empty()) return true; diff --git a/libyul/optimiser/StackCompressor.h b/libyul/optimiser/StackCompressor.h index 11c9aa437142..45240fc6d849 100644 --- a/libyul/optimiser/StackCompressor.h +++ b/libyul/optimiser/StackCompressor.h @@ -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 const& _dialect, Block& _ast); + static bool run( + std::shared_ptr const& _dialect, + Block& _ast, + bool _optimizeStackAllocation, + size_t _maxIterations + ); }; } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index ef9026999ca4..91ac3f3a769c 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -59,6 +59,7 @@ void OptimiserSuite::run( shared_ptr const& _dialect, Block& _ast, AsmAnalysisInfo const& _analysisInfo, + bool _optimizeStackAllocation, set 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); diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index aed26b714e86..ff80661a7bed 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -42,6 +42,7 @@ class OptimiserSuite std::shared_ptr const& _dialect, Block& _ast, AsmAnalysisInfo const& _analysisInfo, + bool _optimizeStackAllocation, std::set const& _externallyUsedIdentifiers = {} ); }; diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 0ed13fdd624c..a0e1ff352a78 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -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"; diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 2eb01c47b9b2..20612dacd549 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -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(); 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) { diff --git a/test/Common.cpp b/test/Common.cpp index 5c21744b2c33..acba2a016ba2 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -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(&this->testPath)->default_value(dev::test::testPath()), "path to test files") ("ipcpath", po::value(&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(); +} + } } diff --git a/test/Common.h b/test/Common.h index 272db5a0f6e9..f16646daaf45 100644 --- a/test/Common.h +++ b/test/Common.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include @@ -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 @@ struct CommonOptions: boost::noncopyable CommonOptions(std::string caption = ""); boost::program_options::options_description options; + +private: + std::string evmVersionString; }; } diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index dcf61af66924..eeac68be28aa 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -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)) { diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index f70c64b3ff5c..4a42382d0845 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -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 @@ class ExecutionFramework }; 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; diff --git a/test/Options.cpp b/test/Options.cpp index e7d4badb1773..26a83d16f301 100644 --- a/test/Options.cpp +++ b/test/Options.cpp @@ -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(); -} diff --git a/test/Options.h b/test/Options.h index 7f2ea430b9ea..35a49c6194a0 100644 --- a/test/Options.h +++ b/test/Options.h @@ -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(); }; diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 84b7da8e9b62..deb64dd80d14 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -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 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"]; } diff --git a/test/TestCase.cpp b/test/TestCase.cpp index e9e2c9f21a83..6f3c92a53f8b 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -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; } diff --git a/test/TestCase.h b/test/TestCase.h index 52bca52749cb..86a109682ff2 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -17,11 +17,15 @@ #pragma once +#include + #include +#include #include #include #include +#include namespace dev { @@ -46,6 +50,7 @@ class TestCase { std::string filename; std::string ipcPath; + langutil::EVMVersion evmVersion; }; using TestCaseCreator = std::unique_ptr(*)(Config const&); @@ -69,8 +74,11 @@ class TestCase 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 @@ -86,7 +94,8 @@ class TestCase while (_it != _end && *_it == '/') ++_it; } - +private: + std::vector> m_evmVersionRules; }; } diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 34412cb30fe7..d9bf9c3fc48a 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -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) { diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 9a3e53111a4e..37c319e7d07c 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -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 diff --git a/test/cmdlineTests/gas_test_abiv2_optimize_yul/err b/test/cmdlineTests/gas_test_abiv2_optimize_yul/err index 1e205d0e3115..edf9a8927ae2 100644 --- a/test/cmdlineTests/gas_test_abiv2_optimize_yul/err +++ b/test/cmdlineTests/gas_test_abiv2_optimize_yul/err @@ -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; ^-------------------------------^ diff --git a/test/cmdlineTests/object_compiler/err b/test/cmdlineTests/object_compiler/err index e69de29bb2d1..aa7ea77f9272 100644 --- a/test/cmdlineTests/object_compiler/err +++ b/test/cmdlineTests/object_compiler/err @@ -0,0 +1 @@ +Warning: Yul and its optimizer are still experimental. Please use the output with care. diff --git a/test/cmdlineTests/object_compiler/output b/test/cmdlineTests/object_compiler/output index 496ac4193a52..51830a0c0fcf 100644 --- a/test/cmdlineTests/object_compiler/output +++ b/test/cmdlineTests/object_compiler/output @@ -42,7 +42,6 @@ Text representation: 0x00 /* "object_compiler/input.sol":265:295 */ return - /* "object_compiler/input.sol":29:299 */ pop stop diff --git a/test/cmdlineTests/standard_optimizer_yul/input.json b/test/cmdlineTests/standard_optimizer_yul/input.json new file mode 100644 index 000000000000..1c93be8714ef --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yul/input.json @@ -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 } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yul/output.json b/test/cmdlineTests/standard_optimizer_yul/output.json new file mode 100644 index 000000000000..61ff605a125b --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yul/output.json @@ -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}}} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails/input.json b/test/cmdlineTests/standard_optimizer_yulDetails/input.json new file mode 100644 index 000000000000..5203e64bf12d --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails/input.json @@ -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": {} } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails/output.json b/test/cmdlineTests/standard_optimizer_yulDetails/output.json new file mode 100644 index 000000000000..61ff605a125b --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails/output.json @@ -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}}} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json index 056aee91bfd0..18d3852dbf45 100644 --- a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json @@ -10,7 +10,7 @@ "settings": { "optimizer": { - "details": { "yulDetails": 7 } + "details": { "yul": true, "yulDetails": 7 } } } } diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json index b71a8a61a2a1..35638adf0772 100644 --- a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json @@ -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"}]} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/input.json new file mode 100644 index 000000000000..056aee91bfd0 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "yulDetails": 7 } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/output.json new file mode 100644 index 000000000000..c44794cc138a --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/output.json @@ -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"}]} diff --git a/test/cmdlineTests/standard_yul/input.json b/test/cmdlineTests/standard_yul/input.json new file mode 100644 index 000000000000..fb26c9751390 --- /dev/null +++ b/test/cmdlineTests/standard_yul/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul/output.json b/test/cmdlineTests/standard_yul/output.json new file mode 100644 index 000000000000..7ea2df486405 --- /dev/null +++ b/test/cmdlineTests/standard_yul/output.json @@ -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"}]} diff --git a/test/cmdlineTests/standard_yul_embedded_object_name/input.json b/test/cmdlineTests/standard_yul_embedded_object_name/input.json new file mode 100644 index 000000000000..49761d2531f7 --- /dev/null +++ b/test/cmdlineTests/standard_yul_embedded_object_name/input.json @@ -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": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_embedded_object_name/output.json b/test/cmdlineTests/standard_yul_embedded_object_name/output.json new file mode 100644 index 000000000000..32a2a647a030 --- /dev/null +++ b/test/cmdlineTests/standard_yul_embedded_object_name/output.json @@ -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"}]} diff --git a/test/cmdlineTests/standard_yul_invalid_object_name/input.json b/test/cmdlineTests/standard_yul_invalid_object_name/input.json new file mode 100644 index 000000000000..715075310e7b --- /dev/null +++ b/test/cmdlineTests/standard_yul_invalid_object_name/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "outputSelection": + { + "A": { "OtherObject": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_invalid_object_name/output.json b/test/cmdlineTests/standard_yul_invalid_object_name/output.json new file mode 100644 index 000000000000..32a2a647a030 --- /dev/null +++ b/test/cmdlineTests/standard_yul_invalid_object_name/output.json @@ -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"}]} diff --git a/test/cmdlineTests/standard_yul_multiple_files/input.json b/test/cmdlineTests/standard_yul_multiple_files/input.json new file mode 100644 index 000000000000..d63a218f0c65 --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files/input.json @@ -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": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_multiple_files/output.json b/test/cmdlineTests/standard_yul_multiple_files/output.json new file mode 100644 index 000000000000..b748cd50f8de --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files/output.json @@ -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"}]} \ No newline at end of file diff --git a/test/cmdlineTests/standard_yul_multiple_files_selected/input.json b/test/cmdlineTests/standard_yul_multiple_files_selected/input.json new file mode 100644 index 000000000000..faf1ead488e8 --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files_selected/input.json @@ -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": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_multiple_files_selected/output.json b/test/cmdlineTests/standard_yul_multiple_files_selected/output.json new file mode 100644 index 000000000000..b748cd50f8de --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files_selected/output.json @@ -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"}]} \ No newline at end of file diff --git a/test/cmdlineTests/standard_yul_object/input.json b/test/cmdlineTests/standard_yul_object/input.json new file mode 100644 index 000000000000..29391e84acb0 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "object \"NamedObject\" { code { let x := dataoffset(\"DataName\") sstore(add(x, 0), 0) } data \"DataName\" \"abc\" }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_object/output.json b/test/cmdlineTests/standard_yul_object/output.json new file mode 100644 index 000000000000..77dcb9678dce --- /dev/null +++ b/test/cmdlineTests/standard_yul_object/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"NamedObject":{"evm":{"assembly":" data_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45\n /* \"A\":80:81 */\n 0x00\n /* \"A\":76:77 */\n 0x00\n /* \"A\":73:74 */\n dup3\n /* \"A\":69:78 */\n add\n /* \"A\":62:82 */\n sstore\n /* \"A\":28:84 */\n pop\nstop\ndata_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 616263\n","bytecode":{"linkReferences":{},"object":"600b6000600082015550fe616263","opcodes":"PUSH1 0xB PUSH1 0x0 PUSH1 0x0 DUP3 ADD SSTORE POP INVALID PUSH2 0x6263 ","sourceMap":""}},"ir":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\n}\n","irOptimized":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\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"}]} diff --git a/test/cmdlineTests/standard_yul_object_name/input.json b/test/cmdlineTests/standard_yul_object_name/input.json new file mode 100644 index 000000000000..ccc1db2d41b1 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_name/input.json @@ -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": { "NamedObject": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_object_name/output.json b/test/cmdlineTests/standard_yul_object_name/output.json new file mode 100644 index 000000000000..16644297f461 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_name/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"NamedObject":{"evm":{"assembly":" data_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45\n /* \"A\":80:81 */\n 0x00\n /* \"A\":76:77 */\n 0x00\n /* \"A\":73:74 */\n dup3\n /* \"A\":69:78 */\n add\n /* \"A\":62:82 */\n sstore\n /* \"A\":28:84 */\n pop\nstop\ndata_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 616263\n\nsub_0: assembly {\n /* \"A\":147:148 */\n 0x00\n /* \"A\":144:145 */\n 0x00\n /* \"A\":137:149 */\n revert\n}\n","bytecode":{"linkReferences":{},"object":"600b6000600082015550fe616263","opcodes":"PUSH1 0xB PUSH1 0x0 PUSH1 0x0 DUP3 ADD SSTORE POP INVALID PUSH2 0x6263 ","sourceMap":""}},"ir":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\n object \"OtherObject\" {\n code {\n revert(0, 0)\n }\n }\n}\n","irOptimized":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\n object \"OtherObject\" {\n code {\n revert(0, 0)\n }\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"}]} diff --git a/test/cmdlineTests/standard_yul_optimized/input.json b/test/cmdlineTests/standard_yul_optimized/input.json new file mode 100644 index 000000000000..97611e5fbc91 --- /dev/null +++ b/test/cmdlineTests/standard_yul_optimized/input.json @@ -0,0 +1,23 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "optimizer": { + "enabled": true, + "details": { + "yul": true + } + }, + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_optimized/output.json b/test/cmdlineTests/standard_yul_optimized/output.json new file mode 100644 index 000000000000..80b764ece5bb --- /dev/null +++ b/test/cmdlineTests/standard_yul_optimized/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"object":{"evm":{"assembly":" /* \"A\":17:18 */\n 0x00\n 0x00\n /* \"A\":11:19 */\n mload\n /* \"A\":20:40 */\n sstore\n","bytecode":{"linkReferences":{},"object":"600060005155","opcodes":"PUSH1 0x0 PUSH1 0x0 MLOAD SSTORE ","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 sstore(mload(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"}]} diff --git a/test/cmdlineTests/strict_asm_jump/err b/test/cmdlineTests/strict_asm_jump/err index f4033b762689..ba46230906ce 100644 --- a/test/cmdlineTests/strict_asm_jump/err +++ b/test/cmdlineTests/strict_asm_jump/err @@ -1,3 +1,4 @@ +Warning: Yul and its optimizer are still experimental. Please use the output with care. strict_asm_jump/input.sol:1:3: Error: Jump instructions and labels are low-level EVM features that can lead to incorrect stack access. Because of that they are disallowed in strict assembly. Use functions, "switch", "if" or "for" statements instead. { jump(1) } ^-----^ diff --git a/test/cmdlineTests/yul_stack_opt/args b/test/cmdlineTests/yul_stack_opt/args new file mode 100644 index 000000000000..20fe41eb5b9f --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt/args @@ -0,0 +1 @@ +--strict-assembly --optimize diff --git a/test/cmdlineTests/yul_stack_opt/err b/test/cmdlineTests/yul_stack_opt/err new file mode 100644 index 000000000000..aa7ea77f9272 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt/err @@ -0,0 +1 @@ +Warning: Yul and its optimizer are still experimental. Please use the output with care. diff --git a/test/cmdlineTests/yul_stack_opt/input.sol b/test/cmdlineTests/yul_stack_opt/input.sol new file mode 100644 index 000000000000..772a6d4df9b5 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt/input.sol @@ -0,0 +1,24 @@ +{ + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) +} diff --git a/test/cmdlineTests/yul_stack_opt/output b/test/cmdlineTests/yul_stack_opt/output new file mode 100644 index 000000000000..c8e10fe86cfc --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt/output @@ -0,0 +1,202 @@ + +======= yul_stack_opt/input.sol (EVM) ======= + +Pretty printed source: +object "object" { + code { + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + sstore(a, a) + sstore(2, a) + sstore(3, a) + sstore(4, a) + sstore(5, a) + sstore(6, a) + sstore(7, a) + sstore(8, a) + sstore(9, a) + sstore(10, a) + sstore(11, a) + sstore(12, a) + sstore(13, a) + } + } +} + + +Binary representation: +60056032565b505050505050505050505050505050601a6032565b5050505050505050505050505050508082555050609a565b60006000600060006000600060006000600060006000600060006000600060006001808155806002558060035580600455806005558060065580600755806008558060095580600a5580600b5580600c5580600d5550909192939495969798999a9b9c9d9e9f565b + +Text representation: + /* "yul_stack_opt/input.sol":495:500 */ + tag_1 + jump(tag_2) +tag_1: + /* "yul_stack_opt/input.sol":425:500 */ + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + /* "yul_stack_opt/input.sol":572:577 */ + tag_3 + jump(tag_2) +tag_3: + /* "yul_stack_opt/input.sol":502:577 */ + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + /* "yul_stack_opt/input.sol":590:592 */ + dup1 + /* "yul_stack_opt/input.sol":586:588 */ + dup3 + /* "yul_stack_opt/input.sol":579:593 */ + sstore + pop + pop + /* "yul_stack_opt/input.sol":3:423 */ + jump(tag_4) +tag_2: + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + /* "yul_stack_opt/input.sol":98:99 */ + 0x01 + /* "yul_stack_opt/input.sol":139:140 */ + dup1 + /* "yul_stack_opt/input.sol":136:137 */ + dup2 + /* "yul_stack_opt/input.sol":129:141 */ + sstore + /* "yul_stack_opt/input.sol":162:163 */ + dup1 + /* "yul_stack_opt/input.sol":151:160 */ + 0x02 + /* "yul_stack_opt/input.sol":144:164 */ + sstore + /* "yul_stack_opt/input.sol":185:186 */ + dup1 + /* "yul_stack_opt/input.sol":174:183 */ + 0x03 + /* "yul_stack_opt/input.sol":167:187 */ + sstore + /* "yul_stack_opt/input.sol":208:209 */ + dup1 + /* "yul_stack_opt/input.sol":197:206 */ + 0x04 + /* "yul_stack_opt/input.sol":190:210 */ + sstore + /* "yul_stack_opt/input.sol":231:232 */ + dup1 + /* "yul_stack_opt/input.sol":220:229 */ + 0x05 + /* "yul_stack_opt/input.sol":213:233 */ + sstore + /* "yul_stack_opt/input.sol":254:255 */ + dup1 + /* "yul_stack_opt/input.sol":243:252 */ + 0x06 + /* "yul_stack_opt/input.sol":236:256 */ + sstore + /* "yul_stack_opt/input.sol":277:278 */ + dup1 + /* "yul_stack_opt/input.sol":266:275 */ + 0x07 + /* "yul_stack_opt/input.sol":259:279 */ + sstore + /* "yul_stack_opt/input.sol":300:301 */ + dup1 + /* "yul_stack_opt/input.sol":289:298 */ + 0x08 + /* "yul_stack_opt/input.sol":282:302 */ + sstore + /* "yul_stack_opt/input.sol":323:324 */ + dup1 + /* "yul_stack_opt/input.sol":312:321 */ + 0x09 + /* "yul_stack_opt/input.sol":305:325 */ + sstore + /* "yul_stack_opt/input.sol":346:347 */ + dup1 + /* "yul_stack_opt/input.sol":335:344 */ + 0x0a + /* "yul_stack_opt/input.sol":328:348 */ + sstore + /* "yul_stack_opt/input.sol":370:371 */ + dup1 + /* "yul_stack_opt/input.sol":358:368 */ + 0x0b + /* "yul_stack_opt/input.sol":351:372 */ + sstore + /* "yul_stack_opt/input.sol":394:395 */ + dup1 + /* "yul_stack_opt/input.sol":382:392 */ + 0x0c + /* "yul_stack_opt/input.sol":375:396 */ + sstore + /* "yul_stack_opt/input.sol":418:419 */ + dup1 + /* "yul_stack_opt/input.sol":406:416 */ + 0x0d + /* "yul_stack_opt/input.sol":399:420 */ + sstore + pop + /* "yul_stack_opt/input.sol":85:423 */ + swap1 + swap2 + swap3 + swap4 + swap5 + swap6 + swap7 + swap8 + swap9 + swap10 + swap11 + swap12 + swap13 + swap14 + swap15 + swap16 + jump +tag_4: + diff --git a/test/cmdlineTests/yul_stack_opt_disabled/args b/test/cmdlineTests/yul_stack_opt_disabled/args new file mode 100644 index 000000000000..2c89c24e0a35 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/args @@ -0,0 +1 @@ +--strict-assembly diff --git a/test/cmdlineTests/yul_stack_opt_disabled/err b/test/cmdlineTests/yul_stack_opt_disabled/err new file mode 100644 index 000000000000..392200019439 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/err @@ -0,0 +1,6 @@ +Warning: Yul and its optimizer are still experimental. Please use the output with care. +Exception while assembling: +Dynamic exception type: +std::exception::what: Variable a1 is 17 slot(s) too deep inside the stack. +[dev::tag_comment*] = Variable a1 is 17 slot(s) too deep inside the stack. + diff --git a/test/cmdlineTests/yul_stack_opt_disabled/exit b/test/cmdlineTests/yul_stack_opt_disabled/exit new file mode 100644 index 000000000000..d00491fd7e5b --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/yul_stack_opt_disabled/input.sol b/test/cmdlineTests/yul_stack_opt_disabled/input.sol new file mode 100644 index 000000000000..772a6d4df9b5 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/input.sol @@ -0,0 +1,24 @@ +{ + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) +} diff --git a/test/cmdlineTests/yul_stack_opt_disabled/output b/test/cmdlineTests/yul_stack_opt_disabled/output new file mode 100644 index 000000000000..c9e3d5078f78 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/output @@ -0,0 +1,31 @@ + +======= yul_stack_opt_disabled/input.sol (EVM) ======= + +Pretty printed source: +object "object" { + code { + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) + } +} + diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 6d76c201aa87..97632e348eaa 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -268,6 +268,26 @@ BOOST_AUTO_TEST_CASE(cse_double_shift_left_overflow) } } +BOOST_AUTO_TEST_CASE(cse_byte_ordering_bug) +{ + AssemblyItems input{ + u256(31), + Instruction::CALLVALUE, + Instruction::BYTE + }; + checkCSE(input, {u256(31), Instruction::CALLVALUE, Instruction::BYTE}); +} + +BOOST_AUTO_TEST_CASE(cse_byte_ordering_fix) +{ + AssemblyItems input{ + Instruction::CALLVALUE, + u256(31), + Instruction::BYTE + }; + checkCSE(input, {u256(0xff), Instruction::CALLVALUE, Instruction::AND}); +} + BOOST_AUTO_TEST_CASE(cse_storage) { AssemblyItems input{ diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp index cc0d5e60cf55..52153eb8699b 100644 --- a/test/liblll/EndToEndTest.cpp +++ b/test/liblll/EndToEndTest.cpp @@ -1017,10 +1017,10 @@ BOOST_AUTO_TEST_CASE(string_literal) { char const* sourceCode = R"( (returnlll - (return \"hello\"))) + (return "hello")) )"; compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256("68656c6c6f000000000000000000000000000000000000000000000000000000"))); + BOOST_CHECK(callFallback() == encodeArgs(u256("0x68656c6c6f000000000000000000000000000000000000000000000000000000"))); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/liblll/ExecutionFramework.h b/test/liblll/ExecutionFramework.h index 7c1ce6708b4e..2f54afa8a854 100644 --- a/test/liblll/ExecutionFramework.h +++ b/test/liblll/ExecutionFramework.h @@ -56,7 +56,12 @@ class LLLExecutionFramework: public ExecutionFramework BOOST_REQUIRE(_libraryAddresses.empty()); std::vector errors; - bytes bytecode = lll::compileLLL(_sourceCode, dev::test::Options::get().evmVersion(), m_optimize, &errors); + bytes bytecode = lll::compileLLL( + _sourceCode, + dev::test::Options::get().evmVersion(), + m_optimiserSettings == solidity::OptimiserSettings::standard(), + &errors + ); if (!errors.empty()) { for (auto const& error: errors) diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index a8f175543d4e..ae96c3b302d4 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -18,15 +18,19 @@ * Unit tests for Solidity's ABI encoder. */ -#include -#include -#include -#include -#include #include #include +#include + +#include +#include + +#include +#include +#include + using namespace std; using namespace std::placeholders; using namespace dev::test; @@ -41,7 +45,7 @@ namespace test #define REQUIRE_LOG_DATA(DATA) do { \ BOOST_REQUIRE_EQUAL(m_logs.size(), 1); \ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); \ - BOOST_CHECK_EQUAL(toHex(m_logs[0].data), toHex(DATA)); \ + ABI_CHECK(m_logs[0].data, DATA); \ } while (false) BOOST_FIXTURE_TEST_SUITE(ABIEncoderTest, SolidityExecutionFramework) @@ -510,6 +514,204 @@ BOOST_AUTO_TEST_CASE(structs2) ) } +BOOST_AUTO_TEST_CASE(bool_arrays) +{ + string sourceCode = R"( + contract C { + bool[] x; + bool[4] y; + event E(bool[], bool[4]); + function f() public returns (bool[] memory, bool[4] memory) { + x.length = 4; + x[0] = true; + x[1] = false; + x[2] = true; + x[3] = false; + y[0] = true; + y[1] = false; + y[2] = true; + y[3] = false; + emit E(x, y); + return (x, y); // this copies to memory first + } + } + )"; + + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + bytes encoded = encodeArgs( + 0xa0, 1, 0, 1, 0, + 4, 1, 0, 1, 0 + ); + ABI_CHECK(callContractFunction("f()"), encoded); + REQUIRE_LOG_DATA(encoded); + ) +} + +BOOST_AUTO_TEST_CASE(bool_arrays_split) +{ + string sourceCode = R"( + contract C { + bool[] x; + bool[4] y; + event E(bool[], bool[4]); + function store() public { + x.length = 4; + x[0] = true; + x[1] = false; + x[2] = true; + x[3] = false; + y[0] = true; + y[1] = false; + y[2] = true; + y[3] = false; + } + function f() public returns (bool[] memory, bool[4] memory) { + emit E(x, y); + return (x, y); // this copies to memory first + } + } + )"; + + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + bytes encoded = encodeArgs( + 0xa0, 1, 0, 1, 0, + 4, 1, 0, 1, 0 + ); + ABI_CHECK(callContractFunction("store()"), bytes{}); + ABI_CHECK(callContractFunction("f()"), encoded); + REQUIRE_LOG_DATA(encoded); + ) +} + +BOOST_AUTO_TEST_CASE(bytesNN_arrays) +{ + // This tests that encoding packed arrays from storage work correctly. + string sourceCode = R"( + contract C { + bytes8[] x; + bytesWIDTH[SIZE] y; + event E(bytes8[], bytesWIDTH[SIZE]); + function store() public { + x.length = 2; + x[0] = "abc"; + x[1] = "def"; + for (uint i = 0; i < y.length; i ++) + y[i] = bytesWIDTH(uintUINTWIDTH(i + 1)); + } + function f() public returns (bytes8[] memory, bytesWIDTH[SIZE] memory) { + emit E(x, y); + return (x, y); // this copies to memory first + } + } + )"; + + BOTH_ENCODERS( + for (size_t size = 1; size < 15; size++) + { + for (size_t width: {1, 2, 4, 5, 7, 15, 16, 17, 31, 32}) + { + string source = boost::algorithm::replace_all_copy(sourceCode, "SIZE", to_string(size)); + source = boost::algorithm::replace_all_copy(source, "UINTWIDTH", to_string(width * 8)); + source = boost::algorithm::replace_all_copy(source, "WIDTH", to_string(width)); + compileAndRun(source, 0, "C"); + ABI_CHECK(callContractFunction("store()"), bytes{}); + vector arr; + for (size_t i = 0; i < size; i ++) + arr.emplace_back(u256(i + 1) << (8 * (32 - width))); + bytes encoded = encodeArgs( + 0x20 * (1 + size), arr, + 2, "abc", "def" + ); + ABI_CHECK(callContractFunction("f()"), encoded); + REQUIRE_LOG_DATA(encoded); + } + } + ) +} + +BOOST_AUTO_TEST_CASE(bytesNN_arrays_dyn) +{ + // This tests that encoding packed arrays from storage work correctly. + string sourceCode = R"( + contract C { + bytes8[] x; + bytesWIDTH[] y; + event E(bytesWIDTH[], bytes8[]); + function store() public { + x.length = 2; + x[0] = "abc"; + x[1] = "def"; + for (uint i = 0; i < SIZE; i ++) + y.push(bytesWIDTH(uintUINTWIDTH(i + 1))); + } + function f() public returns (bytesWIDTH[] memory, bytes8[] memory) { + emit E(y, x); + return (y, x); // this copies to memory first + } + } + )"; + + BOTH_ENCODERS( + for (size_t size = 0; size < 15; size++) + { + for (size_t width: {1, 2, 4, 5, 7, 15, 16, 17, 31, 32}) + { + string source = boost::algorithm::replace_all_copy(sourceCode, "SIZE", to_string(size)); + source = boost::algorithm::replace_all_copy(source, "UINTWIDTH", to_string(width * 8)); + source = boost::algorithm::replace_all_copy(source, "WIDTH", to_string(width)); + compileAndRun(source, 0, "C"); + ABI_CHECK(callContractFunction("store()"), bytes{}); + vector arr; + for (size_t i = 0; i < size; i ++) + arr.emplace_back(u256(i + 1) << (8 * (32 - width))); + bytes encoded = encodeArgs( + 0x20 * 2, 0x20 * (3 + size), + size, arr, + 2, "abc", "def" + ); + ABI_CHECK(callContractFunction("f()"), encoded); + REQUIRE_LOG_DATA(encoded); + } + } + ) +} + +BOOST_AUTO_TEST_CASE(packed_structs) +{ + string sourceCode = R"( + contract C { + struct S { bool a; int8 b; function() external g; bytes3 d; int8 e; } + S s; + event E(S); + function store() public { + s.a = false; + s.b = -5; + s.g = this.g; + s.d = 0x010203; + s.e = -3; + } + function f() public returns (S memory) { + emit E(s); + return s; // this copies to memory first + } + function g() public pure {} + } + )"; + + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("store()"), bytes{}); + bytes fun = m_contractAddress.asBytes() + fromHex("0xe2179b8e"); + bytes encoded = encodeArgs( + 0, u256(-5), asString(fun), "\x01\x02\x03", u256(-3) + ); + ABI_CHECK(callContractFunction("f()"), encoded); + REQUIRE_LOG_DATA(encoded); + ) +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index b028e5176f17..f48f022efc74 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -86,7 +86,7 @@ eth::AssemblyItems compileContract(std::shared_ptr _sourceCode) { Compiler compiler( dev::test::Options::get().evmVersion(), - dev::test::Options::get().optimize ? OptimiserSettings::enabled() : OptimiserSettings::minimal() + dev::test::Options::get().optimize ? OptimiserSettings::standard() : OptimiserSettings::minimal() ); compiler.compileContract(*contract, map>{}, bytes()); diff --git a/test/libsolidity/GasCosts.cpp b/test/libsolidity/GasCosts.cpp index 985dd3d24991..9970d0454f0b 100644 --- a/test/libsolidity/GasCosts.cpp +++ b/test/libsolidity/GasCosts.cpp @@ -41,7 +41,7 @@ namespace test u256 gasOpt{_gasOpt}; \ u256 gasNoOpt{_gasNoOpt}; \ u256 tolerance{_tolerance}; \ - u256 gas = m_optimize ? gasOpt : gasNoOpt; \ + u256 gas = m_optimiserSettings == OptimiserSettings::minimal() ? gasNoOpt : gasOpt; \ u256 diff = gas < m_gasUsed ? m_gasUsed - gas : gas - m_gasUsed; \ BOOST_CHECK_MESSAGE( \ diff <= tolerance, \ diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index ebdb46545b33..140401f10eed 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -143,22 +143,46 @@ BOOST_AUTO_TEST_CASE(complex_import) BOOST_CHECK(c.compile()); } -BOOST_AUTO_TEST_CASE(name_clash_in_import) +BOOST_AUTO_TEST_CASE(name_clash_in_import_1) { CompilerStack c; c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("b", "import \"a\"; contract A {} pragma solidity >=0.0;"); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); +} + +BOOST_AUTO_TEST_CASE(name_clash_in_import_2) +{ + CompilerStack c; + c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("b", "import \"a\" as A; contract A {} pragma solidity >=0.0;"); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); +} + +BOOST_AUTO_TEST_CASE(name_clash_in_import_3) +{ + CompilerStack c; + c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("b", "import {A as b} from \"a\"; contract b {} pragma solidity >=0.0;"); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); +} + +BOOST_AUTO_TEST_CASE(name_clash_in_import_4) +{ + CompilerStack c; + c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("b", "import {A} from \"a\"; contract A {} pragma solidity >=0.0;"); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); +} + +BOOST_AUTO_TEST_CASE(name_clash_in_import_5) +{ + CompilerStack c; + c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("b", "import {A} from \"a\"; contract B {} pragma solidity >=0.0;"); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); @@ -213,7 +237,7 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_pres BOOST_CHECK(c.compile()); } -BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent) +BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent_1) { CompilerStack c; c.setRemappings(vector{{"a", "x/y/z", "d"}, {"a/b", "x", "e"}}); @@ -223,14 +247,18 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent) c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); - CompilerStack d; - d.setRemappings(vector{{"a/b", "x", "e"}, {"a", "x/y/z", "d"}}); - d.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); - d.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); - d.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); - d.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); - d.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(d.compile()); +} + +BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent_2) +{ + CompilerStack c; + c.setRemappings(vector{{"a/b", "x", "e"}, {"a", "x/y/z", "d"}}); + c.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); + c.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); + c.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); + c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); + BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(shadowing_via_import) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 7e5c47cf27a5..149f7679caa9 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -66,8 +66,9 @@ boost::optional parseAndReturnFirstError( try { success = stack.parseAndAnalyze("", _source); + bool const optimize = false; if (success && _assemble) - stack.assemble(_machine); + stack.assemble(_machine, optimize); } catch (FatalError const&) { diff --git a/test/libsolidity/SMTCheckerJSONTest.cpp b/test/libsolidity/SMTCheckerJSONTest.cpp index 18a5bc41002e..d682c250ea37 100644 --- a/test/libsolidity/SMTCheckerJSONTest.cpp +++ b/test/libsolidity/SMTCheckerJSONTest.cpp @@ -35,8 +35,8 @@ using namespace dev; using namespace std; using namespace boost::unit_test; -SMTCheckerTest::SMTCheckerTest(string const& _filename) -: SyntaxTest(_filename) +SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion _evmVersion) +: SyntaxTest(_filename, _evmVersion) { if (!boost::algorithm::ends_with(_filename, ".sol")) BOOST_THROW_EXCEPTION(runtime_error("Invalid test contract file name: \"" + _filename + "\".")); @@ -49,7 +49,7 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename) BOOST_THROW_EXCEPTION(runtime_error("Invalid JSON file.")); } -bool SMTCheckerTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +bool SMTCheckerTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) { StandardCompiler compiler; diff --git a/test/libsolidity/SMTCheckerJSONTest.h b/test/libsolidity/SMTCheckerJSONTest.h index 256056689009..5abbbff4d9d4 100644 --- a/test/libsolidity/SMTCheckerJSONTest.h +++ b/test/libsolidity/SMTCheckerJSONTest.h @@ -35,11 +35,11 @@ class SMTCheckerTest: public SyntaxTest public: static std::unique_ptr create(Config const& _config) { - return std::unique_ptr(new SMTCheckerTest(_config.filename)); + return std::make_unique(_config.filename, _config.evmVersion); } - SMTCheckerTest(std::string const& _filename); + SMTCheckerTest(std::string const& _filename, langutil::EVMVersion _evmVersion); - bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; private: std::vector hashesFromJson(Json::Value const& _jsonObj, std::string const& _auxInput, std::string const& _smtlib); diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index f8c9fa110cd3..1dcf91719f67 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -36,8 +36,8 @@ using namespace boost::unit_test; namespace fs = boost::filesystem; -SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath): - SolidityExecutionFramework(_ipcPath) +SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, langutil::EVMVersion _evmVersion): + SolidityExecutionFramework(_ipcPath, _evmVersion) { ifstream file(_filename); soltestAssert(file, "Cannot open test contract: \"" + _filename + "\"."); @@ -47,7 +47,7 @@ SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath): parseExpectations(file); } -bool SemanticTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +bool SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) { soltestAssert(deploy("", 0, bytes()), "Failed to deploy contract."); @@ -87,7 +87,7 @@ bool SemanticTest::run(ostream& _stream, string const& _linePrefix, bool const _ return true; } -void SemanticTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const +void SemanticTest::printSource(ostream& _stream, string const& _linePrefix, bool) const { stringstream stream(m_source); string line; diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index ccc8812a187b..27e64422041d 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -44,12 +44,12 @@ class SemanticTest: public SolidityExecutionFramework, public TestCase { public: static std::unique_ptr create(Config const& _options) - { return std::make_unique(_options.filename, _options.ipcPath); } + { return std::make_unique(_options.filename, _options.ipcPath, _options.evmVersion); } - explicit SemanticTest(std::string const& _filename, std::string const& _ipcPath); + explicit SemanticTest(std::string const& _filename, std::string const& _ipcPath, langutil::EVMVersion _evmVersion); - bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; - void printSource(std::ostream &_stream, std::string const& _linePrefix = "", bool const _formatted = false) const override; + bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; + void printSource(std::ostream &_stream, std::string const& _linePrefix = "", bool _formatted = false) const override; void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix = "") const override; /// Instantiates a test file parser that parses the additional comment section at the end of diff --git a/test/libsolidity/SolidityCompiler.cpp b/test/libsolidity/SolidityCompiler.cpp index 57a7c8d1baf7..10bdf9a86fca 100644 --- a/test/libsolidity/SolidityCompiler.cpp +++ b/test/libsolidity/SolidityCompiler.cpp @@ -19,7 +19,7 @@ */ #include - +#include #include using namespace std; @@ -31,7 +31,7 @@ namespace solidity namespace test { -BOOST_FIXTURE_TEST_SUITE(Compiler, AnalysisFramework) +BOOST_FIXTURE_TEST_SUITE(SolidityCompiler, AnalysisFramework) BOOST_AUTO_TEST_CASE(does_not_include_creation_time_only_internal_functions) { @@ -45,12 +45,12 @@ BOOST_AUTO_TEST_CASE(does_not_include_creation_time_only_internal_functions) m_compiler.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE(success(sourceCode)); BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); - bytes const& creationBytecode = m_compiler.object("C").bytecode; - bytes const& runtimeBytecode = m_compiler.runtimeObject("C").bytecode; - BOOST_CHECK(creationBytecode.size() >= 130); - BOOST_CHECK(creationBytecode.size() <= 160); - BOOST_CHECK(runtimeBytecode.size() >= 50); - BOOST_CHECK(runtimeBytecode.size() <= 70); + bytes const& creationBytecode = dev::test::bytecodeSansMetadata(m_compiler.object("C").bytecode); + bytes const& runtimeBytecode = dev::test::bytecodeSansMetadata(m_compiler.runtimeObject("C").bytecode); + BOOST_CHECK(creationBytecode.size() >= 90); + BOOST_CHECK(creationBytecode.size() <= 120); + BOOST_CHECK(runtimeBytecode.size() >= 10); + BOOST_CHECK(runtimeBytecode.size() <= 30); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 0dc1ad22c578..127e60e193b8 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -10495,6 +10495,27 @@ BOOST_AUTO_TEST_CASE(fixed_bytes_length_access) ABI_CHECK(callContractFunction("f(bytes32)", "789"), encodeArgs(u256(32), u256(16), u256(8))); } +BOOST_AUTO_TEST_CASE(byte_optimization_bug) +{ + char const* sourceCode = R"( + contract C { + function f(uint x) public returns (uint a) { + assembly { + a := byte(x, 31) + } + } + function g(uint x) public returns (uint a) { + assembly { + a := byte(31, x) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("g(uint256)", u256(2)), encodeArgs(u256(2))); +} + BOOST_AUTO_TEST_CASE(inline_assembly_write_to_stack) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityExecutionFramework.cpp b/test/libsolidity/SolidityExecutionFramework.cpp index 934c563f44ff..1b2322e898c8 100644 --- a/test/libsolidity/SolidityExecutionFramework.cpp +++ b/test/libsolidity/SolidityExecutionFramework.cpp @@ -33,7 +33,7 @@ SolidityExecutionFramework::SolidityExecutionFramework(): { } -SolidityExecutionFramework::SolidityExecutionFramework(std::string const& _ipcPath): - ExecutionFramework(_ipcPath) +SolidityExecutionFramework::SolidityExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion _evmVersion): + ExecutionFramework(_ipcPath, _evmVersion) { } diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 37adbdf4e457..900d0b9d8896 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -43,7 +43,7 @@ class SolidityExecutionFramework: public dev::test::ExecutionFramework public: SolidityExecutionFramework(); - SolidityExecutionFramework(std::string const& _ipcPath); + SolidityExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion _evmVersion); virtual bytes const& compileAndRunWithoutCheck( std::string const& _sourceCode, @@ -73,7 +73,7 @@ class SolidityExecutionFramework: public dev::test::ExecutionFramework m_compiler.addSource("", sourceCode); m_compiler.setLibraries(_libraryAddresses); m_compiler.setEVMVersion(m_evmVersion); - m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); + m_compiler.setOptimiserSettings(m_optimiserSettings); if (!m_compiler.compile()) { langutil::SourceReferenceFormatter formatter(std::cerr); diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 22ca9182f84c..419bc8ad8b70 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -54,13 +54,13 @@ class OptimizerTestFramework: public SolidityExecutionFramework unsigned const _optimizeRuns = 200 ) { - bool const c_optimize = m_optimize; - unsigned const c_optimizeRuns = m_optimizeRuns; - m_optimize = _optimize; - m_optimizeRuns = _optimizeRuns; + OptimiserSettings previousSettings = std::move(m_optimiserSettings); + // This uses "none" / "full" while most other test frameworks use + // "minimal" / "standard". + m_optimiserSettings = _optimize ? OptimiserSettings::full() : OptimiserSettings::none(); + m_optimiserSettings.expectedExecutionsPerDeployment = _optimizeRuns; bytes const& ret = compileAndRun(_sourceCode, _value, _contractName); - m_optimize = c_optimize; - m_optimizeRuns = c_optimizeRuns; + m_optimiserSettings = std::move(previousSettings); return ret; } @@ -665,6 +665,42 @@ BOOST_AUTO_TEST_CASE(optimise_constant_to_codecopy) BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::CODECOPY), 4); } +BOOST_AUTO_TEST_CASE(byte_access) +{ + char const* sourceCode = R"( + contract C + { + function f(bytes32 x) public returns (byte r) + { + assembly { r := and(byte(x, 31), 0xff) } + } + } + )"; + compileBothVersions(sourceCode); + compareVersions("f(bytes32)", u256("0x1223344556677889900112233445566778899001122334455667788990011223")); +} + +BOOST_AUTO_TEST_CASE(shift_optimizer_bug) +{ + char const* sourceCode = R"( + contract C + { + function f(uint x) public returns (uint) + { + return (x << 1) << uint(-1); + } + function g(uint x) public returns (uint) + { + return (x >> 1) >> uint(-1); + } + } + )"; + compileBothVersions(sourceCode); + compareVersions("f(uint256)", 7); + compareVersions("g(uint256)", u256(-1)); +} + + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 050fdaf2e25e..3f60c3548bba 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -243,6 +243,50 @@ BOOST_AUTO_TEST_CASE(encoded_sizes) BOOST_CHECK_EQUAL(twoDimArray.calldataEncodedSize(false), 9 * 3 * 32); } +BOOST_AUTO_TEST_CASE(helper_bool_result) +{ + BoolResult r1{true}; + BoolResult r2 = BoolResult::err("Failure."); + r1.merge(r2, logical_and()); + BOOST_REQUIRE_EQUAL(r1.get(), false); + BOOST_REQUIRE_EQUAL(r1.message(), "Failure."); + + BoolResult r3{false}; + BoolResult r4{true}; + r3.merge(r4, logical_and()); + BOOST_REQUIRE_EQUAL(r3.get(), false); + BOOST_REQUIRE_EQUAL(r3.message(), ""); + + BoolResult r5{true}; + BoolResult r6{true}; + r5.merge(r6, logical_and()); + BOOST_REQUIRE_EQUAL(r5.get(), true); + BOOST_REQUIRE_EQUAL(r5.message(), ""); + + BoolResult r7{true}; + // Attention: this will implicitly convert to bool. + BoolResult r8{"true"}; + r7.merge(r8, logical_and()); + BOOST_REQUIRE_EQUAL(r7.get(), true); + BOOST_REQUIRE_EQUAL(r7.message(), ""); +} + +BOOST_AUTO_TEST_CASE(helper_string_result) +{ + using StringResult = Result; + + StringResult r1{string{"Success"}}; + StringResult r2 = StringResult::err("Failure"); + + BOOST_REQUIRE_EQUAL(r1.get(), "Success"); + BOOST_REQUIRE_EQUAL(r2.get(), ""); + + r1.merge(r2, [](string const&, string const& _rhs) { return _rhs; }); + + BOOST_REQUIRE_EQUAL(r1.get(), ""); + BOOST_REQUIRE_EQUAL(r1.message(), "Failure"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index f2c8f958260f..4b837d6e7aef 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -124,11 +124,12 @@ BOOST_AUTO_TEST_CASE(invalid_language) { char const* input = R"( { - "language": "INVALID" + "language": "INVALID", + "sources": { "name": { "content": "abc" } } } )"; Json::Value result = compile(input); - BOOST_CHECK(containsError(result, "JSONError", "Only \"Solidity\" is supported as a language.")); + BOOST_CHECK(containsError(result, "JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language.")); } BOOST_AUTO_TEST_CASE(valid_language) @@ -139,7 +140,7 @@ BOOST_AUTO_TEST_CASE(valid_language) } )"; Json::Value result = compile(input); - BOOST_CHECK(!containsError(result, "JSONError", "Only \"Solidity\" is supported as a language.")); + BOOST_CHECK(!containsError(result, "JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language.")); } BOOST_AUTO_TEST_CASE(no_sources) @@ -995,6 +996,12 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) )"; Json::Value result = compile(input); BOOST_CHECK(containsAtMostWarnings(result)); + BOOST_CHECK(containsError( + result, + "Warning", + "The Yul optimiser is still experimental. " + "Do not use it in production unless correctness of generated code is verified with extensive tests." + )); Json::Value contract = getContractResult(result, "fileA", "A"); BOOST_CHECK(contract.isObject()); BOOST_CHECK(contract["metadata"].isString()); @@ -1010,7 +1017,10 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) BOOST_CHECK(optimizer["details"]["jumpdestRemover"].asBool() == true); BOOST_CHECK(optimizer["details"]["orderLiterals"].asBool() == false); BOOST_CHECK(optimizer["details"]["peephole"].asBool() == true); + BOOST_CHECK(optimizer["details"]["yul"].asBool() == true); BOOST_CHECK(optimizer["details"]["yulDetails"].isObject()); + BOOST_CHECK(optimizer["details"]["yulDetails"].getMemberNames() == vector{"stackAllocation"}); + BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true); BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 8); BOOST_CHECK(optimizer["runs"].asUInt() == 600); } @@ -1077,6 +1087,75 @@ BOOST_AUTO_TEST_CASE(common_pattern) BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString()); } +BOOST_AUTO_TEST_CASE(use_stack_optimization) +{ + // NOTE: the contract code here should fail to compile due to "out of stack" + // If we enable stack optimization, though, it will compile. + char const* input = R"( + { + "language": "Solidity", + "settings": { + "optimizer": { "enabled": true, "details": { "yul": true } }, + "outputSelection": { + "fileA": { "A": [ "evm.bytecode.object" ] } + } + }, + "sources": { + "fileA": { + "content": "contract A { + function y() public { + assembly { + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) + } + } + }" + } + } + } + )"; + + Json::Value parsedInput; + BOOST_REQUIRE(jsonParseStrict(input, parsedInput)); + + dev::solidity::StandardCompiler compiler; + Json::Value result = compiler.compile(parsedInput); + + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_REQUIRE(contract.isObject()); + BOOST_REQUIRE(contract["evm"]["bytecode"]["object"].isString()); + BOOST_CHECK(contract["evm"]["bytecode"]["object"].asString().length() > 20); + + // Now disable stack optimizations + // results in "stack too deep" + parsedInput["settings"]["optimizer"]["details"]["yulDetails"]["stackAllocation"] = false; + result = compiler.compile(parsedInput); + BOOST_REQUIRE(result["errors"].isArray()); + BOOST_CHECK(result["errors"][0]["severity"] == "error"); + BOOST_CHECK(result["errors"][0]["type"] == "InternalCompilerError"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index f13b2e79cde2..d9900ab2bbf4 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -52,7 +52,7 @@ int parseUnsignedInteger(string::iterator& _it, string::iterator _end) } -SyntaxTest::SyntaxTest(string const& _filename) +SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) { ifstream file(_filename); if (!file) @@ -63,12 +63,12 @@ SyntaxTest::SyntaxTest(string const& _filename) m_expectations = parseExpectations(file); } -bool SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +bool SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) { string const versionPragma = "pragma solidity >=0.0;\n"; m_compiler.reset(); m_compiler.addSource("", versionPragma + m_source); - m_compiler.setEVMVersion(dev::test::Options::get().evmVersion()); + m_compiler.setEVMVersion(m_evmVersion); if (m_compiler.parse()) m_compiler.analyze(); @@ -95,7 +95,7 @@ bool SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool const _fo return printExpectationAndError(_stream, _linePrefix, _formatted); } -bool SyntaxTest::printExpectationAndError(ostream& _stream, string const& _linePrefix, bool const _formatted) +bool SyntaxTest::printExpectationAndError(ostream& _stream, string const& _linePrefix, bool _formatted) { if (m_expectations != m_errorList) { @@ -109,7 +109,7 @@ bool SyntaxTest::printExpectationAndError(ostream& _stream, string const& _lineP return true; } -void SyntaxTest::printSource(ostream& _stream, string const& _linePrefix, bool const _formatted) const +void SyntaxTest::printSource(ostream& _stream, string const& _linePrefix, bool _formatted) const { if (_formatted) { @@ -162,7 +162,7 @@ void SyntaxTest::printErrorList( ostream& _stream, vector const& _errorList, string const& _linePrefix, - bool const _formatted + bool _formatted ) { if (_errorList.empty()) diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index c331636aba11..a7f5d0b98b6e 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -54,12 +54,12 @@ class SyntaxTest: AnalysisFramework, public TestCase { public: static std::unique_ptr create(Config const& _config) - { return std::unique_ptr(new SyntaxTest(_config.filename)); } - SyntaxTest(std::string const& _filename); + { return std::make_unique(_config.filename, _config.evmVersion); } + SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion); - bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; - void printSource(std::ostream &_stream, std::string const &_linePrefix = "", bool const _formatted = false) const override; + void printSource(std::ostream &_stream, std::string const &_linePrefix = "", bool _formatted = false) const override; void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override { if (!m_errorList.empty()) @@ -72,16 +72,17 @@ class SyntaxTest: AnalysisFramework, public TestCase std::ostream& _stream, std::vector const& _errors, std::string const& _linePrefix, - bool const _formatted = false + bool _formatted = false ); - virtual bool printExpectationAndError(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false); + virtual bool printExpectationAndError(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false); static std::vector parseExpectations(std::istream& _stream); std::string m_source; std::vector m_expectations; std::vector m_errorList; + langutil::EVMVersion const m_evmVersion; }; } diff --git a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol new file mode 100644 index 000000000000..f2016c967099 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol @@ -0,0 +1,32 @@ +contract C { + function f() public returns (uint) { + return 0; + } + function f(uint a) public returns (uint) { + return a; + } + function f(uint a, uint b) public returns (uint) { + return a+b; + } + function f(uint a, uint b, uint c) public returns (uint) { + return a+b+c; + } + function call(uint num) public returns (uint256) { + if (num == 0) + return f(); + if (num == 1) + return f({a: 1}); + if (num == 2) + return f({b: 1, a: 2}); + if (num == 3) + return f({c: 1, a: 2, b: 3}); + + return 500; + } +} +// ---- +// call(uint256): 0 -> 0 +// call(uint256): 1 -> 1 +// call(uint256): 2 -> 3 +// call(uint256): 3 -> 6 +// call(uint256): 4 -> 500 diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol new file mode 100644 index 000000000000..0b2e080fb91a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol @@ -0,0 +1,20 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public { + x = 0; + assert((f() > 0) || (f() > 0)); + // This assertion should NOT fail. + // It currently does because the SMTChecker does not + // handle short-circuiting properly and inlines f() twice. + assert(x == 1); + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (344-358): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol new file mode 100644 index 000000000000..d4b486767a35 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + + modifier m { + require(x > 0); + _; + // Fails because of overflow behavior. + // Overflow is not reported because this assertion prevents + // it from reaching the end of the function. + assert(x > 1); + } + + function f() m public { + assert(x > 0); + x = x + 1; + } +} +// ---- +// Warning: (245-258): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_control_flow.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_control_flow.sol new file mode 100644 index 000000000000..7a8d9264d69e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_control_flow.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + + modifier m { + if (x == 0) + _; + } + + function f() m public view { + assert(x == 0); + assert(x > 1); + } +} +// ---- +// Warning: (144-157): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol new file mode 100644 index 000000000000..8a508cd68400 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol @@ -0,0 +1,25 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + + modifier m { + require(x > 0); + require(x < 10000); + _; + } + + modifier n { + x = x + 1; + _; + assert(x > 2); + assert(x > 8); + } + + function f() m n public { + x = x + 1; + } +} +// ---- +// Warning: (170-183): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions.sol new file mode 100644 index 000000000000..903d4f91c164 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions.sol @@ -0,0 +1,26 @@ +pragma experimental SMTChecker; + +contract C +{ + modifier m(uint a, uint b) { + require(g(a, b)); + _; + } + + modifier notZero(uint x) { + require(x > 0); + _; + } + + function g(uint a, uint b) notZero(a) internal pure returns (bool) { + return a > b; + } + + function f(uint x) m(x, 0) public pure { + assert(x > 0); + assert(x > 1); + } +} +// ---- +// Warning: (86-93): Condition is always true. +// Warning: (311-324): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions_recursive.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions_recursive.sol new file mode 100644 index 000000000000..4346b2cc2f5c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions_recursive.sol @@ -0,0 +1,24 @@ +pragma experimental SMTChecker; + +contract C +{ + modifier m(uint a, uint b) { + require(g(a, b)); + _; + } + + function g(uint a, uint b) m(a, b) internal pure returns (bool) { + return a > b; + } + + function f(uint x) m(x, 0) public pure { + assert(x > 0); + assert(x > 1); + } +} +// ---- +// Warning: (86-93): Assertion checker does not support recursive function calls. +// Warning: (86-93): Internal error: Expression undefined for SMT solver. +// Warning: (86-93): Assertion checker does not support recursive function calls. +// Warning: (86-93): Internal error: Expression undefined for SMT solver. +// Warning: (253-266): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_parameters.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_parameters.sol new file mode 100644 index 000000000000..ec6ce47de998 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi_parameters.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C +{ + modifier m(uint a, uint b) { + require(a > b); + _; + } + + function f(uint x) m(x, 0) public pure { + assert(x > 0); + assert(x > 1); + } +} +// ---- +// Warning: (164-177): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_overflow.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_overflow.sol new file mode 100644 index 000000000000..60b35e59dfa6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_overflow.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + + modifier m { + require(x > 0); + _; + } + + function f() m public { + assert(x > 0); + x = x + 1; + } +} +// ---- +// Warning: (145-150): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_parameter_copy.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_parameter_copy.sol new file mode 100644 index 000000000000..b91d2ee51608 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_parameter_copy.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + modifier m(uint x) { + x == 2; + _; + } + + function f(uint x) m(x) public pure { + assert(x == 2); + } +} +// ---- +// Warning: (128-142): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_parameters.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_parameters.sol new file mode 100644 index 000000000000..90eab0afd68c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_parameters.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + uint s; + modifier m(uint a) { + // Condition is always true for m(2). + require(a > 0); + _; + } + + function f(uint x) m(x) m(2) m(s) public view { + assert(x > 0); + assert(s > 0); + } +} +// ---- +// Warning: (127-132): Condition is always true. diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_return.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_return.sol new file mode 100644 index 000000000000..2681b896f0aa --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_return.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract C +{ + modifier m(uint x) { + require(x == 2); + _; + return; + } + + modifier n(uint x) { + require(x == 3); + _; + } + + function f(uint x) m(x) n(x) public pure { + assert(x == 3); + } +} +// ---- +// Warning: (138-144): Condition is always false. diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_same_local_variables.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_same_local_variables.sol new file mode 100644 index 000000000000..7c43a311d8e0 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_same_local_variables.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + modifier m { + uint x = 2; + _; + } + + function f(uint x) m public pure { + assert(x == 2); + } +} +// ---- +// Warning: (121-135): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_simple.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_simple.sol new file mode 100644 index 000000000000..47df698724f7 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_simple.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + + modifier m { + require(x > 0); + _; + } + + function f() m public view { + assert(x > 0); + } +} diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_two_invocations.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_two_invocations.sol new file mode 100644 index 000000000000..f954cf7ca985 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_two_invocations.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + + modifier m { + // Condition is always true for the second invocation. + require(x > 0); + require(x < 10000); + _; + assert(x > 1); + } + + function f() m m public { + x = x + 1; + } +} +// ---- +// Warning: (137-142): Condition is always true. +// Warning: (155-164): Condition is always true. diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol new file mode 100644 index 000000000000..288506607490 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + + modifier m { + require(x > 0); + require(x < 10000); + _; + assert(x > 1); + _; + assert(x > 2); + assert(x > 10); + } + + function f() m public { + x = x + 1; + } +} +// ---- +// Warning: (156-170): Assertion violation happens here diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol new file mode 100644 index 000000000000..589d22ff39ca --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol @@ -0,0 +1,14 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + f({x: 1}); + f({x: 1, y: 2}); + f({y: 2, x: 1}); + f({x: 1, y: 2, z: 3}); + f({z: 3, x: 1, y: 2}); + f({y: 2, z: 3, x: 1}); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol new file mode 100644 index 000000000000..49ec2921a563 --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol @@ -0,0 +1,14 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + f(1, 2); + f(1); + + f({x: 1, y: 2}); + f({y: 2}); + } +} +// ---- +// TypeError: (241-242): No matching declaration found after argument-dependent lookup. diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol new file mode 100644 index 000000000000..be08f7e426a0 --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol @@ -0,0 +1,12 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + + f({x:1, y: 2}); + f({x:1, z: 3}); + } +} +// ---- +// TypeError: (209-210): No matching declaration found after argument-dependent lookup. diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol new file mode 100644 index 000000000000..1f315579e66e --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol @@ -0,0 +1,11 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + f({x:1, y: 2, z: 3}); + f({y:2, v: 10, z: 3}); + } +} +// ---- +// TypeError: (214-215): No matching declaration found after argument-dependent lookup. diff --git a/test/libsolidity/syntaxTests/functionTypes/internal_function_array_memory_as_external_parameter_in_library_external.sol b/test/libsolidity/syntaxTests/functionTypes/internal_function_array_memory_as_external_parameter_in_library_external.sol new file mode 100644 index 000000000000..5ca4721b76af --- /dev/null +++ b/test/libsolidity/syntaxTests/functionTypes/internal_function_array_memory_as_external_parameter_in_library_external.sol @@ -0,0 +1,6 @@ +library L { + // Used to cause internal error + function f(function(uint) internal returns (uint)[] memory x) public { } +} +// ---- +// TypeError: (63-112): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/functionTypes/internal_function_array_storage_as_external_parameter_in_library_external.sol b/test/libsolidity/syntaxTests/functionTypes/internal_function_array_storage_as_external_parameter_in_library_external.sol new file mode 100644 index 000000000000..9ae10f44d9d3 --- /dev/null +++ b/test/libsolidity/syntaxTests/functionTypes/internal_function_array_storage_as_external_parameter_in_library_external.sol @@ -0,0 +1,6 @@ +library L { + // Used to cause internal error + function g(function(uint) internal returns (uint)[] storage x) public { } +} +// ---- +// TypeError: (63-113): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter.sol b/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter.sol index fa92d5597b8e..c3aaa30ccd82 100644 --- a/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter.sol +++ b/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// TypeError: (124-164): Internal or recursive type is not allowed for public or external functions. +// TypeError: (124-164): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter_in_library_external.sol b/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter_in_library_external.sol index b37fb285c5b3..d464dc35e2a3 100644 --- a/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter_in_library_external.sol +++ b/test/libsolidity/syntaxTests/functionTypes/internal_function_as_external_parameter_in_library_external.sol @@ -3,4 +3,4 @@ library L { } } // ---- -// TypeError: (27-67): Internal or recursive type is not allowed for public or external functions. +// TypeError: (27-67): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/functionTypes/internal_function_returned_from_public_function.sol b/test/libsolidity/syntaxTests/functionTypes/internal_function_returned_from_public_function.sol index 41fcd0a44472..5b36cc8b859e 100644 --- a/test/libsolidity/syntaxTests/functionTypes/internal_function_returned_from_public_function.sol +++ b/test/libsolidity/syntaxTests/functionTypes/internal_function_returned_from_public_function.sol @@ -4,4 +4,4 @@ contract C { } } // ---- -// TypeError: (129-169): Internal or recursive type is not allowed for public or external functions. +// TypeError: (129-169): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/functionTypes/internal_function_struct_as_external_parameter_in_library_external.sol b/test/libsolidity/syntaxTests/functionTypes/internal_function_struct_as_external_parameter_in_library_external.sol new file mode 100644 index 000000000000..02d5cca91d1e --- /dev/null +++ b/test/libsolidity/syntaxTests/functionTypes/internal_function_struct_as_external_parameter_in_library_external.sol @@ -0,0 +1,9 @@ +library L { + struct S + { + function(uint) internal returns (uint)[] x; + } + function f(S storage s) public { } +} +// ---- +// TypeError: (104-115): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol b/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol index 8430c5c3e3f8..abfc1eb87a5f 100644 --- a/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol +++ b/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol @@ -3,4 +3,4 @@ contract C { function f() costs(1 ether) public view {} } // ---- -// TypeError: (101-115): This modifier uses "msg.value" and thus the function has to be payable or internal. +// TypeError: (101-115): This modifier uses "msg.value" or "callvalue()" and thus the function has to be payable or internal. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/041_functions_with_stucts_of_non_external_types_in_interface.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/041_functions_with_stucts_of_non_external_types_in_interface.sol index 73b608aed6b4..57c60d89348a 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/041_functions_with_stucts_of_non_external_types_in_interface.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/041_functions_with_stucts_of_non_external_types_in_interface.sol @@ -6,4 +6,4 @@ contract C { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (103-111): Internal or recursive type is not allowed for public or external functions. +// TypeError: (103-111): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/042_functions_with_stucts_of_non_external_types_in_interface_2.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/042_functions_with_stucts_of_non_external_types_in_interface_2.sol index 607a4a68513f..1d1da7f1fbf2 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/042_functions_with_stucts_of_non_external_types_in_interface_2.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/042_functions_with_stucts_of_non_external_types_in_interface_2.sol @@ -6,4 +6,4 @@ contract C { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (105-113): Internal or recursive type is not allowed for public or external functions. +// TypeError: (105-113): Only libraries are allowed to use the mapping type in public or external functions. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/043_functions_with_stucts_of_non_external_types_in_interface_nested.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/043_functions_with_stucts_of_non_external_types_in_interface_nested.sol index da73d8ddfb4d..0ddcf438a1b5 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/043_functions_with_stucts_of_non_external_types_in_interface_nested.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/043_functions_with_stucts_of_non_external_types_in_interface_nested.sol @@ -7,4 +7,4 @@ contract C { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (132-140): Internal or recursive type is not allowed for public or external functions. +// TypeError: (132-140): Only libraries are allowed to use the mapping type in public or external functions. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/397_warns_msg_value_in_non_payable_public_function.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/397_warns_msg_value_in_non_payable_public_function.sol index c56ad25f9fbf..6797857af896 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/397_warns_msg_value_in_non_payable_public_function.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/397_warns_msg_value_in_non_payable_public_function.sol @@ -4,4 +4,4 @@ contract C { } } // ---- -// TypeError: (52-61): "msg.value" can only be used in payable public functions. Make the function "payable" or use an internal function to avoid this error. +// TypeError: (52-61): "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. diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_contract_function_parameter.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_contract_function_parameter.sol new file mode 100644 index 000000000000..4bce69bca8f6 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_contract_function_parameter.sol @@ -0,0 +1,10 @@ +contract Test { + struct MyStructName { + address addr; + MyStructName[] x; + } + + function f(MyStructName memory s) public {} +} +// ---- +// TypeError: (112-133): Recursive type not allowed for public or external contract functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_library_function_parameter.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_library_function_parameter.sol new file mode 100644 index 000000000000..37685dc8d0c1 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_library_function_parameter.sol @@ -0,0 +1,9 @@ +library Test { + struct MyStructName { + address addr; + MyStructName[] x; + } + + function f(MyStructName storage s) public {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_memory_library_function_parameter.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_memory_library_function_parameter.sol new file mode 100644 index 000000000000..0c5d5dac9ddc --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_memory_library_function_parameter.sol @@ -0,0 +1,14 @@ +pragma experimental ABIEncoderV2; + +library Test { + struct MyStructName { + address addr; + MyStructName[] x; + } + + function f(MyStructName memory _x) public { + } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (146-168): Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol index d2a411ec82ae..e2d1a4d11c3d 100644 --- a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol @@ -8,4 +8,4 @@ contract Data { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (63-78): Internal or recursive type is not allowed for public or external functions. +// TypeError: (63-78): Recursive type not allowed for public or external contract functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_with_internal_function_as_library_function_parameter.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_with_internal_function_as_library_function_parameter.sol new file mode 100644 index 000000000000..f9418bea5888 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_with_internal_function_as_library_function_parameter.sol @@ -0,0 +1,11 @@ +library Test { + struct MyStructName { + address addr; + MyStructName[] x; + function() internal y; + } + + function f(MyStructName storage s) public {} +} +// ---- +// TypeError: (142-164): Internal type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs.sol b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs.sol index c8f9185c146e..22885e95686f 100644 --- a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs.sol +++ b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs.sol @@ -4,4 +4,4 @@ contract C { } } // ---- -// TypeError: (91-99): Internal or recursive type is not allowed for public or external functions. +// TypeError: (91-99): Recursive type not allowed for public or external contract functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs2.sol b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs2.sol index a8b7ac759048..2ead307d623d 100644 --- a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs2.sol +++ b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs2.sol @@ -4,4 +4,4 @@ contract C { } } // ---- -// TypeError: (94-102): Internal or recursive type is not allowed for public or external functions. +// TypeError: (94-102): Recursive type not allowed for public or external contract functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs3.sol b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs3.sol index 0a5b1bc8d348..c47df25bf060 100644 --- a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs3.sol +++ b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs3.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// TypeError: (119-129): Internal or recursive type is not allowed for public or external functions. +// TypeError: (119-129): Recursive type not allowed for public or external contract functions. diff --git a/test/libsolidity/syntaxTests/types/mapping/mapping_array_data_location_function_param_external.sol b/test/libsolidity/syntaxTests/types/mapping/mapping_array_data_location_function_param_external.sol index 0c29ebd8b3fd..ffe757474271 100644 --- a/test/libsolidity/syntaxTests/types/mapping/mapping_array_data_location_function_param_external.sol +++ b/test/libsolidity/syntaxTests/types/mapping/mapping_array_data_location_function_param_external.sol @@ -3,4 +3,4 @@ contract c { } // ---- // TypeError: (29-61): Type is required to live outside storage. -// TypeError: (29-61): Internal or recursive type is not allowed for public or external functions. +// TypeError: (29-61): Only libraries are allowed to use the mapping type in public or external functions. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_fallback.sol b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_fallback.sol new file mode 100644 index 000000000000..d019110e4611 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_fallback.sol @@ -0,0 +1,11 @@ +contract C +{ + function () external { + uint x; + assembly { + x := callvalue() + } + } +} +// ---- +// TypeError: (92-103): "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. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function.sol b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function.sol new file mode 100644 index 000000000000..edc413e2d07f --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function.sol @@ -0,0 +1,10 @@ +contract C +{ + function f(uint x) public { + assembly { + x := callvalue() + } + } +} +// ---- +// TypeError: (81-92): "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. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_internal.sol b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_internal.sol new file mode 100644 index 000000000000..63014543e5b7 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_internal.sol @@ -0,0 +1,11 @@ +contract C +{ + function f() internal returns (uint x) { + assembly { + x := callvalue() + } + } + function g() public returns (uint) { + return f(); + } +} diff --git a/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_modifier.sol b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_modifier.sol new file mode 100644 index 000000000000..916b0d2dbdbd --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_modifier.sol @@ -0,0 +1,14 @@ +contract C +{ + modifier m { + uint x; + assembly { + x := callvalue() + } + _; + } + function f() m public { + } +} +// ---- +// TypeError: (99-100): This modifier uses "msg.value" or "callvalue()" and thus the function has to be payable or internal. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_fallback.sol b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_fallback.sol new file mode 100644 index 000000000000..7e87fd0fc45b --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_fallback.sol @@ -0,0 +1,9 @@ +contract C +{ + function () external payable { + uint x; + assembly { + x := callvalue() + } + } +} diff --git a/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function.sol b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function.sol new file mode 100644 index 000000000000..77dee8bda450 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function.sol @@ -0,0 +1,8 @@ +contract C +{ + function f(uint x) public payable { + assembly { + x := callvalue() + } + } +} diff --git a/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function_modifier.sol b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function_modifier.sol new file mode 100644 index 000000000000..9938fadd889e --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function_modifier.sol @@ -0,0 +1,12 @@ +contract C +{ + modifier m { + uint x; + assembly { + x := callvalue() + } + _; + } + function f() m public payable { + } +} diff --git a/test/libsolidity/syntaxTests/viewPureChecker/msg_value_modifier_view.sol b/test/libsolidity/syntaxTests/viewPureChecker/msg_value_modifier_view.sol index 613b01980abb..8c0df6e9aa47 100644 --- a/test/libsolidity/syntaxTests/viewPureChecker/msg_value_modifier_view.sol +++ b/test/libsolidity/syntaxTests/viewPureChecker/msg_value_modifier_view.sol @@ -3,4 +3,4 @@ contract C { function f() m(1 ether, msg.value) public view {} } // ---- -// TypeError: (118-127): "msg.value" can only be used in payable public functions. Make the function "payable" or use an internal function to avoid this error. +// TypeError: (118-127): "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. diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index 0e84cd8a4536..8782fe01d7ae 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -291,7 +291,7 @@ tuple TestFileParser::parseABITypeLiteral() if (alignment != DeclaredAlignment::None) throw Error(Error::Type::ParserError, "Hex string literals cannot be aligned or padded."); string parsed = parseHexNumber(); - rawString += parsed; + rawString += "hex\"" + parsed + "\""; result = convertHexString(parsed); abiType = ABIType{ABIType::HexString, ABIType::AlignNone, result.size()}; } diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index f10dd855cc57..1eef4d966ac4 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -124,13 +124,21 @@ string TestFunctionCall::formatBytesParameters(bytes const& _bytes, dev::solidit stringstream resultStream; if (_bytes.empty()) return {}; + auto sizeFold = [](size_t const _a, Parameter const& _b) { return _a + _b.abiType.size; }; + size_t encodingSize = std::accumulate(_params.begin(), _params.end(), size_t{0}, sizeFold); + + soltestAssert( + encodingSize == _bytes.size(), + "Encoding does not match byte range: the call returned " + + to_string(_bytes.size()) + " bytes, but " + + to_string(encodingSize) + " bytes were expected." + ); + auto it = _bytes.begin(); for (auto const& param: _params) { long offset = static_cast(param.abiType.size); auto offsetIter = it + offset; - soltestAssert(offsetIter <= _bytes.end(), "Byte range can not be extended past the end of given bytes."); - bytes byteRange{it, offsetIter}; switch (param.abiType.type) { @@ -176,7 +184,6 @@ string TestFunctionCall::formatBytesParameters(bytes const& _bytes, dev::solidit if (it != _bytes.end() && !(param.abiType.type == ABIType::None)) resultStream << ", "; } - soltestAssert(it == _bytes.end(), "Parameter encoding too short for the given byte range."); return resultStream.str(); } diff --git a/test/libyul/CompilabilityChecker.cpp b/test/libyul/CompilabilityChecker.cpp index ffd3e1816f9e..45188c8c6333 100644 --- a/test/libyul/CompilabilityChecker.cpp +++ b/test/libyul/CompilabilityChecker.cpp @@ -39,7 +39,7 @@ string check(string const& _input) { shared_ptr ast = yul::test::parse(_input, false).first; BOOST_REQUIRE(ast); - map functions = CompilabilityChecker::run(EVMDialect::strictAssemblyForEVM(dev::test::Options::get().evmVersion()), *ast); + map functions = CompilabilityChecker::run(EVMDialect::strictAssemblyForEVM(dev::test::Options::get().evmVersion()), *ast, true); string out; for (auto const& function: functions) out += function.first.str() + ": " + to_string(function.second) + " "; diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index c710cfd4b890..4c43157331c3 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -74,7 +74,7 @@ bool ObjectCompilerTest::run(ostream& _stream, string const& _linePrefix, bool c if (m_optimize) stack.optimize(); - MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM); + MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM, m_optimize); solAssert(obj.bytecode, ""); m_obtainedResult = "Assembly:\n" + obj.assembly; diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index 9d24ecd918fc..8dc4bac369d5 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -384,6 +384,12 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case) BOOST_CHECK(successParse("{ switch 0:u256 case 42:u256 {} case 0x42:u256 {} }")); } +BOOST_AUTO_TEST_CASE(switch_duplicate_case_different_literal) +{ + CHECK_ERROR("{ switch 0:u256 case 0:u256 {} case \"\":u256 {} }", DeclarationError, "Duplicate case defined."); + BOOST_CHECK(successParse("{ switch 1:u256 case \"1\":u256 {} case \"2\":u256 {} }")); +} + BOOST_AUTO_TEST_CASE(builtins_parser) { struct SimpleDialect: public Dialect diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index eb67eacb9cb8..7d05ea9f5ad4 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -247,11 +247,12 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con { disambiguate(); (FunctionGrouper{})(*m_ast); - StackCompressor::run(m_dialect, *m_ast); + size_t maxIterations = 16; + StackCompressor::run(m_dialect, *m_ast, true, maxIterations); (BlockFlattener{})(*m_ast); } else if (m_optimizerStep == "fullSuite") - OptimiserSuite::run(m_dialect, *m_ast, *m_analysisInfo); + OptimiserSuite::run(m_dialect, *m_ast, *m_analysisInfo, true); else { AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Invalid optimizer step: " << m_optimizerStep << endl; diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index 71676bcb7eed..d1690b046216 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -231,11 +231,11 @@ // ---- // fullSuite // { -// let _1 := 0x80 -// mstore(_1, 7673901602397024137095011250362199966051872585513276903826533215767972925880) +// mstore(0x80, 7673901602397024137095011250362199966051872585513276903826533215767972925880) // mstore(0xa0, 8489654445897228341090914135473290831551238522473825886865492707826370766375) +// let notes := add(0x04, calldataload(0x04)) // let m := calldataload(0x24) -// let n := calldataload(add(0x04, calldataload(0x04))) +// let n := calldataload(notes) // let gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 // let challenge := mod(calldataload(0x44), gen_order) // if gt(m, n) @@ -248,8 +248,8 @@ // mstore(0x2c0, kn) // mstore(0x2e0, m) // kn := mulmod(sub(gen_order, kn), challenge, gen_order) -// hashCommitments(add(0x04, calldataload(0x04)), n) -// let b := add(0x300, mul(n, _1)) +// hashCommitments(notes, n) +// let b := add(0x300, mul(n, 0x80)) // let i := 0 // let i_1 := i // for { @@ -259,11 +259,13 @@ // i := add(i, 0x01) // } // { -// let _2 := add(calldataload(0x04), mul(i, 0xc0)) +// let _1 := add(calldataload(0x04), mul(i, 0xc0)) +// let noteIndex := add(_1, 0x24) // let k := i_1 -// let a := calldataload(add(_2, 0x44)) +// let a := calldataload(add(_1, 0x44)) // let c := challenge -// switch eq(add(i, 0x01), n) +// let _2 := add(i, 0x01) +// switch eq(_2, n) // case 1 { // k := kn // if eq(m, n) @@ -272,10 +274,10 @@ // } // } // case 0 { -// k := calldataload(add(_2, 0x24)) +// k := calldataload(noteIndex) // } -// validateCommitment(add(_2, 0x24), k, a) -// switch gt(add(i, 0x01), m) +// validateCommitment(noteIndex, k, a) +// switch gt(_2, m) // case 1 { // kn := addmod(kn, sub(gen_order, k), gen_order) // let x := mod(mload(i_1), gen_order) @@ -288,17 +290,16 @@ // kn := addmod(kn, k, gen_order) // } // let _3 := 0x40 -// calldatacopy(0xe0, add(_2, 164), _3) -// calldatacopy(0x20, add(_2, 100), _3) +// calldatacopy(0xe0, add(_1, 164), _3) +// calldatacopy(0x20, add(_1, 100), _3) // mstore(0x120, sub(gen_order, c)) -// let _4 := 0x60 -// mstore(_4, k) +// mstore(0x60, k) // mstore(0xc0, a) -// let result := call(gas(), 7, i_1, 0xe0, _4, 0x1a0, _3) -// let result_1 := and(result, call(gas(), 7, i_1, 0x20, _4, 0x120, _3)) -// let result_2 := and(result_1, call(gas(), 7, i_1, _1, _4, 0x160, _3)) -// let result_3 := and(result_2, call(gas(), 6, i_1, 0x120, _1, 0x160, _3)) -// result := and(result_3, call(gas(), 6, i_1, 0x160, _1, b, _3)) +// let result := call(gas(), 7, i_1, 0xe0, 0x60, 0x1a0, _3) +// let result_1 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x120, _3)) +// let result_2 := and(result_1, call(gas(), 7, i_1, 0x80, 0x60, 0x160, _3)) +// let result_3 := and(result_2, call(gas(), 6, i_1, 0x120, 0x80, 0x160, _3)) +// result := and(result_3, call(gas(), 6, i_1, 0x160, 0x80, b, _3)) // if eq(i, m) // { // mstore(0x260, mload(0x20)) @@ -308,10 +309,10 @@ // } // if gt(i, m) // { -// mstore(_4, c) -// let result_4 := and(result, call(gas(), 7, i_1, 0x20, _4, 0x220, _3)) -// let result_5 := and(result_4, call(gas(), 6, i_1, 0x220, _1, 0x260, _3)) -// result := and(result_5, call(gas(), 6, i_1, 0x1a0, _1, 0x1e0, _3)) +// mstore(0x60, c) +// let result_4 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x220, _3)) +// let result_5 := and(result_4, call(gas(), 6, i_1, 0x220, 0x80, 0x260, _3)) +// result := and(result_5, call(gas(), 6, i_1, 0x1a0, 0x80, 0x1e0, _3)) // } // if iszero(result) // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_break.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_break.yul new file mode 100644 index 000000000000..f835e84ca1eb --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_break.yul @@ -0,0 +1,38 @@ +{ + let a + let b + for {let i := 0} lt(i, 10) {i := add(a, b)} { + a := origin() + b := origin() + b := caller() + // a=origin, b=caller + if callvalue() { break } + // a=origin, b=caller + a := caller() + } + mstore(a, b) +} +// ---- +// rematerialiser +// { +// let a +// let b +// for { +// let i := 0 +// } +// lt(i, 10) +// { +// i := add(caller(), caller()) +// } +// { +// a := origin() +// b := origin() +// b := caller() +// if callvalue() +// { +// break +// } +// a := caller() +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul new file mode 100644 index 000000000000..96f65ddd5538 --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul @@ -0,0 +1,40 @@ +{ + let a + let b + for { let i := 0 } + lt(i, 10) + { i := add(a, b) } // `b` is always known to be caller() but `a` may be origin() or caller(). + { + a := origin() + b := origin() + + b := caller() + if callvalue() { continue } + a := caller() + } + mstore(a, b) +} +// ---- +// rematerialiser +// { +// let a +// let b +// for { +// let i := 0 +// } +// lt(i, 10) +// { +// i := add(a, caller()) +// } +// { +// a := origin() +// b := origin() +// b := caller() +// if callvalue() +// { +// continue +// } +// a := caller() +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul new file mode 100644 index 000000000000..a95d24add489 --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul @@ -0,0 +1,52 @@ +{ + let a + let b + let c + for { let i := 0 } + lt(i, 10) + { i := add(add(a, b), c) } // `b` is always known to be caller() but `a` and `c` may be origin() or caller(). + { + a := origin() + b := origin() + c := origin() + + b := caller() + if callvalue() { continue } + a := caller() + + if callvalue() { continue } + c := caller() + } + mstore(a, b) +} +// ---- +// rematerialiser +// { +// let a +// let b +// let c +// for { +// let i := 0 +// } +// lt(i, 10) +// { +// i := add(add(a, caller()), c) +// } +// { +// a := origin() +// b := origin() +// c := origin() +// b := caller() +// if callvalue() +// { +// continue +// } +// a := caller() +// if callvalue() +// { +// continue +// } +// c := caller() +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul new file mode 100644 index 000000000000..7620791c6c64 --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul @@ -0,0 +1,54 @@ +{ + let a + let b + let c + for { + let i := 0 + b := origin() + c := origin() + } + lt(i, 10) + { + i := add(a, b) + b := callvalue() + c := caller() + } + { + a := origin() + + b := caller() + if callvalue() { continue } + a := caller() + } + let x := b // does not rematerialize as b may be either origin() or callvalue() (btw: not caller()) + let y := c // does not rematerialize as c may be either origin() or caller() +} +// ---- +// rematerialiser +// { +// let a +// let b +// let c +// for { +// let i := 0 +// b := origin() +// c := origin() +// } +// lt(i, 10) +// { +// i := add(a, caller()) +// b := callvalue() +// c := caller() +// } +// { +// a := origin() +// b := caller() +// if callvalue() +// { +// continue +// } +// a := caller() +// } +// let x := b +// let y := c +// } diff --git a/test/tools/IsolTestOptions.h b/test/tools/IsolTestOptions.h index a13ff2a2590c..95bb5cc92f2d 100644 --- a/test/tools/IsolTestOptions.h +++ b/test/tools/IsolTestOptions.h @@ -19,6 +19,8 @@ #pragma once +#include + #include namespace dev diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 9c212346c946..1269f5e80938 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -47,13 +47,15 @@ namespace fs = boost::filesystem; struct TestStats { - int successCount; - int testCount; - operator bool() const { return successCount == testCount; } + int successCount = 0; + int testCount = 0; + int skippedCount = 0; + operator bool() const noexcept { return successCount + skippedCount == testCount; } TestStats& operator+=(TestStats const& _other) noexcept { successCount += _other.successCount; testCount += _other.testCount; + skippedCount += _other.skippedCount; return *this; } }; @@ -66,15 +68,17 @@ class TestTool string const& _name, fs::path const& _path, string const& _ipcPath, - bool _formatted - ): m_testCaseCreator(_testCaseCreator), m_name(_name), m_path(_path), m_ipcPath(_ipcPath), m_formatted(_formatted) + bool _formatted, + langutil::EVMVersion _evmVersion + ): m_testCaseCreator(_testCaseCreator), m_name(_name), m_path(_path), m_ipcPath(_ipcPath), m_formatted(_formatted), m_evmVersion(_evmVersion) {} enum class Result { Success, Failure, - Exception + Exception, + Skipped }; Result process(); @@ -84,7 +88,8 @@ class TestTool fs::path const& _basepath, fs::path const& _path, string const& _ipcPath, - bool const _formatted + bool _formatted, + langutil::EVMVersion _evmVersion ); static string editor; @@ -96,13 +101,14 @@ class TestTool Quit }; - Request handleResponse(bool const _exception); + Request handleResponse(bool _exception); TestCase::TestCaseCreator m_testCaseCreator; string const m_name; fs::path const m_path; string m_ipcPath; bool const m_formatted = false; + langutil::EVMVersion const m_evmVersion; unique_ptr m_test; static bool m_exitRequested; }; @@ -119,8 +125,14 @@ TestTool::Result TestTool::process() try { - m_test = m_testCaseCreator(TestCase::Config{m_path.string(), m_ipcPath}); - success = m_test->run(outputMessages, " ", m_formatted); + m_test = m_testCaseCreator(TestCase::Config{m_path.string(), m_ipcPath, m_evmVersion}); + if (m_test->supportedForEVMVersion(m_evmVersion)) + success = m_test->run(outputMessages, " ", m_formatted); + else + { + AnsiColorized(cout, m_formatted, {BOLD, YELLOW}) << "NOT RUN" << endl; + return Result::Skipped; + } } catch(boost::exception const& _e) { @@ -158,7 +170,7 @@ TestTool::Result TestTool::process() } } -TestTool::Request TestTool::handleResponse(bool const _exception) +TestTool::Request TestTool::handleResponse(bool _exception) { if (_exception) cout << "(e)dit/(s)kip/(q)uit? "; @@ -204,13 +216,15 @@ TestStats TestTool::processPath( fs::path const& _basepath, fs::path const& _path, string const& _ipcPath, - bool const _formatted + bool _formatted, + langutil::EVMVersion _evmVersion ) { std::queue paths; paths.push(_path); int successCount = 0; int testCount = 0; + int skippedCount = 0; while (!paths.empty()) { @@ -235,7 +249,7 @@ TestStats TestTool::processPath( else { ++testCount; - TestTool testTool(_testCaseCreator, currentPath.string(), fullpath, _ipcPath, _formatted); + TestTool testTool(_testCaseCreator, currentPath.string(), fullpath, _ipcPath, _formatted, _evmVersion); auto result = testTool.process(); switch(result) @@ -254,6 +268,7 @@ TestStats TestTool::processPath( break; case Request::Skip: paths.pop(); + ++skippedCount; break; } break; @@ -261,11 +276,15 @@ TestStats TestTool::processPath( paths.pop(); ++successCount; break; + case Result::Skipped: + paths.pop(); + ++skippedCount; + break; } } } - return { successCount, testCount }; + return { successCount, testCount, skippedCount }; } @@ -298,7 +317,8 @@ boost::optional runTestSuite( fs::path const& _subdirectory, string const& _ipcPath, TestCase::TestCaseCreator _testCaseCreator, - bool _formatted + bool _formatted, + langutil::EVMVersion _evmVersion ) { fs::path testPath = _basePath / _subdirectory; @@ -309,14 +329,21 @@ boost::optional runTestSuite( return {}; } - TestStats stats = TestTool::processPath(_testCaseCreator, _basePath, _subdirectory, _ipcPath, _formatted); + TestStats stats = TestTool::processPath(_testCaseCreator, _basePath, _subdirectory, _ipcPath, _formatted, _evmVersion); cout << endl << _name << " Test Summary: "; AnsiColorized(cout, _formatted, {BOLD, stats ? GREEN : RED}) << stats.successCount << "/" << stats.testCount; - cout << " tests successful." << endl << endl; + cout << " tests successful"; + if (stats.skippedCount > 0) + { + cout << " ("; + AnsiColorized(cout, _formatted, {BOLD, YELLOW}) << stats.skippedCount; + cout<< " tests skipped)"; + } + cout << "." << endl << endl; return stats; } @@ -354,7 +381,7 @@ int main(int argc, char const *argv[]) if (ts.smt && options.disableSMT) continue; - if (auto stats = runTestSuite(ts.title, options.testPath / ts.path, ts.subpath, options.ipcPath.string(), ts.testCaseCreator, !options.noColor)) + if (auto stats = runTestSuite(ts.title, options.testPath / ts.path, ts.subpath, options.ipcPath.string(), ts.testCaseCreator, !options.noColor, options.evmVersion())) global_stats += *stats; else return 1; @@ -363,7 +390,14 @@ int main(int argc, char const *argv[]) cout << endl << "Summary: "; AnsiColorized(cout, !options.noColor, {BOLD, global_stats ? GREEN : RED}) << global_stats.successCount << "/" << global_stats.testCount; - cout << " tests successful." << endl; + cout << " tests successful"; + if (global_stats.skippedCount > 0) + { + cout << " ("; + AnsiColorized(cout, !options.noColor, {BOLD, YELLOW}) << global_stats.skippedCount; + cout << " tests skipped)"; + } + cout << "." << endl; return global_stats ? 0 : 1; } diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 399eada46e84..74787f5fd25b 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -5,6 +5,8 @@ add_dependencies(ossfuzz solc_noopt_ossfuzz const_opt_ossfuzz strictasm_diff_ossfuzz + strictasm_opt_ossfuzz + strictasm_assembly_ossfuzz ) add_custom_target(ossfuzz_proto) @@ -23,6 +25,12 @@ target_link_libraries(const_opt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) add_executable(strictasm_diff_ossfuzz strictasm_diff_ossfuzz.cpp yulFuzzerCommon.cpp) target_link_libraries(strictasm_diff_ossfuzz PRIVATE libsolc evmasm yulInterpreter FuzzingEngine.a) +add_executable(strictasm_opt_ossfuzz strictasm_opt_ossfuzz.cpp) +target_link_libraries(strictasm_opt_ossfuzz PRIVATE yul FuzzingEngine.a) + +add_executable(strictasm_assembly_ossfuzz strictasm_assembly_ossfuzz.cpp) +target_link_libraries(strictasm_assembly_ossfuzz PRIVATE yul FuzzingEngine.a) + add_executable(yul_proto_ossfuzz yulProtoFuzzer.cpp protoToYul.cpp yulProto.pb.cc) target_include_directories(yul_proto_ossfuzz PRIVATE /src/libprotobuf-mutator /src/LPM/external.protobuf/include) target_link_libraries(yul_proto_ossfuzz PRIVATE yul evmasm diff --git a/test/tools/ossfuzz/config/strict_assembly.dict b/test/tools/ossfuzz/config/strict_assembly.dict new file mode 100644 index 000000000000..4415c87f4203 --- /dev/null +++ b/test/tools/ossfuzz/config/strict_assembly.dict @@ -0,0 +1,91 @@ +" -> " +" := " +" address() " +" calldatasize() " +" caller() " +" callvalue() " +" codesize() " +" coinbase() " +" difficulty() " +" gas() " +" gaslimit() " +" gasprice() " +" invalid() " +" number() " +" origin() " +" pc() " +" returndatasize() " +" stop() " +" timestamp() " +"(" +")" +", " +"0x42" +"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +":" +"add(" +"addmod(" +"and(" +"balance(" +"blockhash(" +"byte(" +"call(" +"callcode(" +"calldatacopy(" +"calldataload(" +"case " +"codecopy(" +"create(" +"create2(" +"default " +"delegatecall(" +"div(" +"eq(" +"exp(" +"extcodecopy(" +"extcodehash(" +"extcodesize(" +"for " +"function " +"gt(" +"hello" +"if " +"iszero(" +"keccak256(" +"let " +"log0(" +"log1(" +"log2(" +"log3(" +"log4(" +"lt(" +"mload(" +"mod(" +"msize" +"mstore(" +"mstore8(" +"mul(" +"mulmod(" +"not(" +"or(" +"pop(" +"return(" +"returndatacopy(" +"revert(" +"sar(" +"sdiv(" +"selfdestruct(" +"sgt(" +"shl(" +"shr(" +"signextend(" +"sload(" +"slt(" +"smod(" +"sstore(" +"staticcall(" +"sub(" +"switch " +"xor(" +"{" +"}" diff --git a/test/tools/ossfuzz/config/strictasm_assembly_ossfuzz.options b/test/tools/ossfuzz/config/strictasm_assembly_ossfuzz.options new file mode 100644 index 000000000000..c6170959fecb --- /dev/null +++ b/test/tools/ossfuzz/config/strictasm_assembly_ossfuzz.options @@ -0,0 +1,2 @@ +[libfuzzer] +dict = strict_assembly.dict diff --git a/test/tools/ossfuzz/config/strictasm_opt_ossfuzz.options b/test/tools/ossfuzz/config/strictasm_opt_ossfuzz.options new file mode 100644 index 000000000000..c6170959fecb --- /dev/null +++ b/test/tools/ossfuzz/config/strictasm_opt_ossfuzz.options @@ -0,0 +1,2 @@ +[libfuzzer] +dict = strict_assembly.dict diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index c7df387e2012..03d3facd9266 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -19,13 +19,58 @@ #include #include +#include using namespace std; using namespace yul::test::yul_fuzzer; +std::string yul::test::yul_fuzzer::createHex(std::string const& _hexBytes) +{ + std::string tmp{_hexBytes}; + if (!tmp.empty()) + { + boost::range::remove_erase_if(tmp, [=](char c) -> bool { + return !std::isxdigit(c); + }); + tmp = tmp.substr(0, 64); + } + // We need this awkward if case hex literals cannot be empty. + if (tmp.empty()) + tmp = "1"; + return tmp; +} + +std::string yul::test::yul_fuzzer::createAlphaNum(std::string const& _strBytes) +{ + std::string tmp{_strBytes}; + if (!tmp.empty()) + { + boost::range::remove_erase_if(tmp, [=](char c) -> bool { + return !(std::isalpha(c) || std::isdigit(c)); + }); + tmp = tmp.substr(0, 32); + } + return tmp; +} + ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Literal const& _x) { - return _os << "(" << _x.val() << ")"; + switch (_x.literal_oneof_case()) + { + case Literal::kIntval: + _os << _x.intval(); + break; + case Literal::kHexval: + _os << "0x" << createHex(_x.hexval()); + break; + case Literal::kStrval: + _os << "\"" << createAlphaNum(_x.strval()) << "\""; + break; + case Literal::LITERAL_ONEOF_NOT_SET: + _os << "1"; + break; + } + return _os; } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarRef const& _x) @@ -35,15 +80,25 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarRef const& _x) ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Expression const& _x) { - if (_x.has_varref()) - return _os << _x.varref(); - else if (_x.has_cons()) - return _os << _x.cons(); - else if (_x.has_binop()) - return _os << _x.binop(); - else if (_x.has_unop()) - return _os << _x.unop(); - return _os << ""; + switch (_x.expr_oneof_case()) + { + case Expression::kVarref: + _os << _x.varref(); + break; + case Expression::kCons: + _os << _x.cons(); + break; + case Expression::kBinop: + _os << _x.binop(); + break; + case Expression::kUnop: + _os << _x.unop(); + break; + case Expression::EXPR_ONEOF_NOT_SET: + _os << "1"; + break; + } + return _os; } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, BinaryOp const& _x) @@ -51,47 +106,79 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, BinaryOp const& _x) switch (_x.op()) { case BinaryOp::ADD: - _os << "add("; + _os << "add"; break; case BinaryOp::SUB: - _os << "sub("; + _os << "sub"; break; case BinaryOp::MUL: - _os << "mul("; + _os << "mul"; break; case BinaryOp::DIV: - _os << "div("; + _os << "div"; break; case BinaryOp::MOD: - _os << "mod("; + _os << "mod"; break; case BinaryOp::XOR: - _os << "xor("; + _os << "xor"; break; case BinaryOp::AND: - _os << "and("; + _os << "and"; break; case BinaryOp::OR: - _os << "or("; + _os << "or"; break; case BinaryOp::EQ: - _os << "eq("; + _os << "eq"; break; case BinaryOp::LT: - _os << "lt("; + _os << "lt"; break; case BinaryOp::GT: - _os << "gt("; + _os << "gt"; + break; + case BinaryOp::SHR: + _os << "shr"; + break; + case BinaryOp::SHL: + _os << "shl"; + break; + case BinaryOp::SAR: + _os << "sar"; + break; + case BinaryOp::SDIV: + _os << "sdiv"; + break; + case BinaryOp::SMOD: + _os << "smod"; + break; + case BinaryOp::EXP: + _os << "exp"; + break; + case BinaryOp::SLT: + _os << "slt"; + break; + case BinaryOp::SGT: + _os << "sgt"; + break; + case BinaryOp::BYTE: + _os << "byte"; + break; + case BinaryOp::SI: + _os << "signextend"; + break; + case BinaryOp::KECCAK: + _os << "keccak256"; break; } - return _os << _x.left() << "," << _x.right() << ")"; + return _os << "(" << _x.left() << "," << _x.right() << ")"; } // New var numbering starts from x_10 until x_16 ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarDecl const& _x) { - _os << "let x_" << ((_x.id() % 7) + 10) << " := " << _x.expr() << "\n"; - return _os; + return _os << "let x_" << ((_x.id() % 7) + 10) << " := " << _x.expr() << "\n"; } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, TypedVarDecl const& _x) @@ -141,20 +228,19 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, UnaryOp const& _x) switch (_x.op()) { case UnaryOp::NOT: - _os << "not("; + _os << "not"; break; case UnaryOp::MLOAD: - _os << "mload("; + _os << "mload"; break; case UnaryOp::SLOAD: - _os << "sload("; + _os << "sload"; break; case UnaryOp::ISZERO: - _os << "iszero("; + _os << "iszero"; break; } - _os << _x.operand() << ")"; - return _os; + return _os << "(" << _x.operand() << ")"; } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, AssignmentStatement const& _x) @@ -165,10 +251,10 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, AssignmentStatement con ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, IfStmt const& _x) { return _os << - "if " << - _x.cond() << - " " << - _x.if_body(); + "if " << + _x.cond() << + " " << + _x.if_body(); } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, StoreFunc const& _x) @@ -185,19 +271,60 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, StoreFunc const& _x) return _os; } +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, ForStmt const& _x) +{ + _os << "for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } "; + return _os << _x.for_body(); +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, CaseStmt const& _x) +{ + _os << "case " << _x.case_lit() << " "; + return _os << _x.case_block(); +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, SwitchStmt const& _x) +{ + if (_x.case_stmt_size() > 0 || _x.has_default_block()) + { + _os << "switch " << _x.switch_expr() << "\n"; + for (auto const& caseStmt: _x.case_stmt()) + _os << caseStmt; + if (_x.has_default_block()) + _os << "default " << _x.default_block(); + } + return _os; +} + ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Statement const& _x) { - if (_x.has_decl()) - return _os << _x.decl(); - else if (_x.has_assignment()) - return _os << _x.assignment(); - else if (_x.has_ifstmt()) - return _os << _x.ifstmt(); - else if (_x.has_storage_func()) - return _os << _x.storage_func(); - else if (_x.has_blockstmt()) - return _os << _x.blockstmt(); - return _os << ""; + switch (_x.stmt_oneof_case()) + { + case Statement::kDecl: + _os << _x.decl(); + break; + case Statement::kAssignment: + _os << _x.assignment(); + break; + case Statement::kIfstmt: + _os << _x.ifstmt(); + break; + case Statement::kStorageFunc: + _os << _x.storage_func(); + break; + case Statement::kBlockstmt: + _os << _x.blockstmt(); + break; + case Statement::kForstmt: + _os << _x.forstmt(); + break; + case Statement::kSwitchstmt: + _os << _x.switchstmt(); + break; + case Statement::STMT_ONEOF_NOT_SET: + break; + } + return _os; } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Block const& _x) @@ -208,8 +335,9 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Block const& _x) for (auto const& st: _x.statements()) _os << st; _os << "}\n"; - } + else + _os << "{}\n"; return _os; } diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 14389c547a10..1c06370ccaf5 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -31,6 +31,8 @@ class Function; std::string functionToString(Function const& input); std::string protoToYul(uint8_t const* data, size_t size); +std::string createHex(std::string const& _hexBytes); +std::string createAlphaNum(std::string const& _strBytes); std::ostream& operator<<(std::ostream& _os, BinaryOp const& _x); std::ostream& operator<<(std::ostream& _os, Block const& _x); std::ostream& operator<<(std::ostream& _os, Literal const& _x); @@ -46,6 +48,9 @@ std::ostream& operator<<(std::ostream& _os, StoreFunc const& _x); std::ostream& operator<<(std::ostream& _os, Statement const& _x); std::ostream& operator<<(std::ostream& _os, Block const& _x); std::ostream& operator<<(std::ostream& _os, Function const& _x); +std::ostream& operator<<(std::ostream& _os, ForStmt const& _x); +std::ostream& operator<<(std::ostream& _os, CaseStmt const& _x); +std::ostream& operator<<(std::ostream& _os, SwitchStmt const& _x); +} } } -} \ No newline at end of file diff --git a/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp new file mode 100644 index 000000000000..b3b11426a1f4 --- /dev/null +++ b/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp @@ -0,0 +1,47 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include +#include +#include + +using namespace yul; +using namespace std; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) +{ + if (_size > 600) + return 0; + + string input(reinterpret_cast(_data), _size); + AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + + if (!stack.parseAndAnalyze("source", input)) + return 0; + + try + { + MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM); + solAssert(obj.bytecode, ""); + } + catch (StackTooDeepError const&) + { + + } + + return 0; +} diff --git a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp index 882f35462088..6aadbfa4fbbb 100644 --- a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp @@ -50,6 +50,11 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) string input(reinterpret_cast(_data), _size); + if (std::any_of(input.begin(), input.end(), [](char c) { + return ((static_cast(c) > 127) || !(std::isprint(c) || (c == '\n') || (c == '\t'))); + })) + return 0; + AssemblyStack stack(EVMVersion::petersburg(), AssemblyStack::Language::StrictAssembly); try { diff --git a/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp new file mode 100644 index 000000000000..05e70e8ab5f1 --- /dev/null +++ b/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp @@ -0,0 +1,37 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include +#include + +using namespace yul; +using namespace std; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) +{ + if (_size > 600) + return 0; + + string input(reinterpret_cast(_data), _size); + AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + + if (!stack.parseAndAnalyze("source", input)) + return 0; + + stack.optimize(); + return 0; +} diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 470e2a36fa9b..bd8468d9be7a 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -17,16 +17,9 @@ syntax = "proto2"; -// VariableDeclaration = -// 'let' TypedIdentifierList ( ':=' Expression )? -// TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )* -// Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* -// IdentifierList = Identifier ( ',' Identifier)* -// TypeName = Identifier | BuiltinTypeName -// BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) message VarDecl { required int32 id = 1; - required Expression expr = 3; + required Expression expr = 2; } message TypedVarDecl { @@ -53,7 +46,11 @@ message VarRef { } message Literal { - required int32 val = 1; + oneof literal_oneof { + uint64 intval = 1; + string hexval = 2; + string strval = 3; + } } message TypedLiteral { @@ -87,6 +84,17 @@ message BinaryOp { EQ = 8; LT = 9; GT = 10; + SHR = 11; + SHL = 12; + SAR = 13; + SDIV = 14; + SMOD = 15; + EXP = 16; + SLT = 17; + SGT = 18; + BYTE = 19; + SI = 20; + KECCAK = 21; }; required BOp op = 1; required Expression left = 2; @@ -133,8 +141,21 @@ message IfStmt { required Block if_body = 2; } -// add for loop -// TODO: add block and scope for if +message ForStmt { + required Block for_body = 2; +} + +message CaseStmt { + required Literal case_lit = 1; + required Block case_block = 2; +} + +message SwitchStmt { + required Expression switch_expr = 1; + repeated CaseStmt case_stmt = 2; + optional Block default_block = 3; +} + message Statement { oneof stmt_oneof { VarDecl decl = 1; @@ -142,6 +163,8 @@ message Statement { IfStmt ifstmt = 3; StoreFunc storage_func = 4; Block blockstmt = 5; + ForStmt forstmt = 6; + SwitchStmt switchstmt = 7; } } diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 3469a5427537..05bc3684b6ab 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -82,7 +82,7 @@ void copyZeroExtended( size_t _targetOffset, size_t _sourceOffset, size_t _size ) { - yulAssert(_targetOffset + _size < _target.size(), ""); + yulAssert(_targetOffset + _size <= _target.size(), ""); for (size_t i = 0; i < _size; ++i) _target[_targetOffset + i] = _sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0; } diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index c7429baab6c9..14ac0e950a31 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -198,7 +198,7 @@ class YulOpti SSAReverser::run(*m_ast); break; case 'p': - StackCompressor::run(m_dialect, *m_ast); + StackCompressor::run(m_dialect, *m_ast, true, 16); break; default: cout << "Unknown option." << endl;