From c3543b470f3e0a0718daab3cfe1e3f99dacb4302 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Wed, 13 Mar 2019 14:34:05 +0100 Subject: [PATCH 01/88] Change hyphen to non-breaking hyphen --- docs/yul.rst | 114 +++++++++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/docs/yul.rst b/docs/yul.rst index 627e6e7cd00c..13a1e9b62d9c 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -334,83 +334,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 +428,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 +460,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 +508,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 | From e76bcf25ea6530dddbba438d2efdd8482cda259d Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Wed, 13 Mar 2019 16:29:14 +0100 Subject: [PATCH 02/88] Change conditional operator precedence --- docs/miscellaneous.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 2a536911f29c98319c0b0005200b3d3ea3ea78c9 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 13 Mar 2019 14:55:53 +0100 Subject: [PATCH 03/88] Prints returned / expected byte ranges if conversion failed. --- test/libsolidity/util/TestFunctionCall.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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(); } From c6a5352231a55df02a72bdbbe197a90457cf06fc Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 13 Mar 2019 18:29:22 +0100 Subject: [PATCH 04/88] Adjust order of release page creation. --- ReleaseChecklist.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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. From 94aa971bd538bc57a2594ac5f50788e520ac67f0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Mar 2019 18:38:30 +0100 Subject: [PATCH 05/88] Set version to 0.5.7. --- CMakeLists.txt | 2 +- Changelog.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) 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..8cf703976e44 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,17 @@ +### 0.5.7 (unreleased) + +Language Features: + + +Compiler Features: + + +Bugfixes: + + +Build System: + + ### 0.5.6 (2019-03-13) Important Bugfixes: From 2e7794d8a60dd8d9f12232e356c93f0a95c68a85 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Mar 2019 14:27:25 +0100 Subject: [PATCH 06/88] Defensively pad memory for ``type(C).name`` to multiples of 32. --- Changelog.md | 1 + libsolidity/codegen/ExpressionCompiler.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 8cf703976e44..98f136c0f796 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ Compiler Features: Bugfixes: + * Code Generator: Defensively pad memory for ``type(Contract).name`` to multiples of 32. Build System: 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 From d05cb3662c090211b1dfc3aeacf53b4f19a0c2c6 Mon Sep 17 00:00:00 2001 From: SystemGlitch Date: Wed, 13 Mar 2019 15:08:17 +0100 Subject: [PATCH 07/88] Add Solidity IDE to resources page --- docs/resources.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/resources.rst b/docs/resources.rst index 4dd617fa3227..41c126ad76bf 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). From 470c161e5079b369476d97cfe2c089f593886abd Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 13 Mar 2019 17:14:58 +0100 Subject: [PATCH 08/88] Fixes boost repository URL in CentOS install script. --- Changelog.md | 1 + scripts/install_deps.sh | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 98f136c0f796..043a48013818 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ Bugfixes: Build System: + * Install scripts: Fix boost repository URL for CentOS 6. ### 0.5.6 (2019-03-13) 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"; From db379403fcce3745c38036ee2b94345ffc6ed62d Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Mar 2019 20:58:15 +0100 Subject: [PATCH 09/88] Fix assertion in yul interpreter. --- test/tools/yulInterpreter/EVMInstructionInterpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From ac5f860df9b219ab16fc9b38ecc4576eaa450cca Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 13 Mar 2019 18:52:44 +0100 Subject: [PATCH 10/88] Fixes hex string update via isoltest. --- Changelog.md | 3 ++- test/libsolidity/util/TestFileParser.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 043a48013818..30d1bf2e1e45 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ Bugfixes: Build System: * Install scripts: Fix boost repository URL for CentOS 6. + * Soltest: Fix hex string update in soltest. ### 0.5.6 (2019-03-13) @@ -45,6 +46,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) @@ -86,7 +88,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/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()}; } From cacd271ba6ceb96f5531748dd158fa23ad8b5b7c Mon Sep 17 00:00:00 2001 From: Taariq Levack Date: Thu, 14 Mar 2019 14:51:55 +0200 Subject: [PATCH 11/88] Update introduction-to-smart-contracts.rst Looks like this was missed in the bump --- docs/introduction-to-smart-contracts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index c221a72b21ca..bfce0d5a33b8 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -33,7 +33,7 @@ Storage The first line simply tells that the source code is written for Solidity version 0.4.0 or anything newer that does not break functionality -(up to, but not including, version 0.6.0). This is to ensure that the +(up to, but not including, version 0.7.0). This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently. :ref:`Pragmas` are common instructions for compilers about how to treat the source code (e.g. `pragma once `_). From 6ac5c52528d1fad968267dc24be7543daf66f8e0 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 27 Feb 2019 16:21:56 +0100 Subject: [PATCH 12/88] Implements merging of Result. --- libdevcore/Result.h | 15 +++++++++++++-- test/libsolidity/SolidityTypes.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/libdevcore/Result.h b/libdevcore/Result.h index 4f7a063b5cce..99a244d5a30b 100644 --- a/libdevcore/Result.h +++ b/libdevcore/Result.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace dev { @@ -39,8 +40,8 @@ template class Result { public: - Result(ResultType _value): Result(_value, std::string{}) {} - Result(std::string _message): Result(ResultType{}, std::move(_message)) {} + Result(ResultType _value): Result(_value, std::string{}) { } + Result(std::string _message): Result(ResultType{}, std::move(_message)) { } /// @{ /// @name Wrapper functions @@ -53,6 +54,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/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 050fdaf2e25e..39b0f4b81cc7 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -243,6 +243,34 @@ 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{string{"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 implicitely 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_SUITE_END() } From b9a7a88346d2f7a1409659ce16f31671aae92178 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 27 Feb 2019 18:21:53 +0100 Subject: [PATCH 13/88] Improves Result in order to prevent defects. --- libdevcore/Result.h | 16 ++++++++++++---- libsolidity/ast/Types.cpp | 16 ++++++++-------- test/libsolidity/SolidityTypes.cpp | 20 ++++++++++++++++++-- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/libdevcore/Result.h b/libdevcore/Result.h index 99a244d5a30b..ebc0db0e1301 100644 --- a/libdevcore/Result.h +++ b/libdevcore/Result.h @@ -17,7 +17,6 @@ #pragma once #include -#include namespace dev { @@ -31,7 +30,7 @@ namespace dev /// Result check() /// { /// if (false) -/// return Result("Error message.") +/// return Result::err("Error message.") /// return true; /// } /// @@ -40,8 +39,17 @@ template class Result { public: - Result(ResultType _value): Result(_value, std::string{}) { } - Result(std::string _message): Result(ResultType{}, std::move(_message)) { } + /// 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{}) {} + + /// 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 diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index bcf9b53ff635..c803b3b4b13b 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)); } diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 39b0f4b81cc7..3f60c3548bba 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -246,7 +246,7 @@ BOOST_AUTO_TEST_CASE(encoded_sizes) BOOST_AUTO_TEST_CASE(helper_bool_result) { BoolResult r1{true}; - BoolResult r2{string{"Failure."}}; + BoolResult r2 = BoolResult::err("Failure."); r1.merge(r2, logical_and()); BOOST_REQUIRE_EQUAL(r1.get(), false); BOOST_REQUIRE_EQUAL(r1.message(), "Failure."); @@ -264,13 +264,29 @@ BOOST_AUTO_TEST_CASE(helper_bool_result) BOOST_REQUIRE_EQUAL(r5.message(), ""); BoolResult r7{true}; - // Attention: this will implicitely convert to bool. + // 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() } From 6d1ed9324723da8b68d8b8ae739923339a896439 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 26 Feb 2019 19:55:13 +0100 Subject: [PATCH 14/88] Use stack optimizations. --- Changelog.md | 1 + docs/using-the-compiler.rst | 8 ++++++-- libsolidity/codegen/CompilerContext.cpp | 14 +++++++++++--- libsolidity/codegen/CompilerContext.h | 2 +- libsolidity/codegen/ContractCompiler.cpp | 6 ++++-- libsolidity/interface/CompilerStack.cpp | 6 +++++- libsolidity/interface/OptimiserSettings.h | 9 +++++++-- libsolidity/interface/StandardCompiler.cpp | 13 +++++++++---- libyul/backends/evm/AsmCodeGen.cpp | 4 ++-- libyul/backends/evm/AsmCodeGen.h | 2 +- solc/CommandLineInterface.cpp | 1 + .../input.json | 2 +- .../output.json | 2 +- .../input.json | 16 ++++++++++++++++ .../output.json | 1 + test/libsolidity/StandardCompiler.cpp | 3 +++ 16 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_without_yul/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_without_yul/output.json diff --git a/Changelog.md b/Changelog.md index 30d1bf2e1e45..7e422b8e7b2d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Language Features: Compiler Features: + * Yul Optimizer: Enable stack allocation optimization by default if yul optimizer is active (disable in yulDetails). Bugfixes: diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index f4b5aceec5a1..ca773fa72fff 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -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 diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3053e3f7cd52..e106a4839c42 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,7 +422,7 @@ 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), @@ -445,7 +445,15 @@ void CompilerContext::appendInlineAssembly( 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/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/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c0dc2ffd7575..3c753c25b8bf 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -993,7 +993,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); } diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index aae5fa1ce71e..c88778949f98 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -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(); + 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..326dac60119c 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -399,12 +399,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); 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/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 2eb01c47b9b2..2b762e6462bf 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -907,6 +907,7 @@ bool CommandLineInterface::processInput() OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::enabled() : 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(); 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/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index f2c8f958260f..f7e04a6f1792 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1010,7 +1010,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); } From 49d914a3919b0e861e333c7bb577b8c016d161c6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 11 Mar 2019 17:30:46 +0100 Subject: [PATCH 15/88] Fix and test yul stack optimization flag for commandline interface. --- libyul/AssemblyStack.h | 2 +- solc/CommandLineInterface.cpp | 2 +- test/cmdlineTests.sh | 2 + test/cmdlineTests/object_compiler/output | 1 - test/cmdlineTests/yul_stack_opt/args | 1 + test/cmdlineTests/yul_stack_opt/input.sol | 24 +++ test/cmdlineTests/yul_stack_opt/output | 202 ++++++++++++++++++ test/cmdlineTests/yul_stack_opt_disabled/args | 1 + test/cmdlineTests/yul_stack_opt_disabled/err | 5 + test/cmdlineTests/yul_stack_opt_disabled/exit | 1 + .../yul_stack_opt_disabled/input.sol | 24 +++ .../yul_stack_opt_disabled/output | 31 +++ test/libsolidity/InlineAssembly.cpp | 3 +- test/libyul/ObjectCompilerTest.cpp | 2 +- 14 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 test/cmdlineTests/yul_stack_opt/args create mode 100644 test/cmdlineTests/yul_stack_opt/input.sol create mode 100644 test/cmdlineTests/yul_stack_opt/output create mode 100644 test/cmdlineTests/yul_stack_opt_disabled/args create mode 100644 test/cmdlineTests/yul_stack_opt_disabled/err create mode 100644 test/cmdlineTests/yul_stack_opt_disabled/exit create mode 100644 test/cmdlineTests/yul_stack_opt_disabled/input.sol create mode 100644 test/cmdlineTests/yul_stack_opt_disabled/output 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/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 2b762e6462bf..ac512d4e451c 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -1295,7 +1295,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/cmdlineTests.sh b/test/cmdlineTests.sh index 9a3e53111a4e..389aed5797f5 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -125,6 +125,8 @@ 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" if [[ $exitCode -ne "$exit_code_expected" ]] then 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/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/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..6959bbf418e7 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/err @@ -0,0 +1,5 @@ +Exception while assembling: +Dynamic exception type: boost::exception_detail::clone_impl +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/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/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; From 308e3ef1cdd76e59d0f549f810a5390b20e97751 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 11 Mar 2019 17:54:01 +0100 Subject: [PATCH 16/88] Add test for standard compiler. --- test/libsolidity/StandardCompiler.cpp | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index f7e04a6f1792..1a539bc8f34f 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1080,6 +1080,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() } From 5681086d2c8cf9f84cb95fb411b1eb4e568ce7a5 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Thu, 14 Mar 2019 15:40:54 +0100 Subject: [PATCH 17/88] Proto spec and translator bug fixes. --- test/tools/ossfuzz/protoToYul.cpp | 122 +++++++++++++++++++----------- test/tools/ossfuzz/protoToYul.h | 2 +- test/tools/ossfuzz/yulProto.proto | 15 +--- 3 files changed, 81 insertions(+), 58 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index c7df387e2012..f454ff304a5d 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -25,7 +25,16 @@ using namespace yul::test::yul_fuzzer; 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::LITERAL_ONEOF_NOT_SET: + _os << "1"; + break; + } + return _os; } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarRef const& _x) @@ -35,15 +44,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 +70,46 @@ 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; } - 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 +159,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 +182,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) @@ -187,17 +204,27 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, StoreFunc const& _x) 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::STMT_ONEOF_NOT_SET: + break; + } + return _os; } ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Block const& _x) @@ -208,7 +235,10 @@ 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..f586d97ca492 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -48,4 +48,4 @@ std::ostream& operator<<(std::ostream& _os, Block const& _x); std::ostream& operator<<(std::ostream& _os, Function const& _x); } } -} \ No newline at end of file +} diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 470e2a36fa9b..f6f7dcc7f110 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,9 @@ message VarRef { } message Literal { - required int32 val = 1; + oneof literal_oneof { + uint64 intval = 1; + } } message TypedLiteral { @@ -133,8 +128,6 @@ message IfStmt { required Block if_body = 2; } -// add for loop -// TODO: add block and scope for if message Statement { oneof stmt_oneof { VarDecl decl = 1; From 3b546acd57f239aec4bc10e89b730b7f7c7349e1 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 14 Mar 2019 15:26:18 +0100 Subject: [PATCH 18/88] Clarify documentation of ContractType::m_super --- libsolidity/ast/Types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index f88232c9b9de..95e515062695 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -797,6 +797,7 @@ class ContractType: public Type 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 +814,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; From 57bcb8ba836e77ce08a33b9016459def8a1d8f73 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 11 Mar 2019 16:06:43 +0100 Subject: [PATCH 19/88] [yul] Ensures DataFlowAnalyzer works fine with break/continue statements just like without. --- libyul/optimiser/DataFlowAnalyzer.cpp | 16 ++---- libyul/optimiser/DataFlowAnalyzer.h | 2 - libyul/optimiser/NameCollector.cpp | 26 +++++++++ libyul/optimiser/NameCollector.h | 25 +++++++++ .../rematerialiser/for_break.yul | 38 +++++++++++++ .../rematerialiser/for_continue.yul | 40 ++++++++++++++ .../for_continue_with_assignment_in_post.yul | 54 +++++++++++++++++++ 7 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 test/libyul/yulOptimizerTests/rematerialiser/for_break.yul create mode 100644 test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul create mode 100644 test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul 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/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_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 +// } From 4d060ef991c02184814ee2f97344faf646eca3ef Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 14 Mar 2019 17:19:59 +0100 Subject: [PATCH 20/88] Merge interfaceType() canBeUsedExternally() And cache the result for expensive calls. --- libsolidity/analysis/ReferencesResolver.cpp | 2 +- libsolidity/analysis/TypeChecker.cpp | 2 +- libsolidity/ast/Types.cpp | 92 ++++++++++++--------- libsolidity/ast/Types.h | 9 +- 4 files changed, 59 insertions(+), 46 deletions(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index cce1a3031f79..d624a0135cd9 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)) { 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..ce73164a1208 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -599,7 +599,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), "External function type uses internal types."); } bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c803b3b4b13b..9498d35ae3f5 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1871,33 +1871,36 @@ TypePointer ArrayType::decodingType() const TypePointer 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); -} + TypePointer result; -bool ArrayType::canBeUsedExternally(bool _inLibrary) const -{ - // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) if (_inLibrary && location() == DataLocation::Storage) - return true; + 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 - return true; + { + TypePointer baseExt = m_baseType->interfaceType(_inLibrary); + + if (!baseExt) + result = TypePointer(); + else if (isDynamicallySized()) + result = make_shared(DataLocation::Memory, baseExt); + else + result = make_shared(DataLocation::Memory, baseExt, m_length); + } + + if (_inLibrary) + m_interfaceType_library = result; + else + m_interfaceType = result; + + return result; } u256 ArrayType::memorySize() const @@ -2134,22 +2137,18 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const TypePointer 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; + + TypePointer result = TypePointer{}; -bool StructType::canBeUsedExternally(bool _inLibrary) const -{ if (_inLibrary && location() == DataLocation::Storage) - return true; + result = shared_from_this(); else if (recursive()) - return false; + result = TypePointer{}; else { // Check that all members have interface types. @@ -2159,17 +2158,32 @@ bool StructType::canBeUsedExternally(bool _inLibrary) const // 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. + bool allOkay = true; for (auto const& var: m_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; + { + allOkay = false; + break; + } + if (!var->annotation().type->interfaceType(false)) + { + allOkay = false; + break; + } } + if (allOkay) + result = copyForLocation(DataLocation::Memory, true); } - return true; + + if (_inLibrary) + m_interfaceType_library = result; + else + m_interfaceType = result; + + return result; } TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const @@ -2560,7 +2574,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), "Internal type used as parameter for external function." ); m_parameterTypes.push_back(t->annotation().type); @@ -2570,7 +2584,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), "Internal type used as return parameter for external function." ); m_returnParameterTypes.push_back(t->annotation().type); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index f88232c9b9de..5d6de053291d 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -305,9 +305,6 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this m_interfaceType; + mutable boost::optional m_interfaceType_library; }; /** @@ -845,7 +843,6 @@ 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; TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; @@ -875,6 +872,8 @@ class StructType: public ReferenceType StructDefinition const& m_struct; /// Cache for the recursive() function. mutable boost::optional m_recursive; + mutable boost::optional m_interfaceType; + mutable boost::optional m_interfaceType_library; }; /** From 5e8ed426df840b53b83ff1bb29e13ac02ed32d4f Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Thu, 14 Mar 2019 22:26:25 +0100 Subject: [PATCH 21/88] Add more binary operations to the yul proto spec. --- test/tools/ossfuzz/protoToYul.cpp | 33 +++++++++++++++++++++++++++++++ test/tools/ossfuzz/yulProto.proto | 11 +++++++++++ 2 files changed, 44 insertions(+) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index f454ff304a5d..ec0dfdeb19a4 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -102,6 +102,39 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, BinaryOp const& _x) case BinaryOp::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() << ")"; } diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index f6f7dcc7f110..60f9b2f1ff4e 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -82,6 +82,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; From 4a28e1eb4310cfb6dc0e2dba9d35df03318521da Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 15 Mar 2019 17:22:04 +0100 Subject: [PATCH 22/88] Support EVM Version rules for extracted tests. --- test/Common.cpp | 15 ++++ test/Common.h | 6 ++ test/ExecutionFramework.cpp | 6 +- test/ExecutionFramework.h | 2 +- test/Options.cpp | 15 ---- test/Options.h | 4 -- test/TestCase.cpp | 44 ++++++++++++ test/TestCase.h | 13 +++- test/boostTest.cpp | 8 ++- test/libsolidity/SMTCheckerJSONTest.cpp | 4 +- test/libsolidity/SMTCheckerJSONTest.h | 4 +- test/libsolidity/SemanticTest.cpp | 4 +- test/libsolidity/SemanticTest.h | 4 +- .../SolidityExecutionFramework.cpp | 4 +- test/libsolidity/SolidityExecutionFramework.h | 2 +- test/libsolidity/SyntaxTest.cpp | 4 +- test/libsolidity/SyntaxTest.h | 5 +- test/tools/IsolTestOptions.h | 2 + test/tools/isoltest.cpp | 68 ++++++++++++++----- 19 files changed, 154 insertions(+), 60 deletions(-) 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..946530e4a5eb 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -50,13 +50,13 @@ 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 const _evmVersion): m_rpc(RPCSession::instance(_ipcPath)), - m_evmVersion(dev::test::Options::get().evmVersion()), + m_evmVersion(_evmVersion), m_optimize(dev::test::Options::get().optimize), 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..9b7e3a9a9618 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -53,7 +53,7 @@ class ExecutionFramework public: ExecutionFramework(); - explicit ExecutionFramework(std::string const& _ipcPath); + explicit ExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion const _evmVersion); virtual ~ExecutionFramework() = default; virtual bytes const& compileAndRunWithoutCheck( 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/TestCase.cpp b/test/TestCase.cpp index e9e2c9f21a83..3a5ae425e584 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -35,16 +35,60 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename) !boost::starts_with(_filename.string(), "."); } +bool TestCase::supportedForEVMVersion(langutil::EVMVersion _evmVersion) const +{ + for (auto const& rule: m_evmVersionRules) + if (!rule(_evmVersion)) + return false; + return true; +} + string TestCase::parseSource(istream& _stream) { string source; string line; string const delimiter("// ----"); + 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/libsolidity/SMTCheckerJSONTest.cpp b/test/libsolidity/SMTCheckerJSONTest.cpp index 18a5bc41002e..0d6b9ba8f5e4 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 const _evmVersion) +: SyntaxTest(_filename, _evmVersion) { if (!boost::algorithm::ends_with(_filename, ".sol")) BOOST_THROW_EXCEPTION(runtime_error("Invalid test contract file name: \"" + _filename + "\".")); diff --git a/test/libsolidity/SMTCheckerJSONTest.h b/test/libsolidity/SMTCheckerJSONTest.h index 256056689009..57d79cc16224 100644 --- a/test/libsolidity/SMTCheckerJSONTest.h +++ b/test/libsolidity/SMTCheckerJSONTest.h @@ -35,9 +35,9 @@ class SMTCheckerTest: public SyntaxTest public: static std::unique_ptr create(Config const& _config) { - return std::unique_ptr(new SMTCheckerTest(_config.filename)); + return std::unique_ptr(new SMTCheckerTest(_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; diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index f8c9fa110cd3..5c1a67ae14eb 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 const _evmVersion): + SolidityExecutionFramework(_ipcPath, _evmVersion) { ifstream file(_filename); soltestAssert(file, "Cannot open test contract: \"" + _filename + "\"."); diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index ccc8812a187b..352f266ec0bc 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -44,9 +44,9 @@ 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 const _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; diff --git a/test/libsolidity/SolidityExecutionFramework.cpp b/test/libsolidity/SolidityExecutionFramework.cpp index 934c563f44ff..ccd8c5d09ca4 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 const _evmVersion): + ExecutionFramework(_ipcPath, _evmVersion) { } diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 37adbdf4e457..996ab19a55da 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 const _evmVersion); virtual bytes const& compileAndRunWithoutCheck( std::string const& _sourceCode, diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index f13b2e79cde2..a91fae93ab0d 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 const _evmVersion): m_evmVersion(_evmVersion) { ifstream file(_filename); if (!file) @@ -68,7 +68,7 @@ bool SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool const _fo 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(); diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index c331636aba11..a1eed3209499 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -54,8 +54,8 @@ 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::unique_ptr(new SyntaxTest(_config.filename, _config.evmVersion)); } + SyntaxTest(std::string const& _filename, langutil::EVMVersion const _evmVersion); bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; @@ -82,6 +82,7 @@ class SyntaxTest: AnalysisFramework, public TestCase std::string m_source; std::vector m_expectations; std::vector m_errorList; + langutil::EVMVersion const m_evmVersion; }; } 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..c6c9bb8b54d4 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 { 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 const _formatted, + langutil::EVMVersion const _evmVersion ); static string editor; @@ -103,6 +108,7 @@ class TestTool 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) { @@ -204,13 +216,15 @@ TestStats TestTool::processPath( fs::path const& _basepath, fs::path const& _path, string const& _ipcPath, - bool const _formatted + bool const _formatted, + langutil::EVMVersion const _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 const _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; } From 725fc898fd140b406dba537ad00a7da7b7b016e0 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Thu, 14 Mar 2019 14:58:24 +0100 Subject: [PATCH 23/88] Discard fuzz input containing at least one character not in the following set: "isprint"able, newline, and horizontal Co-Authored-By: bshastry --- test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp | 5 +++++ 1 file changed, 5 insertions(+) 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 { From 82ced641e582c9a02ebf825751b514764ae5cb5a Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 18 Mar 2019 12:43:20 +0100 Subject: [PATCH 24/88] [Yul] Adds another test case for multiple continue statements within a for-loop --- .../rematerialiser/for_continue_2.yul | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul 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) +// } From 61be78b42af875b43e58eaea502835715600ed64 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Mon, 18 Mar 2019 14:20:47 +0100 Subject: [PATCH 25/88] Adding populus gotcha covering storage costs --- docs/introduction-to-smart-contracts.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index bfce0d5a33b8..8675474a7199 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -388,7 +388,9 @@ Each account has a data area called **storage**, which is persistent between fun 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. +comparatively costly to read, and even more to modify storage. Because of this cost, +you should minimise 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 From a1784864369274aa1f776acec174deeb2c62fd48 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Mon, 18 Mar 2019 14:08:56 +0100 Subject: [PATCH 26/88] Review suggestions. --- test/ExecutionFramework.cpp | 2 +- test/ExecutionFramework.h | 2 +- test/TestCase.cpp | 10 ++++------ test/libsolidity/SMTCheckerJSONTest.cpp | 4 ++-- test/libsolidity/SMTCheckerJSONTest.h | 4 ++-- test/libsolidity/SemanticTest.cpp | 6 +++--- test/libsolidity/SemanticTest.h | 6 +++--- .../libsolidity/SolidityExecutionFramework.cpp | 2 +- test/libsolidity/SolidityExecutionFramework.h | 2 +- test/libsolidity/SyntaxTest.cpp | 10 +++++----- test/libsolidity/SyntaxTest.h | 12 ++++++------ test/tools/isoltest.cpp | 18 +++++++++--------- 12 files changed, 38 insertions(+), 40 deletions(-) diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index 946530e4a5eb..6d38cab41252 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -54,7 +54,7 @@ ExecutionFramework::ExecutionFramework(): { } -ExecutionFramework::ExecutionFramework(string const& _ipcPath, langutil::EVMVersion const _evmVersion): +ExecutionFramework::ExecutionFramework(string const& _ipcPath, langutil::EVMVersion _evmVersion): m_rpc(RPCSession::instance(_ipcPath)), m_evmVersion(_evmVersion), m_optimize(dev::test::Options::get().optimize), diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 9b7e3a9a9618..27eb1da1f903 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -53,7 +53,7 @@ class ExecutionFramework public: ExecutionFramework(); - explicit ExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion const _evmVersion); + explicit ExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion _evmVersion); virtual ~ExecutionFramework() = default; virtual bytes const& compileAndRunWithoutCheck( diff --git a/test/TestCase.cpp b/test/TestCase.cpp index 3a5ae425e584..6f3c92a53f8b 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -37,18 +38,15 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename) bool TestCase::supportedForEVMVersion(langutil::EVMVersion _evmVersion) const { - for (auto const& rule: m_evmVersionRules) - if (!rule(_evmVersion)) - return false; - return true; + 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("// ----"); - string const evmVersion("// EVMVersion: "); + static string const delimiter("// ----"); + static string const evmVersion("// EVMVersion: "); bool isTop = true; while (getline(_stream, line)) if (boost::algorithm::starts_with(line, delimiter)) diff --git a/test/libsolidity/SMTCheckerJSONTest.cpp b/test/libsolidity/SMTCheckerJSONTest.cpp index 0d6b9ba8f5e4..d682c250ea37 100644 --- a/test/libsolidity/SMTCheckerJSONTest.cpp +++ b/test/libsolidity/SMTCheckerJSONTest.cpp @@ -35,7 +35,7 @@ using namespace dev; using namespace std; using namespace boost::unit_test; -SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion const _evmVersion) +SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion _evmVersion) : SyntaxTest(_filename, _evmVersion) { if (!boost::algorithm::ends_with(_filename, ".sol")) @@ -49,7 +49,7 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion con 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 57d79cc16224..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, _config.evmVersion)); + return std::make_unique(_config.filename, _config.evmVersion); } 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 5c1a67ae14eb..1dcf91719f67 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -36,7 +36,7 @@ using namespace boost::unit_test; namespace fs = boost::filesystem; -SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, langutil::EVMVersion const _evmVersion): +SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, langutil::EVMVersion _evmVersion): SolidityExecutionFramework(_ipcPath, _evmVersion) { ifstream file(_filename); @@ -47,7 +47,7 @@ SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, lang 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 352f266ec0bc..27e64422041d 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -46,10 +46,10 @@ class SemanticTest: public SolidityExecutionFramework, public TestCase static std::unique_ptr create(Config const& _options) { return std::make_unique(_options.filename, _options.ipcPath, _options.evmVersion); } - explicit SemanticTest(std::string const& _filename, std::string const& _ipcPath, langutil::EVMVersion const _evmVersion); + 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/SolidityExecutionFramework.cpp b/test/libsolidity/SolidityExecutionFramework.cpp index ccd8c5d09ca4..1b2322e898c8 100644 --- a/test/libsolidity/SolidityExecutionFramework.cpp +++ b/test/libsolidity/SolidityExecutionFramework.cpp @@ -33,7 +33,7 @@ SolidityExecutionFramework::SolidityExecutionFramework(): { } -SolidityExecutionFramework::SolidityExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion const _evmVersion): +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 996ab19a55da..44329280d171 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, langutil::EVMVersion const _evmVersion); + SolidityExecutionFramework(std::string const& _ipcPath, langutil::EVMVersion _evmVersion); virtual bytes const& compileAndRunWithoutCheck( std::string const& _sourceCode, diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index a91fae93ab0d..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, langutil::EVMVersion const _evmVersion): m_evmVersion(_evmVersion) +SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) { ifstream file(_filename); if (!file) @@ -63,7 +63,7 @@ SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion const _evmV 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(); @@ -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 a1eed3209499..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, _config.evmVersion)); } - SyntaxTest(std::string const& _filename, langutil::EVMVersion const _evmVersion); + { 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,10 +72,10 @@ 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); diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index c6c9bb8b54d4..1269f5e80938 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -50,7 +50,7 @@ struct TestStats int successCount = 0; int testCount = 0; int skippedCount = 0; - operator bool() const { return successCount + skippedCount == testCount; } + operator bool() const noexcept { return successCount + skippedCount == testCount; } TestStats& operator+=(TestStats const& _other) noexcept { successCount += _other.successCount; @@ -88,8 +88,8 @@ class TestTool fs::path const& _basepath, fs::path const& _path, string const& _ipcPath, - bool const _formatted, - langutil::EVMVersion const _evmVersion + bool _formatted, + langutil::EVMVersion _evmVersion ); static string editor; @@ -101,7 +101,7 @@ class TestTool Quit }; - Request handleResponse(bool const _exception); + Request handleResponse(bool _exception); TestCase::TestCaseCreator m_testCaseCreator; string const m_name; @@ -170,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? "; @@ -216,8 +216,8 @@ TestStats TestTool::processPath( fs::path const& _basepath, fs::path const& _path, string const& _ipcPath, - bool const _formatted, - langutil::EVMVersion const _evmVersion + bool _formatted, + langutil::EVMVersion _evmVersion ) { std::queue paths; @@ -318,7 +318,7 @@ boost::optional runTestSuite( string const& _ipcPath, TestCase::TestCaseCreator _testCaseCreator, bool _formatted, - langutil::EVMVersion const _evmVersion + langutil::EVMVersion _evmVersion ) { fs::path testPath = _basePath / _subdirectory; @@ -395,7 +395,7 @@ int main(int argc, char const *argv[]) { cout << " ("; AnsiColorized(cout, !options.noColor, {BOLD, YELLOW}) << global_stats.skippedCount; - cout<< " tests skipped)"; + cout << " tests skipped)"; } cout << "." << endl; From 855555e9414d00a3f48b4f5e18023eb22365643b Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Mon, 18 Mar 2019 14:37:47 +0100 Subject: [PATCH 27/88] Add mention of initialisation cost of storage --- docs/introduction-to-smart-contracts.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 8675474a7199..08736529034b 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -387,8 +387,8 @@ 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. Because of this cost, +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 minimise 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. From 7075f158e3ad47768f16f69134a24a7a7dbd9989 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Mon, 18 Mar 2019 16:30:23 +0100 Subject: [PATCH 28/88] Instead update heading --- docs/introduction-to-smart-contracts.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index bfce0d5a33b8..6b34ad6ccdf3 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -12,8 +12,8 @@ Let us begin with a basic example that sets the value of a variable and exposes it for other contracts to access. It is fine if you do not understand everything right now, we will go into more detail later. -Storage -======= +Storage Example +=============== :: From 89f1dbfbb6d5d04c5e9905405152b30bf876c3a0 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 18 Mar 2019 17:38:25 +0100 Subject: [PATCH 29/88] [Yul] Refactors RedundantAssignEliminator for future changes wrt. break/continue statements. --- .../optimiser/RedundantAssignEliminator.cpp | 51 ++++++++++--------- libyul/optimiser/RedundantAssignEliminator.h | 23 +++++---- 2 files changed, 38 insertions(+), 36 deletions(-) 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 From f95388011b287497c3983b9110b871ab8b366e47 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 25 Feb 2019 11:31:35 +0000 Subject: [PATCH 30/88] Allow simplification patterns to signal failure --- libevmasm/ExpressionClasses.cpp | 1 + libevmasm/SimplificationRule.h | 13 +++++++++++++ libevmasm/SimplificationRules.cpp | 4 +++- libyul/optimiser/SimplificationRules.cpp | 3 ++- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index abbbbc2c57f0..db60d15bfbb9 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -203,6 +203,7 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr) //cout << "with rule " << match->first.toString() << endl; //ExpressionTemplate t(match->second()); //cout << "to " << match->second().toString() << endl; + return rebuildExpression(ExpressionTemplate(match->action(), _expr.item->location())); } 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/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; } From 15a5b2bf65d983dcafede56c6edb760afd31b0a2 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 19 Mar 2019 16:06:23 +0100 Subject: [PATCH 31/88] Move CommonData operator functions to the global namespace --- libdevcore/CommonData.h | 81 +++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index c157583a274c..63a374c300fb 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -34,6 +34,47 @@ #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 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 +231,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 From 6b60e90cdea86152c26233ccce228b8c540b16d2 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 12 Mar 2019 19:46:52 +0100 Subject: [PATCH 32/88] Fix commented debug output code --- libevmasm/ExpressionClasses.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index db60d15bfbb9..41cf8990e2ca 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -196,13 +196,15 @@ 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())); } From 7babe3dbba70b1ddff81bd2eaea13984a1d3bef2 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 19 Mar 2019 16:06:19 +0000 Subject: [PATCH 33/88] Introduce simplfied CBOR encoding for Metadata --- libsolidity/interface/CompilerStack.cpp | 107 +++++++++++++++++++----- 1 file changed, 87 insertions(+), 20 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 3c753c25b8bf..5b17e6e7f51f 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1024,29 +1024,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 From 7f7f44dd63a708cc388f2fd967180f9ff8cab0e6 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Tue, 19 Mar 2019 17:08:38 +0100 Subject: [PATCH 34/88] Improve exception information in Yul function datasize(name) and dataoffset(name). Makes sure we properly throw a detailed exception instead of an out_of_range from std::map. --- libyul/backends/evm/EVMDialect.cpp | 6 ++++++ 1 file changed, 6 insertions(+) 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&, From 87ebb168906c639abc5bd2e469b1e4bb94195418 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 18 Mar 2019 13:12:10 +0100 Subject: [PATCH 35/88] Use ABI_CHECK for event data comparison. --- test/libsolidity/ABIEncoderTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index a8f175543d4e..be461d641685 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -41,7 +41,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) From 7c524f794fde2822fdda26263b36d211403b376c Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 19 Mar 2019 17:22:56 +0100 Subject: [PATCH 36/88] Also output optimized yul code if requested. --- libsolidity/codegen/CompilerContext.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index e106a4839c42..30b1ebf72574 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -439,6 +439,10 @@ 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()) From b82b970429640a51ba2649160830b590255adcda Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 19 Mar 2019 17:50:50 +0100 Subject: [PATCH 37/88] Add operator for move-append onto set. --- libdevcore/CommonData.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 63a374c300fb..f587b353377b 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -55,6 +55,13 @@ 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) From 9acec99c312241e93b8da6b2cb7e42abe02bebaa Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 19 Mar 2019 16:51:33 +0100 Subject: [PATCH 38/88] Issue error for callvalue in nonpayable function --- Changelog.md | 1 + libsolidity/analysis/ViewPureChecker.cpp | 8 +++++--- .../memberLookup/msg_value_modifier_view.sol | 2 +- ...ns_msg_value_in_non_payable_public_function.sol | 2 +- .../callvalue_nonpayable_assembly_fallback.sol | 11 +++++++++++ .../callvalue_nonpayable_assembly_function.sol | 10 ++++++++++ ...value_nonpayable_assembly_function_modifier.sol | 14 ++++++++++++++ .../callvalue_payable_assembly_fallback.sol | 9 +++++++++ .../callvalue_payable_assembly_function.sol | 8 ++++++++ ...allvalue_payable_assembly_function_modifier.sol | 12 ++++++++++++ .../viewPureChecker/msg_value_modifier_view.sol | 2 +- 11 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_fallback.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_modifier.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_fallback.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/callvalue_payable_assembly_function_modifier.sol diff --git a/Changelog.md b/Changelog.md index 7e422b8e7b2d..26995cf7392f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Language Features: Compiler Features: + * Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does). * Yul Optimizer: Enable stack allocation optimization by default if yul optimizer is active (disable in yulDetails). 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/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/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/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_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. From 0386d39942aff46b60ed774f5941253841194a96 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 19 Mar 2019 20:45:20 +0100 Subject: [PATCH 39/88] Add test with internal function --- ...allvalue_nonpayable_assembly_function_internal.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/callvalue_nonpayable_assembly_function_internal.sol 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(); + } +} From 3296fb37643eb3a4ed11b223e54eaeff4e10ea56 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Mon, 18 Mar 2019 13:04:08 +0100 Subject: [PATCH 40/88] Add callstack to model report --- Changelog.md | 1 + liblangutil/Exceptions.h | 6 ++++++ libsolidity/formal/SMTChecker.cpp | 36 +++++++++++++++++++++++++------ libsolidity/formal/SMTChecker.h | 4 ++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index 26995cf7392f..113ec3037130 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: Compiler Features: * Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does). + * SMTChecker: Show callstack together with model if applicable. * Yul Optimizer: Enable stack allocation optimization by default if yul optimizer is active (disable in yulDetails). 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/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index d9e2340214d5..95622c3804ce 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -24,6 +24,7 @@ #include #include +#include #include using namespace std; @@ -105,6 +106,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) { m_interface->reset(); m_pathConditions.clear(); + m_callStack.clear(); m_expressions.clear(); m_globalContext.clear(); m_uninterpretedTerms.clear(); @@ -495,7 +497,9 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) visitGasLeft(_funCall); break; case FunctionType::Kind::Internal: + m_callStack.push_back(&_funCall); inlineFunctionCall(_funCall); + m_callStack.pop_back(); break; case FunctionType::Kind::External: resetStateVariables(); @@ -1165,18 +1169,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 +1202,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."); @@ -1540,6 +1553,17 @@ 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; +} + 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..a547e0133f44 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -229,6 +229,8 @@ 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(); /// 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 +271,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(); From 9659f40c8d2a3a9c79b44be4bdc15caf81b938c1 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Mon, 11 Mar 2019 21:06:28 +0100 Subject: [PATCH 41/88] [SMTChecker] Support modifiers --- Changelog.md | 1 + libsolidity/formal/SMTChecker.cpp | 87 +++++++++++++++++-- libsolidity/formal/SMTChecker.h | 18 +++- .../modifier_code_after_placeholder.sol | 22 +++++ .../modifiers/modifier_control_flow.sol | 18 ++++ .../modifiers/modifier_multi.sol | 25 ++++++ .../modifiers/modifier_multi_functions.sol | 26 ++++++ .../modifier_multi_functions_recursive.sol | 24 +++++ .../modifiers/modifier_multi_parameters.sol | 16 ++++ .../modifiers/modifier_overflow.sol | 18 ++++ .../modifiers/modifier_parameter_copy.sol | 15 ++++ .../modifiers/modifier_parameters.sol | 18 ++++ .../modifiers/modifier_return.sol | 21 +++++ .../modifier_same_local_variables.sol | 15 ++++ .../modifiers/modifier_simple.sol | 15 ++++ .../modifiers/modifier_two_invocations.sol | 21 +++++ .../modifiers/modifier_two_placeholders.sol | 22 +++++ 17 files changed, 374 insertions(+), 8 deletions(-) create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_control_flow.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_multi_functions_recursive.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_multi_parameters.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_overflow.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_parameter_copy.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_parameters.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_return.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_same_local_variables.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_simple.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_two_invocations.sol create mode 100644 test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol diff --git a/Changelog.md b/Changelog.md index 113ec3037130..70b0d00ca1b6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Language Features: Compiler Features: * Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does). * 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). diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 95622c3804ce..5085a7991312 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -93,14 +93,20 @@ 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()) { @@ -116,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; } @@ -131,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) @@ -497,9 +553,9 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) visitGasLeft(_funCall); break; case FunctionType::Kind::Internal: - m_callStack.push_back(&_funCall); + pushCallStack(&_funCall); inlineFunctionCall(_funCall); - m_callStack.pop_back(); + popCallStack(); break; case FunctionType::Kind::External: resetStateVariables(); @@ -1261,7 +1317,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; @@ -1276,7 +1332,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() + ); } } @@ -1316,7 +1376,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(), ""); @@ -1564,6 +1624,19 @@ SecondarySourceLocation SMTChecker::currentCallStack() 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 a547e0133f44..33aeada2cdd3 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 @@ -174,7 +180,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(); @@ -231,6 +237,10 @@ class SMTChecker: private ASTConstVisitor 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 @@ -280,6 +290,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/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 From bb28c8a877c34a771e682d195e6641b81b2f25d9 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Wed, 20 Mar 2019 11:32:33 +0100 Subject: [PATCH 42/88] Fixes wrong source location when reporting pragma solidity version conflicts. --- libsolidity/parsing/Parser.cpp | 10 +++++++--- libsolidity/parsing/Parser.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) 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(); From 84b68006ba765bc9608c8a0bf6e85c5de5260fb7 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 19 Mar 2019 17:12:21 +0100 Subject: [PATCH 43/88] Fix function calls with named arguments for overloaded functions --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 23 +++++++----- libsolidity/ast/ASTAnnotations.h | 10 +++-- libsolidity/ast/ASTEnums.h | 16 ++++++++ libsolidity/ast/ASTJsonConverter.cpp | 8 ++-- libsolidity/ast/ASTJsonConverter.h | 2 +- libsolidity/ast/Types.cpp | 37 ++++++++++++++++--- libsolidity/ast/Types.h | 8 +++- .../functionCall/named_args_overload.sol | 32 ++++++++++++++++ .../named_arguments_overload.sol | 14 +++++++ .../named_arguments_overload_failing1.sol | 14 +++++++ .../named_arguments_overload_failing2.sol | 12 ++++++ .../named_arguments_overload_failing3.sol | 11 ++++++ 13 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 test/libsolidity/semanticTests/functionCall/named_args_overload.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol diff --git a/Changelog.md b/Changelog.md index 26995cf7392f..f1878b4d58df 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Language Features: Compiler Features: + * Function calls with named arguments now work with overloaded functions. * Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does). * Yul Optimizer: Enable stack allocation optimization by default if yul optimizer is active (disable in yulDetails). diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index ce73164a1208..d1e4516b07cf 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1807,13 +1807,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 +2013,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 +2277,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 +2306,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/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 9498d35ae3f5..3ba1f928d5fb 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2986,26 +2986,53 @@ TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const return TypePointer(); } -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 diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 8aea1803dd82..a6c2991873a5 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1107,11 +1107,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) 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/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. From 38cbf8d230bf13836cfd1e4ab7ec0b0c5a2e07b9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Mar 2019 17:44:16 +0100 Subject: [PATCH 44/88] Pass "optimize stack allocation" flag down to the stack compressor. --- libsolidity/codegen/CompilerContext.cpp | 1 + libyul/AssemblyStack.cpp | 5 ++++- libyul/CompilabilityChecker.cpp | 9 ++++++--- libyul/CompilabilityChecker.h | 6 +++++- libyul/optimiser/StackCompressor.cpp | 6 +++--- libyul/optimiser/StackCompressor.h | 2 +- libyul/optimiser/Suite.cpp | 3 ++- libyul/optimiser/Suite.h | 1 + test/libyul/CompilabilityChecker.cpp | 2 +- test/libyul/YulOptimizerTest.cpp | 4 ++-- test/tools/yulopti.cpp | 2 +- 11 files changed, 27 insertions(+), 14 deletions(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 30b1ebf72574..ef642becf540 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -428,6 +428,7 @@ void CompilerContext::appendInlineAssembly( yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), *parserResult, analysisInfo, + _optimiserSettings.optimizeStackAllocation, externallyUsedIdentifiers ); analysisInfo = yul::AsmAnalysisInfo{}; 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/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/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index bbf34ff61e02..207d1b235c05 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -74,15 +74,15 @@ 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) { 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 < 6; 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..a6f20b8b4d5d 100644 --- a/libyul/optimiser/StackCompressor.h +++ b/libyul/optimiser/StackCompressor.h @@ -41,7 +41,7 @@ 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); }; } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index ef9026999ca4..c30987ed5611 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 ) { @@ -185,7 +186,7 @@ void OptimiserSuite::run( UnusedPruner::runUntilStabilised(*_dialect, ast, reservedIdentifiers); FunctionGrouper{}(ast); - StackCompressor::run(_dialect, ast); + solAssert(StackCompressor::run(_dialect, ast, _optimizeStackAllocation), ""); 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/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/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index eb67eacb9cb8..c0c87369902a 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -247,11 +247,11 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con { disambiguate(); (FunctionGrouper{})(*m_ast); - StackCompressor::run(m_dialect, *m_ast); + StackCompressor::run(m_dialect, *m_ast, true); (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/tools/yulopti.cpp b/test/tools/yulopti.cpp index c7429baab6c9..80de0fac7ce1 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); break; default: cout << "Unknown option." << endl; From aa9a2935ace732a4ef4a229d2e422aaf28688631 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Mar 2019 17:44:45 +0100 Subject: [PATCH 45/88] Properly determine whether a variable can be eliminated or not. --- libyul/optimiser/StackCompressor.cpp | 88 +++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 207d1b235c05..346d8267f752 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; From 9ac117e5b989630a906a6decdaeead6aeab92106 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Mar 2019 17:46:42 +0100 Subject: [PATCH 46/88] Changelog entry. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 113ec3037130..94abf51fdaeb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ Compiler Features: Bugfixes: * Code Generator: Defensively pad memory for ``type(Contract).name`` to multiples of 32. + * Yul Optimizer: Properly determine whether a variable can be eliminated during stack compression pass. Build System: From 8514c0bc60f8e1e1c1c105f12a74a1c816c42bce Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 14 Mar 2019 18:25:33 +0100 Subject: [PATCH 47/88] Provide max iterations for stack compressor as parameter. --- libyul/optimiser/StackCompressor.cpp | 9 +++++++-- libyul/optimiser/StackCompressor.h | 7 ++++++- libyul/optimiser/Suite.cpp | 6 +++++- test/libyul/YulOptimizerTest.cpp | 3 ++- test/tools/yulopti.cpp | 2 +- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 346d8267f752..ffd91bf928b3 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -132,13 +132,18 @@ void eliminateVariables(shared_ptr const& _dialect, ASTNode& _node, siz } -bool StackCompressor::run(shared_ptr const& _dialect, Block& _ast, bool _optimizeStackAllocation) +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 < 6; iterations++) + for (size_t iterations = 0; iterations < _maxIterations; iterations++) { map stackSurplus = CompilabilityChecker::run(_dialect, _ast, _optimizeStackAllocation); if (stackSurplus.empty()) diff --git a/libyul/optimiser/StackCompressor.h b/libyul/optimiser/StackCompressor.h index a6f20b8b4d5d..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, bool _optimizeStackAllocation); + 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 c30987ed5611..91ac3f3a769c 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -185,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); - solAssert(StackCompressor::run(_dialect, ast, _optimizeStackAllocation), ""); + // 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/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index c0c87369902a..7d05ea9f5ad4 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -247,7 +247,8 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con { disambiguate(); (FunctionGrouper{})(*m_ast); - StackCompressor::run(m_dialect, *m_ast, true); + size_t maxIterations = 16; + StackCompressor::run(m_dialect, *m_ast, true, maxIterations); (BlockFlattener{})(*m_ast); } else if (m_optimizerStep == "fullSuite") diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 80de0fac7ce1..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, true); + StackCompressor::run(m_dialect, *m_ast, true, 16); break; default: cout << "Unknown option." << endl; From 57f99247c8be4f606851275c84930cba2641b4a3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 14 Mar 2019 18:25:48 +0100 Subject: [PATCH 48/88] Update tests. --- .../yulOptimizerTests/fullSuite/aztec.yul | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) 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) // { From 6c1d0b62b5036c479f4f424367c022efae4d3ccf Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Wed, 6 Feb 2019 11:18:44 +0100 Subject: [PATCH 49/88] Adds oss-fuzz harnesses to fuzz AssemblyStack API calls for parsing/optimizing StrictAssembly and generating EVM bytecode. --- test/tools/ossfuzz/CMakeLists.txt | 8 ++ .../tools/ossfuzz/config/strict_assembly.dict | 91 +++++++++++++++++++ .../config/strictasm_assembly_ossfuzz.options | 2 + .../config/strictasm_opt_ossfuzz.options | 2 + .../ossfuzz/strictasm_assembly_ossfuzz.cpp | 47 ++++++++++ test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp | 37 ++++++++ 6 files changed, 187 insertions(+) create mode 100644 test/tools/ossfuzz/config/strict_assembly.dict create mode 100644 test/tools/ossfuzz/config/strictasm_assembly_ossfuzz.options create mode 100644 test/tools/ossfuzz/config/strictasm_opt_ossfuzz.options create mode 100644 test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp create mode 100644 test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp 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/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_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; +} From 2fd6661b09a7ce28ca310c825f62178ab378f469 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 12 Mar 2019 15:07:39 +0100 Subject: [PATCH 50/88] Add tests for some optimizer rules. --- test/libsolidity/SolidityOptimizer.cpp | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 22ca9182f84c..fd8adc0c558f 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -665,6 +665,44 @@ 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, uint y, uint z) public returns (uint) + { + return (x << y) << z; + } + function g(uint x, uint y, uint z) public returns (uint) + { + return (x >> y) > z; + } + } + )"; + compileBothVersions(sourceCode); + compareVersions("f(uint256,uint256,uint256)", 7, u256(-1), 5); + compareVersions("g(uint256,uint256,uint256)", 7, u256(-1), 5); + compareVersions("f(uint256,uint256,uint256)", 7, 128, 120); + compareVersions("g(uint256,uint256,uint256)", 0x71117, 2, 2); +} + + BOOST_AUTO_TEST_SUITE_END() } From d788a888731e7630089bb4fea9949cca0957276f Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 12 Mar 2019 15:14:12 +0100 Subject: [PATCH 51/88] Use "none" and "full" optimizer settings in the optimizer tests. --- test/ExecutionFramework.cpp | 2 +- test/ExecutionFramework.h | 5 ++-- test/libsolidity/GasCosts.cpp | 2 +- test/libsolidity/SolidityExecutionFramework.h | 2 +- test/libsolidity/SolidityOptimizer.cpp | 26 +++++++++---------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index 6d38cab41252..3359fa322673 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -57,7 +57,7 @@ ExecutionFramework::ExecutionFramework(): ExecutionFramework::ExecutionFramework(string const& _ipcPath, langutil::EVMVersion _evmVersion): m_rpc(RPCSession::instance(_ipcPath)), m_evmVersion(_evmVersion), - m_optimize(dev::test::Options::get().optimize), + m_optimiserSettings(dev::test::Options::get().optimize ? solidity::OptimiserSettings::enabled() : 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 27eb1da1f903..4a42382d0845 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -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/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/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 44329280d171..900d0b9d8896 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -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 fd8adc0c558f..5f0f66664ec8 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" / "enabled". + 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; } @@ -685,21 +685,19 @@ BOOST_AUTO_TEST_CASE(shift_optimizer_bug) char const* sourceCode = R"( contract C { - function f(uint x, uint y, uint z) public returns (uint) + function f(uint x) public returns (uint) { - return (x << y) << z; + return (x << 1) << uint(-1); } - function g(uint x, uint y, uint z) public returns (uint) + function g(uint x) public returns (uint) { - return (x >> y) > z; + return (x >> 1) >> uint(-1); } } )"; compileBothVersions(sourceCode); - compareVersions("f(uint256,uint256,uint256)", 7, u256(-1), 5); - compareVersions("g(uint256,uint256,uint256)", 7, u256(-1), 5); - compareVersions("f(uint256,uint256,uint256)", 7, 128, 120); - compareVersions("g(uint256,uint256,uint256)", 0x71117, 2, 2); + compareVersions("f(uint256)", 7); + compareVersions("g(uint256)", u256(-1)); } From ea1944bb2d85f14d6d5e089f0159da4694c570be Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 20 Mar 2019 17:24:32 +0000 Subject: [PATCH 52/88] Removed unused isLibrary from flag addSource in CompilerStack --- libsolidity/interface/CompilerStack.cpp | 6 ++---- libsolidity/interface/CompilerStack.h | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 5b17e6e7f51f..e444b2d2a300 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -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; } @@ -828,8 +827,7 @@ void CompilerStack::resolveImports() }; for (auto const& sourcePair: m_sources) - if (!sourcePair.second.isLibrary) - toposort(&sourcePair.second); + toposort(&sourcePair.second); swap(m_sourceOrder, sourceOrder); } 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(); } From 22f5a82edcd3641443abe85e8eb980ccc145f75b Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 18 Mar 2019 17:12:03 +0100 Subject: [PATCH 53/88] yul proto: Add support for generating for and switch statements. --- test/tools/ossfuzz/protoToYul.cpp | 31 +++++++++++++++++++++++++++++++ test/tools/ossfuzz/protoToYul.h | 3 +++ test/tools/ossfuzz/yulProto.proto | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index ec0dfdeb19a4..742b62d395bb 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -235,6 +235,31 @@ 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) { switch (_x.stmt_oneof_case()) @@ -254,6 +279,12 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Statement const& _x) 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; } diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index f586d97ca492..3a427931f957 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -46,6 +46,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); } } } diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 60f9b2f1ff4e..33fd51e135f0 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -139,6 +139,21 @@ message IfStmt { required Block if_body = 2; } +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; @@ -146,6 +161,8 @@ message Statement { IfStmt ifstmt = 3; StoreFunc storage_func = 4; Block blockstmt = 5; + ForStmt forstmt = 6; + SwitchStmt switchstmt = 7; } } From 7ec3eaa40d5d8063912c520d59d18e7b7aad63c3 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 20 Mar 2019 19:21:24 +0000 Subject: [PATCH 54/88] Fix LLL tests using the opimiser --- test/liblll/ExecutionFramework.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/liblll/ExecutionFramework.h b/test/liblll/ExecutionFramework.h index 7c1ce6708b4e..b5c15f8815fb 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::enabled(), + &errors + ); if (!errors.empty()) { for (auto const& error: errors) From 0fbea8a1a0724aa92ff6e45c6045827916cfaf6c Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Mon, 18 Mar 2019 13:22:42 +0100 Subject: [PATCH 55/88] Change return type for interfaceType() to ResultType --- libsolidity/analysis/ReferencesResolver.cpp | 2 +- libsolidity/analysis/TypeChecker.cpp | 6 ++-- libsolidity/ast/Types.cpp | 20 ++++++------- libsolidity/ast/Types.h | 32 ++++++++++----------- libsolidity/interface/ABI.cpp | 8 +++--- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index d624a0135cd9..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->interfaceType(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 d1e4516b07cf..9399e7f88b4a 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -355,7 +355,7 @@ 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))) + if (_function.isPublic() && !(type(var)->interfaceType(isLibraryFunction).get())) m_errorReporter.fatalTypeError(var.location(), "Internal or recursive type is not allowed for public or external functions."); } if ( @@ -576,7 +576,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 +599,7 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) { FunctionType const& fun = dynamic_cast(*_funType.annotation().type); if (fun.kind() == FunctionType::Kind::External) - solAssert(fun.interfaceType(false), "External function type uses internal types."); + solAssert(fun.interfaceType(false).get(), "External function type uses internal types."); } bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3ba1f928d5fb..754f006ba193 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1869,7 +1869,7 @@ TypePointer ArrayType::decodingType() const return shared_from_this(); } -TypePointer ArrayType::interfaceType(bool _inLibrary) const +TypeResult ArrayType::interfaceType(bool _inLibrary) const { if (_inLibrary && m_interfaceType_library.is_initialized()) return *m_interfaceType_library; @@ -2135,7 +2135,7 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const return members; } -TypePointer StructType::interfaceType(bool _inLibrary) const +TypeResult StructType::interfaceType(bool _inLibrary) const { if (_inLibrary && m_interfaceType_library.is_initialized()) return *m_interfaceType_library; @@ -2168,7 +2168,7 @@ TypePointer StructType::interfaceType(bool _inLibrary) const allOkay = false; break; } - if (!var->annotation().type->interfaceType(false)) + if (!var->annotation().type->interfaceType(false).get()) { allOkay = false; break; @@ -2204,8 +2204,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, ",") + ")"; } @@ -2574,7 +2574,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): solAssert(t->annotation().type, "Type not set for parameter."); if (m_kind == Kind::External) solAssert( - t->annotation().type->interfaceType(false), + t->annotation().type->interfaceType(false).get(), "Internal type used as parameter for external function." ); m_parameterTypes.push_back(t->annotation().type); @@ -2584,7 +2584,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->interfaceType(false), + t->annotation().type->interfaceType(false).get(), "Internal type used as return parameter for external function." ); m_returnParameterTypes.push_back(t->annotation().type); @@ -2885,14 +2885,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(); @@ -2978,7 +2978,7 @@ 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(); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index a6c2991873a5..b79baa602860 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -304,7 +304,7 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this m_interfaceType; - mutable boost::optional m_interfaceType_library; + mutable boost::optional m_interfaceType; + mutable boost::optional m_interfaceType_library; }; /** @@ -788,7 +788,7 @@ 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{}; @@ -842,7 +842,7 @@ class StructType: public ReferenceType { return location() == DataLocation::Storage ? std::make_shared(256) : shared_from_this(); } - TypePointer interfaceType(bool _inLibrary) const override; + TypeResult interfaceType(bool _inLibrary) const override; TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; @@ -872,8 +872,8 @@ class StructType: public ReferenceType StructDefinition const& m_struct; /// Cache for the recursive() function. mutable boost::optional m_recursive; - mutable boost::optional m_interfaceType; - mutable boost::optional m_interfaceType_library; + mutable boost::optional m_interfaceType; + mutable boost::optional m_interfaceType_library; }; /** @@ -902,7 +902,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(); } @@ -1098,7 +1098,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 @@ -1223,7 +1223,7 @@ class MappingType: public Type { return std::make_shared(256); } - TypePointer interfaceType(bool _inLibrary) const override + TypeResult interfaceType(bool _inLibrary) const override { return _inLibrary ? shared_from_this() : TypePointer(); } 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 From 8e899a0d32de5032ec9986e14cf1161e657bd061 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 7 Mar 2019 17:12:10 +0100 Subject: [PATCH 56/88] Disallow internal function types as parameters for public/external library function --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 12 +- libsolidity/ast/Types.cpp | 159 +++++++++++------- libsolidity/ast/Types.h | 13 +- ...external_parameter_in_library_external.sol | 6 + ...external_parameter_in_library_external.sol | 6 + ...nternal_function_as_external_parameter.sol | 2 +- ...external_parameter_in_library_external.sol | 2 +- ...function_returned_from_public_function.sol | 2 +- ...external_parameter_in_library_external.sol | 9 + ...cts_of_non_external_types_in_interface.sol | 2 +- ...s_of_non_external_types_in_interface_2.sol | 2 +- ...non_external_types_in_interface_nested.sol | 2 +- ..._struct_as_contract_function_parameter.sol | 10 ++ ...e_struct_as_library_function_parameter.sol | 9 + ...t_as_memory_library_function_parameter.sol | 14 ++ .../recursive_struct_forward_reference.sol | 2 +- ...function_as_library_function_parameter.sol | 11 ++ .../recursion/return_recursive_structs.sol | 2 +- .../recursion/return_recursive_structs2.sol | 2 +- .../recursion/return_recursive_structs3.sol | 2 +- ..._data_location_function_param_external.sol | 2 +- 22 files changed, 187 insertions(+), 85 deletions(-) create mode 100644 test/libsolidity/syntaxTests/functionTypes/internal_function_array_memory_as_external_parameter_in_library_external.sol create mode 100644 test/libsolidity/syntaxTests/functionTypes/internal_function_array_storage_as_external_parameter_in_library_external.sol create mode 100644 test/libsolidity/syntaxTests/functionTypes/internal_function_struct_as_external_parameter_in_library_external.sol create mode 100644 test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_contract_function_parameter.sol create mode 100644 test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_library_function_parameter.sol create mode 100644 test/libsolidity/syntaxTests/structs/recursion/recursive_struct_as_memory_library_function_parameter.sol create mode 100644 test/libsolidity/syntaxTests/structs/recursion/recursive_struct_with_internal_function_as_library_function_parameter.sol diff --git a/Changelog.md b/Changelog.md index 49bc87bee834..3292041fe3f1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,7 @@ Compiler Features: 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. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 9399e7f88b4a..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).get())) - 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() && diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 754f006ba193..fb321ea24280 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1877,23 +1877,22 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const if (!_inLibrary && m_interfaceType.is_initialized()) return *m_interfaceType; - TypePointer result; + TypeResult result{TypePointer{}}; + TypeResult baseExt = m_baseType->interfaceType(_inLibrary); - if (_inLibrary && location() == DataLocation::Storage) + if (!baseExt.get()) + { + solAssert(!baseExt.message().empty(), "Expected detailed error message!"); + result = baseExt; + } + else if (_inLibrary && location() == DataLocation::Storage) result = shared_from_this(); else if (m_arrayKind != ArrayKind::Ordinary) result = this->copyForLocation(DataLocation::Memory, true); + else if (isDynamicallySized()) + result = TypePointer{make_shared(DataLocation::Memory, baseExt)}; else - { - TypePointer baseExt = m_baseType->interfaceType(_inLibrary); - - if (!baseExt) - result = TypePointer(); - else if (isDynamicallySized()) - result = make_shared(DataLocation::Memory, baseExt); - else - result = make_shared(DataLocation::Memory, baseExt, m_length); - } + result = TypePointer{make_shared(DataLocation::Memory, baseExt, m_length)}; if (_inLibrary) m_interfaceType_library = result; @@ -2084,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."); @@ -2143,47 +2142,86 @@ TypeResult StructType::interfaceType(bool _inLibrary) const if (!_inLibrary && m_interfaceType.is_initialized()) return *m_interfaceType; - TypePointer result = TypePointer{}; + bool recursion = false; + TypeResult result{TypePointer{}}; - if (_inLibrary && location() == DataLocation::Storage) - result = shared_from_this(); - else if (recursive()) - result = TypePointer{}; - else + 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. - bool allOkay = true; - 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) + if (!variable->annotation().type) { - allOkay = false; - break; + result = TypeResult::err("Invalid type!"); + return; } - if (!var->annotation().type->interfaceType(false).get()) + + 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()) + ) + { + recursion = 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()) { - allOkay = false; - break; + solAssert(!iType.message().empty(), "Expected detailed error message!"); + result = iType; + return; } } - if (allOkay) - result = copyForLocation(DataLocation::Memory, true); - } + }; + + recursion = recursion || (CycleDetector(visitor).run(structDefinition()) != nullptr); + + std::string const recursiveErrMsg = "Recursive type not allowed for public or external contract functions."; if (_inLibrary) - m_interfaceType_library = result; - else + { + 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 (recursion) + m_interfaceType = TypeResult::err(recursiveErrMsg); + + return *m_interfaceType_library; + } + + if (recursion) + m_interfaceType = TypeResult::err(recursiveErrMsg); + else if (!result.message().empty()) m_interfaceType = result; + else + m_interfaceType = copyForLocation(DataLocation::Memory, true); - return result; + return *m_interfaceType; } TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const @@ -2273,27 +2311,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(); @@ -2983,7 +3000,7 @@ 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( @@ -3277,6 +3294,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 b79baa602860..c2445fc4c366 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -863,15 +863,9 @@ 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. - mutable boost::optional m_recursive; + // Caches for interfaceType(bool) mutable boost::optional m_interfaceType; mutable boost::optional m_interfaceType_library; }; @@ -1223,10 +1217,7 @@ class MappingType: public Type { return std::make_shared(256); } - TypeResult 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/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/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/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. From 7d809df91a3f7d1c3e30008f81666b316e215d7e Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 19 Mar 2019 11:47:58 +0100 Subject: [PATCH 57/88] Add back StructType::recursive() --- libsolidity/ast/Types.cpp | 11 ++++++----- libsolidity/ast/Types.h | 11 +++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index fb321ea24280..1b3e0c723061 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2142,9 +2142,10 @@ TypeResult StructType::interfaceType(bool _inLibrary) const if (!_inLibrary && m_interfaceType.is_initialized()) return *m_interfaceType; - bool recursion = false; TypeResult result{TypePointer{}}; + m_recursive = false; + auto visitor = [&]( StructDefinition const& _struct, CycleDetector& _cycleDetector, @@ -2175,7 +2176,7 @@ TypeResult StructType::interfaceType(bool _inLibrary) const _cycleDetector.run(innerStruct->structDefinition()) ) { - recursion = true; + m_recursive = true; if (_inLibrary && location() == DataLocation::Storage) continue; else @@ -2195,7 +2196,7 @@ TypeResult StructType::interfaceType(bool _inLibrary) const } }; - recursion = recursion || (CycleDetector(visitor).run(structDefinition()) != nullptr); + m_recursive = m_recursive.get() || (CycleDetector(visitor).run(structDefinition()) != nullptr); std::string const recursiveErrMsg = "Recursive type not allowed for public or external contract functions."; @@ -2208,13 +2209,13 @@ TypeResult StructType::interfaceType(bool _inLibrary) const else m_interfaceType_library = copyForLocation(DataLocation::Memory, true); - if (recursion) + if (m_recursive.get()) m_interfaceType = TypeResult::err(recursiveErrMsg); return *m_interfaceType_library; } - if (recursion) + if (m_recursive.get()) m_interfaceType = TypeResult::err(recursiveErrMsg); else if (!result.message().empty()) m_interfaceType = result; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c2445fc4c366..86ae52ff498f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -844,6 +844,16 @@ class StructType: public ReferenceType } 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; std::string canonicalName() const override; @@ -868,6 +878,7 @@ class StructType: public ReferenceType // Caches for interfaceType(bool) mutable boost::optional m_interfaceType; mutable boost::optional m_interfaceType_library; + mutable boost::optional m_recursive; }; /** From 4c2b1c1f291fa68c7ee7b5dbbea1b0dae1f1e5c1 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Wed, 20 Mar 2019 11:39:34 +0100 Subject: [PATCH 58/88] ArrayType::interfaceType(): Rename local variable to make more sense --- libsolidity/ast/Types.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 1b3e0c723061..1d592489dd5c 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1878,21 +1878,21 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const return *m_interfaceType; TypeResult result{TypePointer{}}; - TypeResult baseExt = m_baseType->interfaceType(_inLibrary); + TypeResult baseInterfaceType = m_baseType->interfaceType(_inLibrary); - if (!baseExt.get()) + if (!baseInterfaceType.get()) { - solAssert(!baseExt.message().empty(), "Expected detailed error message!"); - result = baseExt; + 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) result = this->copyForLocation(DataLocation::Memory, true); else if (isDynamicallySized()) - result = TypePointer{make_shared(DataLocation::Memory, baseExt)}; + result = TypePointer{make_shared(DataLocation::Memory, baseInterfaceType)}; else - result = TypePointer{make_shared(DataLocation::Memory, baseExt, m_length)}; + result = TypePointer{make_shared(DataLocation::Memory, baseInterfaceType, m_length)}; if (_inLibrary) m_interfaceType_library = result; From d381e26fc8ebddf4cdc6ed0693f0a4ba13edf0e7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 20 Mar 2019 16:15:07 +0100 Subject: [PATCH 59/88] Rename "enabled" optimizer setting stage to "standard". --- libsolidity/interface/CompilerStack.cpp | 4 ++-- libsolidity/interface/OptimiserSettings.h | 4 ++-- libsolidity/interface/StandardCompiler.cpp | 2 +- solc/CommandLineInterface.cpp | 2 +- test/ExecutionFramework.cpp | 2 +- test/liblll/ExecutionFramework.h | 2 +- test/libsolidity/Assembly.cpp | 2 +- test/libsolidity/SolidityOptimizer.cpp | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index e444b2d2a300..8924a8f28066 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)); } @@ -978,7 +978,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 { diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index c88778949f98..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; @@ -63,7 +63,7 @@ struct OptimiserSettings /// Standard optimisations plus yul and stack optimiser. static OptimiserSettings full() { - OptimiserSettings s = enabled(); + OptimiserSettings s = standard(); s.optimizeStackAllocation = true; s.runYulOptimiser = true; return s; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 326dac60119c..607f2d42edd8 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -369,7 +369,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")) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index ac512d4e451c..4b56db6f163f 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -904,7 +904,7 @@ 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; diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index 3359fa322673..eeac68be28aa 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -57,7 +57,7 @@ ExecutionFramework::ExecutionFramework(): ExecutionFramework::ExecutionFramework(string const& _ipcPath, langutil::EVMVersion _evmVersion): m_rpc(RPCSession::instance(_ipcPath)), m_evmVersion(_evmVersion), - m_optimiserSettings(dev::test::Options::get().optimize ? solidity::OptimiserSettings::enabled() : solidity::OptimiserSettings::minimal()), + 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/liblll/ExecutionFramework.h b/test/liblll/ExecutionFramework.h index b5c15f8815fb..2f54afa8a854 100644 --- a/test/liblll/ExecutionFramework.h +++ b/test/liblll/ExecutionFramework.h @@ -59,7 +59,7 @@ class LLLExecutionFramework: public ExecutionFramework bytes bytecode = lll::compileLLL( _sourceCode, dev::test::Options::get().evmVersion(), - m_optimiserSettings == solidity::OptimiserSettings::enabled(), + m_optimiserSettings == solidity::OptimiserSettings::standard(), &errors ); if (!errors.empty()) 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/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 5f0f66664ec8..419bc8ad8b70 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -56,7 +56,7 @@ class OptimizerTestFramework: public SolidityExecutionFramework { OptimiserSettings previousSettings = std::move(m_optimiserSettings); // This uses "none" / "full" while most other test frameworks use - // "minimal" / "enabled". + // "minimal" / "standard". m_optimiserSettings = _optimize ? OptimiserSettings::full() : OptimiserSettings::none(); m_optimiserSettings.expectedExecutionsPerDeployment = _optimizeRuns; bytes const& ret = compileAndRun(_sourceCode, _value, _contractName); From d1d3dd8571dcadeb03ba1e5f4caeafcef15e5019 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 18 Mar 2019 13:12:19 +0100 Subject: [PATCH 60/88] yul proto: Add support for generating string and hex literals. --- test/tools/ossfuzz/protoToYul.cpp | 38 +++++++++++++++++++++++++++++-- test/tools/ossfuzz/protoToYul.h | 2 ++ test/tools/ossfuzz/yulProto.proto | 2 ++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 742b62d395bb..03d3facd9266 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -19,10 +19,40 @@ #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) { switch (_x.literal_oneof_case()) @@ -30,6 +60,12 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Literal const& _x) 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; @@ -301,9 +337,7 @@ ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Block const& _x) _os << "}\n"; } else - { _os << "{}\n"; - } return _os; } diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 3a427931f957..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); diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 33fd51e135f0..bd8468d9be7a 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -48,6 +48,8 @@ message VarRef { message Literal { oneof literal_oneof { uint64 intval = 1; + string hexval = 2; + string strval = 3; } } From f7b5a27581b2fe91ee36e52a7f373196e9cad068 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Tue, 19 Mar 2019 10:44:39 +0100 Subject: [PATCH 61/88] Fixes bug in byte optimization rule and adds tests. --- Changelog.md | 3 +++ docs/bugs.json | 11 +++++++++++ docs/bugs_by_version.json | 5 ++++- libevmasm/RuleList.h | 2 +- test/libevmasm/Optimiser.cpp | 20 ++++++++++++++++++++ test/libsolidity/SolidityEndToEndTest.cpp | 21 +++++++++++++++++++++ 6 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 3292041fe3f1..e84204f01fd6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ ### 0.5.7 (unreleased) +Important Bugfixes: + * Optimizer: Fix wrong ordering of arguments in byte optimization rule for constants. + Language Features: diff --git a/docs/bugs.json b/docs/bugs.json index 327e54a183aa..0d8a6484940b 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,15 @@ [ + { + "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..b0fe056d6e7e 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -631,12 +631,15 @@ }, "0.5.5": { "bugs": [ + "IncorrectByteInstructionOptimization", "DoubleShiftSizeOverflow" ], "released": "2019-03-05" }, "0.5.6": { - "bugs": [], + "bugs": [ + "IncorrectByteInstructionOptimization" + ], "released": "2019-03-13" } } \ No newline at end of file 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/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/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"( From de89733bd6dc85dfcf468999fd2f32e2c7f35263 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Thu, 21 Mar 2019 15:46:54 +0100 Subject: [PATCH 62/88] [SMTChecker] Fix nullptr deref --- libsolidity/formal/SMTChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 5085a7991312..efa29c5143a8 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -791,7 +791,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) From a207d7f44cd661cc647f132230195d58ea22240f Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Thu, 21 Mar 2019 16:25:26 +0100 Subject: [PATCH 63/88] [SMTChecker] Add callstack model to overflow checks --- libsolidity/formal/SMTChecker.cpp | 5 ++++- libsolidity/formal/SMTChecker.h | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index efa29c5143a8..7cdaa49f3d6d 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -389,7 +389,8 @@ void SMTChecker::addOverflowTarget( std::move(_intType), std::move(_value), currentPathConditions(), - _location + _location, + m_callStack ); } @@ -397,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); } } diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 33aeada2cdd3..66f4397e8ba6 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -154,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()), ""); } From 2de5554e4a59f81a056b398f0448287793d6683f Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 21 Mar 2019 17:17:42 +0100 Subject: [PATCH 64/88] Fix MacOS commandline tests. --- test/cmdlineTests.sh | 2 ++ test/cmdlineTests/yul_stack_opt_disabled/err | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 389aed5797f5..37c319e7d07c 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -127,6 +127,8 @@ function test_solc_behaviour() 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/yul_stack_opt_disabled/err b/test/cmdlineTests/yul_stack_opt_disabled/err index 6959bbf418e7..1093ea3ae56f 100644 --- a/test/cmdlineTests/yul_stack_opt_disabled/err +++ b/test/cmdlineTests/yul_stack_opt_disabled/err @@ -1,5 +1,5 @@ Exception while assembling: -Dynamic exception type: boost::exception_detail::clone_impl +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. From 2ae778bf0a3af3121e41d1d776ea645ebb0d4e56 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Thu, 21 Mar 2019 18:47:14 +0100 Subject: [PATCH 65/88] [SMTChecker] Add buggy short circuit test --- .../control_flow/short_circuit_or.sol | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol 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 From 3bc2c35cc4e8a8173fc67f078edbde4f40fd3001 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 15:34:40 +0100 Subject: [PATCH 66/88] Support compiling Yul within StandardCompiler --- Changelog.md | 1 + docs/using-the-compiler.rst | 11 +-- docs/yul.rst | 17 ++++ libsolidity/interface/StandardCompiler.cpp | 83 +++++++++++++++++-- libsolidity/interface/StandardCompiler.h | 2 + test/cmdlineTests/standard_yul/input.json | 17 ++++ test/cmdlineTests/standard_yul/output.json | 1 + .../input.json | 17 ++++ .../output.json | 1 + .../input.json | 17 ++++ .../output.json | 1 + .../standard_yul_multiple_files/input.json | 21 +++++ .../standard_yul_multiple_files/output.json | 1 + .../input.json | 21 +++++ .../output.json | 1 + .../standard_yul_object/input.json | 17 ++++ .../standard_yul_object/output.json | 1 + .../standard_yul_object_name/input.json | 17 ++++ .../standard_yul_object_name/output.json | 1 + .../standard_yul_optimized/input.json | 23 +++++ .../standard_yul_optimized/output.json | 1 + test/libsolidity/StandardCompiler.cpp | 7 +- 22 files changed, 265 insertions(+), 14 deletions(-) create mode 100644 test/cmdlineTests/standard_yul/input.json create mode 100644 test/cmdlineTests/standard_yul/output.json create mode 100644 test/cmdlineTests/standard_yul_embedded_object_name/input.json create mode 100644 test/cmdlineTests/standard_yul_embedded_object_name/output.json create mode 100644 test/cmdlineTests/standard_yul_invalid_object_name/input.json create mode 100644 test/cmdlineTests/standard_yul_invalid_object_name/output.json create mode 100644 test/cmdlineTests/standard_yul_multiple_files/input.json create mode 100644 test/cmdlineTests/standard_yul_multiple_files/output.json create mode 100644 test/cmdlineTests/standard_yul_multiple_files_selected/input.json create mode 100644 test/cmdlineTests/standard_yul_multiple_files_selected/output.json create mode 100644 test/cmdlineTests/standard_yul_object/input.json create mode 100644 test/cmdlineTests/standard_yul_object/output.json create mode 100644 test/cmdlineTests/standard_yul_object_name/input.json create mode 100644 test/cmdlineTests/standard_yul_object_name/output.json create mode 100644 test/cmdlineTests/standard_yul_optimized/input.json create mode 100644 test/cmdlineTests/standard_yul_optimized/output.json diff --git a/Changelog.md b/Changelog.md index e84204f01fd6..46be38d419d7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ Language Features: Compiler Features: * Function calls with named arguments now work with overloaded functions. * 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). diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index ca773fa72fff..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": @@ -263,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 @@ -273,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 13a1e9b62d9c..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 diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 607f2d42edd8..c7fd2dbb9cba 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) @@ -427,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"]; @@ -850,15 +850,86 @@ 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; + if (!stack.parseAndAnalyze(sourceName, sourceContents)) + { + 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; + } + + 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/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..1724d333e215 --- /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"}}}} 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..0967ef424bce --- /dev/null +++ b/test/cmdlineTests/standard_yul_embedded_object_name/output.json @@ -0,0 +1 @@ +{} 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..0967ef424bce --- /dev/null +++ b/test/cmdlineTests/standard_yul_invalid_object_name/output.json @@ -0,0 +1 @@ +{} 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..5e45df9b7b05 --- /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"}}}} 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..af29f34a1e56 --- /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"}}}} 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..5eea1cb16677 --- /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"}}}} diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 1a539bc8f34f..707ed65e68a0 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) From 532d43e0c88759767c284cfd216969a6d74dc181 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 22 Mar 2019 17:08:16 +0000 Subject: [PATCH 67/88] Exclude metadata in compiler test In SolidityCompiler/does_not_include_creation_time_only_internal_functions. --- test/libsolidity/SolidityCompiler.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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() From 5936f52aa5066012b30205e5aceaa4bee35780db Mon Sep 17 00:00:00 2001 From: Constantin Kloecker Date: Sat, 23 Mar 2019 11:38:59 +0100 Subject: [PATCH 68/88] fixed code block display --- docs/examples/micropayment.rst | 1 + 1 file changed, 1 insertion(+) 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 Date: Mon, 25 Mar 2019 10:53:49 +0100 Subject: [PATCH 69/88] Fix LLL tests. --- test/liblll/EndToEndTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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() From 66d6711364222d30e1a181e1b79fbe5405c14885 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 15 Mar 2019 13:53:26 +0100 Subject: [PATCH 70/88] ABIFunctions: Split out a function for generating a comma separated list of variable names. --- libsolidity/codegen/ABIFunctions.cpp | 66 ++++++++++++++++++---------- libsolidity/codegen/ABIFunctions.h | 12 +++++ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index d78c6e7fb173..eb73fcb9e7ba 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(); @@ -636,29 +626,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(); } }); @@ -1566,3 +1559,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..c9c19f80fbfc 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -253,6 +253,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; From 7f8957c9ea0db9406be31e5cd882982ea9bfbddd Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Mon, 25 Mar 2019 10:59:49 +0100 Subject: [PATCH 71/88] Change to US spelling --- docs/introduction-to-smart-contracts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 08736529034b..0a7ea861ebd1 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -389,7 +389,7 @@ 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, it is comparatively costly to read, and even more to initialise and modify storage. Because of this cost, -you should minimise what you store in persistent storage to what the contract needs to run. +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. From d9303d4cfbaf8d619a717596b4019c5c84c13e72 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 25 Mar 2019 11:14:17 +0000 Subject: [PATCH 72/88] Split imports tests to avoid resetting CompilerStack --- test/libsolidity/Imports.cpp | 48 ++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) 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) From caddce6ef03b504fd4b40e06f7630ab200cf2b6e Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 25 Mar 2019 12:30:05 +0100 Subject: [PATCH 73/88] Detect duplicate cases based on integer value of case label. --- Changelog.md | 1 + libyul/AsmAnalysis.cpp | 4 ++-- test/libyul/Parser.cpp | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 46be38d419d7..4fcf6e595940 100644 --- a/Changelog.md +++ b/Changelog.md @@ -19,6 +19,7 @@ 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: 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/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 From b7af8baed5f1aa17663eae943d466d13db4d42cc Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Mon, 25 Mar 2019 14:04:01 +0100 Subject: [PATCH 74/88] Add universal mutator to resources --- docs/resources.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/resources.rst b/docs/resources.rst index 41c126ad76bf..a3a89113aaf1 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -124,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. From 642d4b9217a6b543a7fe0732931dbbbf9648a5d8 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 25 Mar 2019 13:09:18 +0000 Subject: [PATCH 75/88] Abort if parseAndAnalyze failed but no errors are reported in compileYul --- libsolidity/interface/StandardCompiler.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index c7fd2dbb9cba..43b5aeae2430 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -867,7 +867,12 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) AssemblyStack stack(_inputsAndSettings.evmVersion, AssemblyStack::Language::StrictAssembly); string const& sourceName = _inputsAndSettings.sources.begin()->first; string const& sourceContents = _inputsAndSettings.sources.begin()->second; - if (!stack.parseAndAnalyze(sourceName, sourceContents)) + + // 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()) From e478115d2637d3f068eb2772e0f20968d0d1179c Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Mon, 25 Mar 2019 15:09:15 +0100 Subject: [PATCH 76/88] Provide more info when CI fails --- test/RPCSession.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 84b7da8e9b62..4edf0c218b37 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.asString()); + return result["result"]; } From e8605ea831c4698e4e4e72d9df934940a5fe06c5 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Mon, 25 Mar 2019 17:22:30 +0100 Subject: [PATCH 77/88] Add Homebrew version mention --- docs/installing-solidity.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 375130b88421..fcbeb3aaad2a 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -126,6 +126,8 @@ currently not supported. brew tap ethereum/ethereum brew install solidity +To install the most recent 0.4.x version of Solidity you can also use ``brew install solidity@4``. + If you need a specific version of Solidity you can install a Homebrew formula directly from Github. @@ -140,7 +142,7 @@ Install it using ``brew``: .. code-block:: bash brew unlink solidity - # Install 0.4.8 + # eg. Install 0.4.8 brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb Gentoo Linux also provides a solidity package that can be installed using ``emerge``: From 68d4a855758cb2c19d7ed6259effb833587473d0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 18 Mar 2019 11:39:15 +0100 Subject: [PATCH 78/88] Properly read and clean value types read from storage. --- libsolidity/codegen/ABIFunctions.cpp | 86 +++++++++++++++++++++++++- libsolidity/codegen/ABIFunctions.h | 22 +++++++ libsolidity/codegen/YulUtilFunctions.h | 1 + 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index eb73fcb9e7ba..ef6148ec0ac0 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -346,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.category() == Type::Category::Function || _type.category() == Type::Category::FixedBytes) + 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 = @@ -778,6 +811,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( subOptions.encodeFunctionFromStack = false; subOptions.padded = true; templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); + // There is only one element per slot, so sload suffices, no need to use "readFromStorage". templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); return templ.render(); @@ -900,7 +934,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( { let data := sload(srcPtr) <#items> - ((data), pos) + ((data), pos) pos := add(pos, ) srcPtr := add(srcPtr, 1) @@ -934,7 +968,7 @@ 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); + items[i]["extractFromSlot"] = extractFromStorageValue(*_from.baseType(), i * storageBytes, false); templ("items", items); return templ.render(); } @@ -1020,7 +1054,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 { @@ -1509,6 +1543,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(); diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index c9c19f80fbfc..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. 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(); From 3039456f48a1cb4b11778d8e526ead6075897bc4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 18 Mar 2019 12:30:06 +0100 Subject: [PATCH 79/88] Tests for encoder from storage bug. --- test/libsolidity/ABIEncoderTests.cpp | 102 +++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index be461d641685..dd79dfdc4ff5 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -510,6 +510,108 @@ 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(bytes3_arrays) +{ + string sourceCode = R"( + contract C { + bytes3[] x; + bytes3[4] y; + event E(bytes3[], bytes3[4]); + function f() public returns (bytes3[] memory, bytes3[4] memory) { + x.length = 4; + x[0] = 0x010203; + x[1] = 0x00; + x[2] = 0x040506; + x[3] = 0x00; + y[0] = 0x00; + y[1] = 0x010203; + y[2] = 0x00; + y[3] = 0x040506; + emit E(x, y); + return (x, y); // this copies to memory first + } + } + )"; + + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + bytes encoded = encodeArgs( + 0xa0, 0, "\x01\x02\x03", 0, "\x04\x05\x06", + 4, "\x01\x02\x03", 0, "\x04\x05\x06", 0 + ); + 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() } From 6b69c31703189995d726bf48f3a49d3d857e31fc Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 19 Mar 2019 16:13:16 +0100 Subject: [PATCH 80/88] Fix ABIEncoderV2 array overwrite bug. --- libsolidity/ast/Types.cpp | 8 ++++ libsolidity/ast/Types.h | 15 +++++++ libsolidity/codegen/ABIFunctions.cpp | 62 +++++++++++++++++++++++----- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 1d592489dd5c..ae2cb2966edf 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2842,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) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 86ae52ff498f..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); } @@ -604,6 +615,7 @@ 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"; } @@ -775,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(); } @@ -897,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; @@ -1096,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; } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index ef6148ec0ac0..47475ecedc1f 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -370,7 +370,7 @@ string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFu if (storageBytes == 32) templ("body", "cleaned := value"); - else if (_type.category() == Type::Category::Function || _type.category() == Type::Category::FixedBytes) + 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) + ")"); @@ -811,8 +811,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( subOptions.encodeFunctionFromStack = false; subOptions.padded = true; templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); - // There is only one element per slot, so sload suffices, no need to use "readFromStorage". - 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(); }); @@ -920,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"( // -> @@ -930,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, )) } )" @@ -952,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()); @@ -968,7 +1000,15 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("encodeToMemoryFun", encodeToMemoryFun); std::vector> items(itemsPerSlot); for (size_t i = 0; i < itemsPerSlot; ++i) + { + 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(); } From 6b9c44d5f9afe3b156557f33ac9ccad09dad996d Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 19 Mar 2019 17:23:10 +0100 Subject: [PATCH 81/88] Tests for arrays. --- test/libsolidity/ABIEncoderTests.cpp | 140 +++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 20 deletions(-) diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index dd79dfdc4ff5..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; @@ -544,23 +548,25 @@ BOOST_AUTO_TEST_CASE(bool_arrays) ) } -BOOST_AUTO_TEST_CASE(bytes3_arrays) +BOOST_AUTO_TEST_CASE(bool_arrays_split) { string sourceCode = R"( contract C { - bytes3[] x; - bytes3[4] y; - event E(bytes3[], bytes3[4]); - function f() public returns (bytes3[] memory, bytes3[4] memory) { + bool[] x; + bool[4] y; + event E(bool[], bool[4]); + function store() public { x.length = 4; - x[0] = 0x010203; - x[1] = 0x00; - x[2] = 0x040506; - x[3] = 0x00; - y[0] = 0x00; - y[1] = 0x010203; - y[2] = 0x00; - y[3] = 0x040506; + 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 } @@ -570,14 +576,108 @@ BOOST_AUTO_TEST_CASE(bytes3_arrays) BOTH_ENCODERS( compileAndRun(sourceCode, 0, "C"); bytes encoded = encodeArgs( - 0xa0, 0, "\x01\x02\x03", 0, "\x04\x05\x06", - 4, "\x01\x02\x03", 0, "\x04\x05\x06", 0 + 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"( From e0268a065842be6516d3f1d318cbd5289ad49dc3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 22 Mar 2019 13:45:22 +0100 Subject: [PATCH 82/88] Changelog entry. --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 4fcf6e595940..9f8568ead117 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,8 @@ ### 0.5.7 (unreleased) Important Bugfixes: + * ABIEncoderV2: Fix bugs related to loading short value types from storage when encoding a packed 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: From 3c9af6716eaa1614e826423686cc9cda505e854e Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 22 Mar 2019 14:07:39 +0100 Subject: [PATCH 83/88] Bug list entry. --- docs/bugs.json | 11 +++++++++++ docs/bugs_by_version.json | 32 ++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/bugs.json b/docs/bugs.json index 0d8a6484940b..55214f7d5f6c 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,15 @@ [ + { + "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.", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index b0fe056d6e7e..87bd1310b4c8 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,27 +618,38 @@ "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" ], @@ -638,6 +657,7 @@ }, "0.5.6": { "bugs": [ + "ABIEncoderV2PackedStorage", "IncorrectByteInstructionOptimization" ], "released": "2019-03-13" From 0432401e20c5873eb3ef8389d3849f6eddb19d0a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 25 Mar 2019 13:56:12 +0000 Subject: [PATCH 84/88] Emit warning when using the Yul optimiser --- libsolidity/interface/CompilerStack.cpp | 6 ++++++ .../gas_test_abiv2_optimize_yul/err | 1 + .../standard_optimizer_yul/input.json | 17 +++++++++++++++++ .../standard_optimizer_yul/output.json | 1 + .../standard_optimizer_yulDetails/input.json | 16 ++++++++++++++++ .../standard_optimizer_yulDetails/output.json | 1 + test/libsolidity/StandardCompiler.cpp | 6 ++++++ 7 files changed, 48 insertions(+) create mode 100644 test/cmdlineTests/standard_optimizer_yul/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yul/output.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails/output.json diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 8924a8f28066..81619acb76e2 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -179,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); 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/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/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 707ed65e68a0..4b837d6e7aef 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -996,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()); From e211b5b1e1091c7f440a9dc26387038b0c27c9f2 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 25 Mar 2019 13:41:37 +0000 Subject: [PATCH 85/88] Add warning that Yul is still experimental --- libsolidity/interface/StandardCompiler.cpp | 4 ++++ solc/CommandLineInterface.cpp | 4 ++++ test/cmdlineTests/object_compiler/err | 1 + test/cmdlineTests/standard_yul/output.json | 2 +- .../standard_yul_embedded_object_name/output.json | 2 +- .../cmdlineTests/standard_yul_invalid_object_name/output.json | 2 +- test/cmdlineTests/standard_yul_object/output.json | 2 +- test/cmdlineTests/standard_yul_object_name/output.json | 2 +- test/cmdlineTests/standard_yul_optimized/output.json | 2 +- test/cmdlineTests/strict_asm_jump/err | 1 + test/cmdlineTests/yul_stack_opt/err | 1 + test/cmdlineTests/yul_stack_opt_disabled/err | 1 + 12 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 test/cmdlineTests/yul_stack_opt/err diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 43b5aeae2430..c1f7067901d0 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -891,6 +891,10 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) 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")) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 4b56db6f163f..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)) 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/standard_yul/output.json b/test/cmdlineTests/standard_yul/output.json index 1724d333e215..7ea2df486405 100644 --- a/test/cmdlineTests/standard_yul/output.json +++ b/test/cmdlineTests/standard_yul/output.json @@ -1 +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"}}}} +{"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/output.json b/test/cmdlineTests/standard_yul_embedded_object_name/output.json index 0967ef424bce..32a2a647a030 100644 --- a/test/cmdlineTests/standard_yul_embedded_object_name/output.json +++ b/test/cmdlineTests/standard_yul_embedded_object_name/output.json @@ -1 +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/output.json b/test/cmdlineTests/standard_yul_invalid_object_name/output.json index 0967ef424bce..32a2a647a030 100644 --- a/test/cmdlineTests/standard_yul_invalid_object_name/output.json +++ b/test/cmdlineTests/standard_yul_invalid_object_name/output.json @@ -1 +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_object/output.json b/test/cmdlineTests/standard_yul_object/output.json index 5e45df9b7b05..77dcb9678dce 100644 --- a/test/cmdlineTests/standard_yul_object/output.json +++ b/test/cmdlineTests/standard_yul_object/output.json @@ -1 +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"}}}} +{"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/output.json b/test/cmdlineTests/standard_yul_object_name/output.json index af29f34a1e56..16644297f461 100644 --- a/test/cmdlineTests/standard_yul_object_name/output.json +++ b/test/cmdlineTests/standard_yul_object_name/output.json @@ -1 +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"}}}} +{"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/output.json b/test/cmdlineTests/standard_yul_optimized/output.json index 5eea1cb16677..80b764ece5bb 100644 --- a/test/cmdlineTests/standard_yul_optimized/output.json +++ b/test/cmdlineTests/standard_yul_optimized/output.json @@ -1 +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"}}}} +{"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/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_disabled/err b/test/cmdlineTests/yul_stack_opt_disabled/err index 1093ea3ae56f..392200019439 100644 --- a/test/cmdlineTests/yul_stack_opt_disabled/err +++ b/test/cmdlineTests/yul_stack_opt_disabled/err @@ -1,3 +1,4 @@ +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. From d2185bf529c03395ea1c2a40d2f755d4f2fc1d64 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 22 Mar 2019 03:31:11 +0000 Subject: [PATCH 86/88] Remove obsolete identifer for the identity precompile --- libsolidity/codegen/CompilerUtils.cpp | 1 - libsolidity/codegen/CompilerUtils.h | 3 --- 2 files changed, 4 deletions(-) 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); From 79d7fb14e70542b82cbf35ef773e7a2c4b51e188 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 26 Mar 2019 10:25:17 +0100 Subject: [PATCH 87/88] Preparation for 0.5.7 release. --- Changelog.md | 9 +++++---- docs/bugs_by_version.json | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 9f8568ead117..f9755997ed5a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,20 +1,21 @@ -### 0.5.7 (unreleased) +### 0.5.7 (2019-03-26) Important Bugfixes: - * ABIEncoderV2: Fix bugs related to loading short value types from storage when encoding a packed array or struct from storage. + * 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: - * Function calls with named arguments now work with overloaded functions. * 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). + * Yul Optimizer: Enable stack allocation optimization by default if Yul optimizer is active (disable in ``yulDetails``). Bugfixes: diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 87bd1310b4c8..fd2ad4b5e3cc 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -661,5 +661,9 @@ "IncorrectByteInstructionOptimization" ], "released": "2019-03-13" + }, + "0.5.7": { + "bugs": [], + "released": "2019-03-26" } } \ No newline at end of file From bfe3f378b28140477aa70b09fc1772d8f2c59dc7 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 26 Mar 2019 11:32:08 +0100 Subject: [PATCH 88/88] Fix throw in error output --- test/RPCSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 4edf0c218b37..deb64dd80d14 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -351,7 +351,7 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector const& } if (!result.isMember("result") || result["result"].isNull()) - BOOST_FAIL("Missing result for JSON-RPC call: " + result.asString()); + BOOST_FAIL("Missing result for JSON-RPC call: " + result.toStyledString()); return result["result"]; }