From 4061ad0a7bc254e3870951592047e1f0b83b9429 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 27 May 2019 20:27:06 +0200 Subject: [PATCH 001/115] Implement references using InvertibleRelation as data structure. --- libdevcore/CMakeLists.txt | 1 + libdevcore/InvertibleMap.h | 51 +++++++++++++++++++ libyul/optimiser/DataFlowAnalyzer.cpp | 23 +++------ libyul/optimiser/DataFlowAnalyzer.h | 9 ++-- libyul/optimiser/Rematerialiser.cpp | 2 +- libyul/optimiser/StackCompressor.cpp | 2 +- .../clear_not_needed.yul | 21 ++++++++ 7 files changed, 86 insertions(+), 23 deletions(-) create mode 100644 libdevcore/InvertibleMap.h create mode 100644 test/libyul/yulOptimizerTests/commonSubexpressionEliminator/clear_not_needed.yul diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index b7bf917f8..50cbb6a95 100644 --- a/libdevcore/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -12,6 +12,7 @@ set(sources FixedHash.h IndentedWriter.cpp IndentedWriter.h + InvertibleMap.h IpfsHash.cpp IpfsHash.h JSON.cpp diff --git a/libdevcore/InvertibleMap.h b/libdevcore/InvertibleMap.h new file mode 100644 index 000000000..9c67119a1 --- /dev/null +++ b/libdevcore/InvertibleMap.h @@ -0,0 +1,51 @@ +/* + 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 . +*/ + +#pragma once + +#include +#include + +template +struct InvertibleRelation +{ + /// forward[x] contains y <=> backward[y] contains x + std::map> forward; + std::map> backward; + + void insert(T _key, T _value) + { + forward[_key].insert(_value); + backward[_value].insert(_key); + } + + void set(T _key, std::set _values) + { + for (T v: forward[_key]) + backward[v].erase(_key); + for (T v: _values) + backward[v].insert(_key); + forward[_key] = std::move(_values); + } + + void eraseKey(T _key) + { + for (auto const& v: forward[_key]) + backward[v].erase(_key); + forward.erase(_key); + } +}; diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 86011ffd1..7a7e3f963 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -88,11 +88,9 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) // Save all information. We might rather reinstantiate this class, // but this could be difficult if it is subclassed. map value; - map> references; - map> referencedBy; + InvertibleRelation references; m_value.swap(value); - m_references.swap(references); - m_referencedBy.swap(referencedBy); + swap(m_references, references); pushScope(true); for (auto const& parameter: _fun.parameters) @@ -106,8 +104,7 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) popScope(); m_value.swap(value); - m_references.swap(references); - m_referencedBy.swap(referencedBy); + swap(m_references, references); } void DataFlowAnalyzer::operator()(ForLoop& _for) @@ -163,11 +160,7 @@ void DataFlowAnalyzer::handleAssignment(set const& _variables, Expres auto const& referencedVariables = movableChecker.referencedVariables(); for (auto const& name: _variables) - { - m_references[name] = referencedVariables; - for (auto const& ref: referencedVariables) - m_referencedBy[ref].emplace(name); - } + m_references.set(name, referencedVariables); } void DataFlowAnalyzer::pushScope(bool _functionScope) @@ -198,18 +191,14 @@ void DataFlowAnalyzer::clearValues(set _variables) // Clear variables that reference variables to be cleared. for (auto const& name: _variables) - for (auto const& ref: m_referencedBy[name]) + for (auto const& ref: m_references.backward[name]) _variables.emplace(ref); // Clear the value and update the reference relation. for (auto const& name: _variables) m_value.erase(name); for (auto const& name: _variables) - { - for (auto const& ref: m_references[name]) - m_referencedBy[ref].erase(name); - m_references[name].clear(); - } + m_references.eraseKey(name); } bool DataFlowAnalyzer::inScope(YulString _variableName) const diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 100a38665..56c70f2f5 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -74,10 +76,9 @@ protected: /// Current values of variables, always movable. std::map m_value; - /// m_references[a].contains(b) <=> the current expression assigned to a references b - std::map> m_references; - /// m_referencedBy[b].contains(a) <=> the current expression assigned to a references b - std::map> m_referencedBy; + /// m_references.forward[a].contains(b) <=> the current expression assigned to a references b + /// m_references.backward[b].contains(a) <=> the current expression assigned to a references b + InvertibleRelation m_references; struct Scope { diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index c70b02423..e11ed98f3 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -81,7 +81,7 @@ void Rematerialiser::visit(Expression& _e) if (refs <= 1 || cost == 0 || (refs <= 5 && cost <= 1) || m_varsToAlwaysRematerialize.count(name)) { assertThrow(m_referenceCounts[name] > 0, OptimizerException, ""); - for (auto const& ref: m_references[name]) + for (auto const& ref: m_references.forward[name]) assertThrow(inScope(ref), OptimizerException, ""); // update reference counts m_referenceCounts[name]--; diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 6d35cee04..bb47b8bda 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -57,7 +57,7 @@ public: for (auto const& codeCost: m_expressionCodeCost) { size_t numRef = m_numReferences[codeCost.first]; - cand.emplace(make_tuple(codeCost.second * numRef, codeCost.first, m_references[codeCost.first])); + cand.emplace(make_tuple(codeCost.second * numRef, codeCost.first, m_references.forward[codeCost.first])); } return cand; } diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/clear_not_needed.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/clear_not_needed.yul new file mode 100644 index 000000000..89075e3bb --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/clear_not_needed.yul @@ -0,0 +1,21 @@ +{ + let a := calldataload(0) + let x := calldataload(0x20) + x := a + let z := 0 + x := z + a := 9 + sstore(x, 3) +} +// ==== +// step: commonSubexpressionEliminator +// ---- +// { +// let a := calldataload(0) +// let x := calldataload(0x20) +// x := a +// let z := 0 +// x := z +// a := 9 +// sstore(z, 3) +// } From 931b93146fa8655fcd282cce7365f87bb0c48bed Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Tue, 23 Apr 2019 16:40:43 +1000 Subject: [PATCH 002/115] Update subcurrency example in introductory section Further updates Add line breaks Changes from review Updates from review Changes from review Fix label --- docs/introduction-to-smart-contracts.rst | 126 +++++++++++------------ docs/types/value-types.rst | 1 + docs/units-and-global-variables.rst | 2 + 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 7d99e94a6..7aa51d070 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -71,40 +71,41 @@ so that only you can alter the number. Subcurrency Example =================== -The following contract will implement the simplest form of a -cryptocurrency. It is possible to generate coins out of thin air, but -only the person that created the contract will be able to do that (it is easy -to implement a different issuance scheme). -Furthermore, anyone can send coins to each other without a need for -registering with username and password — all you need is an Ethereum keypair. - +The following contract implements the simplest form of a +cryptocurrency. The contract allows only its creator to create new coins (different issuance scheme are possible). +Anyone can send coins to each other without a need for +registering with a username and password, all you need is an Ethereum keypair. :: pragma solidity >=0.5.0 <0.7.0; contract Coin { - // The keyword "public" makes those variables - // easily readable from outside. + // The keyword "public" makes variables + // accessible from other contracts address public minter; mapping (address => uint) public balances; - // Events allow light clients to react to - // changes efficiently. + // Events allow clients to react to specific + // contract changes you declare event Sent(address from, address to, uint amount); - // This is the constructor whose code is - // run only when the contract is created. + // Constructor code is only run when the contract + // is created constructor() public { minter = msg.sender; } + // Sends an amount of newly created coins to an address + // Can only be called by the contract creator function mint(address receiver, uint amount) public { require(msg.sender == minter); require(amount < 1e60); balances[receiver] += amount; } + // Sends an amount of existing coins + // from any caller to an address function send(address receiver, uint amount) public { require(amount <= balances[msg.sender], "Insufficient balance."); balances[msg.sender] -= amount; @@ -115,58 +116,56 @@ registering with username and password — all you need is an Ethereum keypair. This contract introduces some new concepts, let us go through them one by one. -The line ``address public minter;`` declares a state variable of type address -that is publicly accessible. The ``address`` type is a 160-bit value -that does not allow any arithmetic operations. It is suitable for -storing addresses of contracts or of keypairs belonging to external -persons. The keyword ``public`` automatically generates a function that -allows you to access the current value of the state variable -from outside of the contract. -Without this keyword, other contracts have no way to access the variable. -The code of the function generated by the compiler is roughly equivalent +The line ``address public minter;`` declares a state variable of type :ref:`address
`. +The ``address`` type is a 160-bit value that does not allow any arithmetic operations. +It is suitable for storing addresses of contracts, or a hash of the public half of a keypair belonging to :ref:`external accounts`. + +The keyword ``public`` automatically generates a function that allows you to access the current value of the state +variable from outside of the contract. Without this keyword, other contracts have no way to access the variable. +The code of the function generated by the compiler is equivalent to the following (ignore ``external`` and ``view`` for now):: function minter() external view returns (address) { return minter; } -Of course, adding a function exactly like that will not work -because we would have a -function and a state variable with the same name, but hopefully, you -get the idea - the compiler figures that out for you. +You could add a function like the above yourself, but you would have a function and state variable with the same name. +You do not need to do this, the compiler figures it out for you. .. index:: mapping The next line, ``mapping (address => uint) public balances;`` also creates a public state variable, but it is a more complex datatype. -The type maps addresses to unsigned integers. +The :ref:`mapping ` type maps addresses to :ref:`unsigned integers `. + Mappings can be seen as `hash tables `_ which are -virtually initialized such that every possible key exists from the start and is mapped to a -value whose byte-representation is all zeros. This analogy does not go -too far, though, as it is neither possible to obtain a list of all keys of -a mapping, nor a list of all values. So either keep in mind (or -better, keep a list or use a more advanced data type) what you -added to the mapping or use it in a context where this is not needed. +virtually initialised such that every possible key exists from the start and is mapped to a +value whose byte-representation is all zeros. However, it is neither possible to obtain a list of all keys of +a mapping, nor a list of all values. Record what you +added to the mapping, or use it in a context where this is not needed. Or +even better, keep a list, or use a more suitable data type. + The :ref:`getter function` created by the ``public`` keyword -is a bit more complex in this case. It roughly looks like the +is more complex in the case of a mapping. It looks like the following:: function balances(address _account) external view returns (uint) { return balances[_account]; } -As you see, you can use this function to easily query the balance of a -single account. +You can use this function to query the balance of a single account. .. index:: event The line ``event Sent(address from, address to, uint amount);`` declares -a so-called "event" which is emitted in the last line of the function -``send``. User interfaces (as well as server applications of course) can -listen for those events being emitted on the blockchain without much -cost. As soon as it is emitted, the listener will also receive the -arguments ``from``, ``to`` and ``amount``, which makes it easy to track -transactions. In order to listen for this event, you would use the following -JavaScript code (which assumes that ``Coin`` is a contract object created via -web3.js or a similar module):: +an :ref:`"event" `, which is emitted in the last line of the function +``send``. Ethereum clients such as web applications can +listen for these events emitted on the blockchain without much +cost. As soon as it is emitted, the listener receives the +arguments ``from``, ``to`` and ``amount``, which makes it possible to track +transactions. + +To listen for this event, you could use the following +JavaScript code, which uses `web3.js `_ to create the ``Coin`` contract object, +and any user interface calls the automatically generated ``balances`` function from aboves:: Coin.Sent().watch({}, '', function(error, result) { if (!error) { @@ -179,36 +178,33 @@ web3.js or a similar module):: } }) -Note how the automatically generated function ``balances`` is called from -the user interface. - .. index:: coin -The constructor is a special function which is run during creation of the contract and -cannot be called afterwards. It permanently stores the address of the person creating the -contract: ``msg`` (together with ``tx`` and ``block``) is a special global variable that -contains some properties which allow access to the blockchain. ``msg.sender`` is +The :ref:`constructor` is a special function run during the creation of the contract and +cannot be called afterwards. In this case, it permanently stores the address of the person creating the +contract. The ``msg`` variable (together with ``tx`` and ``block``) is a +:ref:`special global variable ` that +contains properties which allow access to the blockchain. ``msg.sender`` is always the address where the current (external) function call came from. -Finally, the functions that will actually end up with the contract and can be called -by users and contracts alike are ``mint`` and ``send``. -If ``mint`` is called by anyone except the account that created the contract, -nothing will happen. This is ensured by the special function ``require`` which -causes all changes to be reverted if its argument evaluates to false. -The second call to ``require`` ensures that there will not be too many coins, -which could cause overflow errors later. +The functions that make up the contract, and that users and contracts can call are ``mint`` and ``send``. -On the other hand, ``send`` can be used by anyone (who already -has some of these coins) to send coins to anyone else. If you do not have -enough coins to send, the ``require`` call will fail and also provide the -user with an appropriate error message string. +The ``mint`` function sends an amount of newly created coins to another address. +The :ref:`require ` function call defines conditions that reverts all changes if not met. +In this example, ``require(msg.sender == minter);`` ensures that only the creator of the contract can call ``mint``, +and ``require(amount < 1e60);`` ensures a maximum amount of tokens, without which could cause overflow errors in the future. + +The ``send`` function can be used by anyone (who already +has some of these coins) to send coins to anyone else. If the sender does not have +enough coins to send, the ``require`` call fails and provides the +sender with an appropriate error message string. .. note:: If you use this contract to send coins to an address, you will not see anything when you - look at that address on a blockchain explorer, because the fact that you sent + look at that address on a blockchain explorer, because the record that you sent coins and the changed balances are only stored in the data storage of this - particular coin contract. By the use of events it is relatively easy to create + particular coin contract. By using events, you can create a "blockchain explorer" that tracks transactions and balances of your new coin, but you have to inspect the coin contract address and not the addresses of the coin owners. @@ -302,6 +298,8 @@ Smart contracts even have limited access to other smart contracts. .. index:: ! account, address, storage, balance +.. _accounts: + Accounts ======== diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 9c082f9b4..4451ac01f 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -26,6 +26,7 @@ Operators: The operators ``||`` and ``&&`` apply the common short-circuiting rules. This means that in the expression ``f(x) || g(y)``, if ``f(x)`` evaluates to ``true``, ``g(y)`` will not be evaluated even if it may have side-effects. .. index:: ! uint, ! int, ! integer +.. _integers: Integers -------- diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index befb97dbb..c797187be 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -52,6 +52,8 @@ interpret a function parameter in days, you can in the following way:: } } +.. _special-variables-functions: + Special Variables and Functions =============================== From feab4065c3354bf20305a605cdcb77c3efb66cca Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 28 May 2019 17:31:06 +0200 Subject: [PATCH 003/115] Set version to 0.5.10 --- CMakeLists.txt | 2 +- Changelog.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bfe35fcf5..e8d729f15 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.9") +set(PROJECT_VERSION "0.5.10") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) option(LLL "Build LLL" OFF) diff --git a/Changelog.md b/Changelog.md index ac635537d..32f6e6e4b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,17 @@ +### 0.5.10 (unreleased) + +Language Features: + + + +Compiler Features: + + + +Bugfixes: + + + ### 0.5.9 (2019-05-28) Language Features: From f57439035ae178005486b61eda78d53d4b223ea9 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Wed, 29 May 2019 09:46:34 +0200 Subject: [PATCH 004/115] Do not enclose string literal within double quotes before conversion to u256 --- test/tools/ossfuzz/protoToYul.cpp | 32 +++++++++++++------------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index c4a137088..6582d6b4e 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -55,36 +55,30 @@ string ProtoConverter::createAlphaNum(string const& _strBytes) const bool ProtoConverter::isCaseLiteralUnique(Literal const& _x) { - std::string tmp; + dev::u256 mpCaseLiteralValue; bool isUnique = false; - bool isEmptyString = false; + switch (_x.literal_oneof_case()) { case Literal::kIntval: - tmp = std::to_string(_x.intval()); + mpCaseLiteralValue = dev::u256(_x.intval()); break; case Literal::kHexval: - tmp = "0x" + createHex(_x.hexval()); + // We need to ask boost mp library to treat this + // as a hex value. Hence the "0x" prefix. + mpCaseLiteralValue = dev::u256("0x" + createHex(_x.hexval())); break; case Literal::kStrval: - tmp = createAlphaNum(_x.strval()); - if (tmp.empty()) - { - isEmptyString = true; - tmp = std::to_string(0); - } - else - tmp = "\"" + tmp + "\""; + mpCaseLiteralValue = dev::u256(dev::h256(createAlphaNum(_x.strval()), dev::h256::FromBinary, dev::h256::AlignLeft)); break; case Literal::LITERAL_ONEOF_NOT_SET: - tmp = std::to_string(1); + // If the proto generator does not generate a valid Literal + // we generate a case 1: + mpCaseLiteralValue = 1; break; } - if (!_x.has_strval() || isEmptyString) - isUnique = m_switchLiteralSetPerScope.top().insert(dev::u256(tmp)).second; - else - isUnique = m_switchLiteralSetPerScope.top().insert( - dev::u256(dev::h256(tmp, dev::h256::FromBinary, dev::h256::AlignLeft))).second; + + isUnique = m_switchLiteralSetPerScope.top().insert(mpCaseLiteralValue).second; return isUnique; } @@ -993,4 +987,4 @@ std::string ProtoConverter::functionTypeToString(NumFunctionReturns _type) case NumFunctionReturns::Multiple: return "multireturn"; } -} \ No newline at end of file +} From b015d2cd408a292089bb123ac3d300cddcab9f2d Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Wed, 29 May 2019 14:35:48 +0200 Subject: [PATCH 005/115] Bring code examples inline with style guide Remove visibility --- docs/contracts/using-for.rst | 54 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst index 12fc976d5..ed19dd046 100644 --- a/docs/contracts/using-for.rst +++ b/docs/contracts/using-for.rst @@ -33,39 +33,41 @@ Let us rewrite the set example from the pragma solidity >=0.4.16 <0.7.0; + // This is the same code as before, just without comments library Set { - struct Data { mapping(uint => bool) flags; } + struct Data { mapping(uint => bool) flags; } - function insert(Data storage self, uint value) - public - returns (bool) - { - if (self.flags[value]) - return false; // already there - self.flags[value] = true; - return true; - } + function insert(Data storage self, uint value) + public + returns (bool) + { + if (self.flags[value]) + return false; // already there + self.flags[value] = true; + return true; + } - function remove(Data storage self, uint value) - public - returns (bool) - { - if (!self.flags[value]) - return false; // not there - self.flags[value] = false; - return true; - } + function remove(Data storage self, uint value) + public + returns (bool) + { + if (!self.flags[value]) + return false; // not there + self.flags[value] = false; + return true; + } - function contains(Data storage self, uint value) - public - view - returns (bool) - { - return self.flags[value]; - } + function contains(Data storage self, uint value) + public + view + returns (bool) + { + return self.flags[value]; + } } + contract C { using Set for Set.Data; // this is the crucial change Set.Data knownValues; From bebc479fb52a572998966a94c3b893f570155778 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Wed, 29 May 2019 14:43:23 +0200 Subject: [PATCH 006/115] Update code examples to match style guide Remove visibility --- docs/control-structures.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index c8be772f4..2cc0b9a05 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -271,19 +271,20 @@ because only a reference and not a copy is passed. pragma solidity >=0.4.16 <0.7.0; - contract C { + + contract C { uint[20] x; - function f() public { + function f() public { g(x); h(x); } - function g(uint[20] memory y) internal pure { + function g(uint[20] memory y) internal pure { y[2] = 3; } - function h(uint[20] storage y) internal { + function h(uint[20] storage y) internal { y[3] = 4; } } @@ -354,7 +355,7 @@ In any case, you will get a warning about the outer variable being shadowed. for the entire function, regardless where it was declared. The following example shows a code snippet that used to compile but leads to an error starting from version 0.5.0. - :: +:: pragma solidity >=0.5.0 <0.7.0; // This will not compile From 66fe9731fc3922edd670f004fb2fff0e04c7e8f6 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Wed, 29 May 2019 16:26:23 +0200 Subject: [PATCH 007/115] Bring code examples in line with style guide --- docs/miscellaneous.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index ff80b01ae..22eac11f6 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -64,10 +64,11 @@ So for the following contract snippet:: pragma solidity >=0.4.0 <0.7.0; + contract C { - struct s { uint a; uint b; } - uint x; - mapping(uint => mapping(uint => s)) data; + struct S { uint a; uint b; } + uint x; + mapping(uint => mapping(uint => S)) data; } The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``. From f1be1b27585fcab827092085258337f8f43886d2 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 29 May 2019 21:02:03 -0400 Subject: [PATCH 008/115] Learn how to spell section --- docs/style-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 7bc75a306..10cbffb67 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -1126,4 +1126,4 @@ added looks like the one below:: It is recommended that Solidity contracts are fully annontated using `NatSpec `_ for all public interfaces (everything in the ABI). -Please see the sectian about `NatSpec `_ for a detailed explanation. \ No newline at end of file +Please see the section about `NatSpec `_ for a detailed explanation. From 69e4e69e69d3626ad417bf2e5d42024e8933b013 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Thu, 30 May 2019 14:37:17 +0200 Subject: [PATCH 009/115] Bring value types code examples inline with style guide --- docs/types/value-types.rst | 156 ++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 9c082f9b4..2f1cfd185 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -624,100 +624,116 @@ Example that shows how to use the members:: pragma solidity >=0.4.16 <0.7.0; + contract Example { - function f() public payable returns (bytes4) { - return this.f.selector; - } - function g() public { - this.f.gas(10).value(800)(); - } + function f() public payable returns (bytes4) { + return this.f.selector; + } + + function g() public { + this.f.gas(10).value(800)(); + } } Example that shows how to use internal function types:: pragma solidity >=0.4.16 <0.7.0; + library ArrayUtils { - // internal functions can be used in internal library functions because - // they will be part of the same code context - function map(uint[] memory self, function (uint) pure returns (uint) f) - internal - pure - returns (uint[] memory r) - { - r = new uint[](self.length); - for (uint i = 0; i < self.length; i++) { - r[i] = f(self[i]); + // internal functions can be used in internal library functions because + // they will be part of the same code context + function map(uint[] memory self, function (uint) pure returns (uint) f) + internal + pure + returns (uint[] memory r) + { + r = new uint[](self.length); + for (uint i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } } - } - function reduce( - uint[] memory self, - function (uint, uint) pure returns (uint) f - ) - internal - pure - returns (uint r) - { - r = self[0]; - for (uint i = 1; i < self.length; i++) { - r = f(r, self[i]); + + function reduce( + uint[] memory self, + function (uint, uint) pure returns (uint) f + ) + internal + pure + returns (uint r) + { + r = self[0]; + for (uint i = 1; i < self.length; i++) { + r = f(r, self[i]); + } } - } - function range(uint length) internal pure returns (uint[] memory r) { - r = new uint[](length); - for (uint i = 0; i < r.length; i++) { - r[i] = i; + + function range(uint length) internal pure returns (uint[] memory r) { + r = new uint[](length); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } } - } } + contract Pyramid { - using ArrayUtils for *; - function pyramid(uint l) public pure returns (uint) { - return ArrayUtils.range(l).map(square).reduce(sum); - } - function square(uint x) internal pure returns (uint) { - return x * x; - } - function sum(uint x, uint y) internal pure returns (uint) { - return x + y; - } + using ArrayUtils for *; + + function pyramid(uint l) public pure returns (uint) { + return ArrayUtils.range(l).map(square).reduce(sum); + } + + function square(uint x) internal pure returns (uint) { + return x * x; + } + + function sum(uint x, uint y) internal pure returns (uint) { + return x + y; + } } Another example that uses external function types:: pragma solidity >=0.4.22 <0.7.0; + contract Oracle { - struct Request { - bytes data; - function(uint) external callback; - } - Request[] requests; - event NewRequest(uint); - function query(bytes memory data, function(uint) external callback) public { - requests.push(Request(data, callback)); - emit NewRequest(requests.length - 1); - } - function reply(uint requestID, uint response) public { - // Here goes the check that the reply comes from a trusted source - requests[requestID].callback(response); - } + struct Request { + bytes data; + function(uint) external callback; + } + + Request[] private requests; + event NewRequest(uint); + + function query(bytes memory data, function(uint) external callback) public { + requests.push(Request(data, callback)); + emit NewRequest(requests.length - 1); + } + + function reply(uint requestID, uint response) public { + // Here goes the check that the reply comes from a trusted source + requests[requestID].callback(response); + } } + contract OracleUser { - Oracle constant oracle = Oracle(0x1234567); // known contract - uint exchangeRate; - function buySomething() public { - oracle.query("USD", this.oracleResponse); - } - function oracleResponse(uint response) public { - require( - msg.sender == address(oracle), - "Only oracle can call this." - ); - exchangeRate = response; - } + Oracle constant private ORACLE_CONST = Oracle(0x1234567); // known contract + uint private exchangeRate; + + function buySomething() public { + ORACLE_CONST.query("USD", this.oracleResponse); + } + + function oracleResponse(uint response) public { + require( + msg.sender == address(ORACLE_CONST), + "Only oracle can call this." + ); + exchangeRate = response; + } } .. note:: From 3d4015210293b901656a820e6dd3fcdfda28d405 Mon Sep 17 00:00:00 2001 From: Vignesh Karthikeyan Date: Tue, 4 Jun 2019 04:16:06 +0530 Subject: [PATCH 010/115] Brought warning to high priority Update functions.rst Update for warning over note priority Updated units-and-global-variables.rst Updated file for warning over note priority Updated control-structures.rst Updated priority of warning over note Updated assembly.rst Updated priority of warning over code Updated introduction-to-smart-contracts.rst Updated priority of warning over note Update installing-solidity.rst Corrected control-structures.rst Corrected white space error Corrected whitespace Corrected whitespace introduction to smart contract --- docs/assembly.rst | 20 ++++---- docs/contracts/functions.rst | 8 ++-- docs/control-structures.rst | 60 ++++++++++++------------ docs/installing-solidity.rst | 8 ++-- docs/introduction-to-smart-contracts.rst | 14 +++--- docs/types/value-types.rst | 5 +- docs/units-and-global-variables.rst | 20 ++++---- 7 files changed, 68 insertions(+), 67 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 355286d49..5b475a9b1 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -394,6 +394,16 @@ use ``x_slot``, and to retrieve the byte-offset you use ``x_offset``. Local Solidity variables are available for assignments, for example: +.. warning:: + If you access variables of a type that spans less than 256 bits + (for example ``uint64``, ``address``, ``bytes16`` or ``byte``), + you cannot make any assumptions about bits not part of the + encoding of the type. Especially, do not assume them to be zero. + To be safe, always clear the data properly before you use it + in a context where this is important: + ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` + To clean signed types, you can use the ``signextend`` opcode. + .. code:: pragma solidity >=0.4.11 <0.7.0; @@ -407,16 +417,6 @@ Local Solidity variables are available for assignments, for example: } } -.. warning:: - If you access variables of a type that spans less than 256 bits - (for example ``uint64``, ``address``, ``bytes16`` or ``byte``), - you cannot make any assumptions about bits not part of the - encoding of the type. Especially, do not assume them to be zero. - To be safe, always clear the data properly before you use it - in a context where this is important: - ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` - To clean signed types, you can use the ``signextend`` opcode. - Labels ------ diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index 4e5a52c0f..e8d9961e7 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -252,10 +252,6 @@ will consume more gas than the 2300 gas stipend: Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. -.. note:: - Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve - any payload supplied with the call. - .. warning:: The fallback function is also executed if the caller meant to call a function that is not available. If you want to implement the fallback @@ -273,6 +269,10 @@ Like any function, the fallback function can execute complex operations as long A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`) or as a destination of a ``selfdestruct``. +.. note:: + Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve + any payload supplied with the call. + A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it. It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function). diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 2cc0b9a05..1505a8548 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -66,6 +66,28 @@ as the actual contract has not been created yet. Functions of other contracts have to be called externally. For an external call, all function arguments have to be copied to memory. +.. warning:: + Be careful that ``feed.info.value(10).gas(800)`` only locally sets the ``value`` and amount of ``gas`` sent with the function call, and the parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost. + +Function calls cause exceptions if the called contract does not exist (in the +sense that the account does not contain code) or if the called contract itself +throws an exception or goes out of gas. + +.. warning:: + Any interaction with another contract imposes a potential danger, especially + if the source code of the contract is not known in advance. The + current contract hands over control to the called contract and that may potentially + do just about anything. Even if the called contract inherits from a known parent contract, + the inheriting contract is only required to have a correct interface. The + implementation of the contract, however, can be completely arbitrary and thus, + pose a danger. In addition, be prepared in case it calls into other contracts of + your system or even back into the calling contract before the first + call returns. This means + that the called contract can change state variables of the calling contract + via its functions. Write your functions in a way that, for example, calls to + external functions happen after any changes to state variables in your contract + so your contract is not vulnerable to a reentrancy exploit. + .. note:: A function call from one contract to another does not create its own transaction, it is a message call as part of the overall transaction. @@ -89,28 +111,6 @@ When calling functions of other contracts, you can specify the amount of Wei or You need to use the modifier ``payable`` with the ``info`` function because otherwise, the ``.value()`` option would not be available. -.. warning:: - Be careful that ``feed.info.value(10).gas(800)`` only locally sets the ``value`` and amount of ``gas`` sent with the function call, and the parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost. - -Function calls cause exceptions if the called contract does not exist (in the -sense that the account does not contain code) or if the called contract itself -throws an exception or goes out of gas. - -.. warning:: - Any interaction with another contract imposes a potential danger, especially - if the source code of the contract is not known in advance. The - current contract hands over control to the called contract and that may potentially - do just about anything. Even if the called contract inherits from a known parent contract, - the inheriting contract is only required to have a correct interface. The - implementation of the contract, however, can be completely arbitrary and thus, - pose a danger. In addition, be prepared in case it calls into other contracts of - your system or even back into the calling contract before the first - call returns. This means - that the called contract can change state variables of the calling contract - via its functions. Write your functions in a way that, for example, calls to - external functions happen after any changes to state variables in your contract - so your contract is not vulnerable to a reentrancy exploit. - Named Calls and Anonymous Function Parameters --------------------------------------------- @@ -247,16 +247,16 @@ groupings of expressions. It is not possible to mix variable declarations and non-declaration assignments, i.e. the following is not valid: ``(x, uint y) = (1, 2);`` -.. note:: - Prior to version 0.5.0 it was possible to assign to tuples of smaller size, either - filling up on the left or on the right side (which ever was empty). This is - now disallowed, so both sides have to have the same number of components. - .. warning:: Be careful when assigning to multiple variables at the same time when reference types are involved, because it could lead to unexpected copying behaviour. +.. note:: + Prior to version 0.5.0 it was possible to assign to tuples of smaller size, either + filling up on the left or on the right side (which ever was empty). This is + now disallowed, so both sides have to have the same number of components. + Complications for Arrays and Structs ------------------------------------ @@ -385,6 +385,9 @@ There are two other ways to trigger exceptions: The ``revert`` function can be u revert the current call. It is possible to provide a string message containing details about the error that will be passed back to the caller. +.. warning:: + The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. + .. note:: There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which was deprecated in version 0.4.13 and removed in version 0.5.0. @@ -393,9 +396,6 @@ When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are reth and the low-level functions ``call``, ``delegatecall`` and ``staticcall`` -- those return ``false`` as their first return value in case of an exception instead of "bubbling up". -.. warning:: - The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. - Catching exceptions is not yet possible. In the following example, you can see how ``require`` can be used to easily check conditions on inputs diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 71073e29c..757b722f8 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -298,16 +298,16 @@ You might want to install ccache to speed up repeated builds. CMake will pick it up automatically. Building Solidity is quite similar on Linux, macOS and other Unices: +.. warning:: + + BSD builds should work, but are untested by the Solidity team. + .. code-block:: bash mkdir build cd build cmake .. && make -.. warning:: - - BSD builds should work, but are untested by the Solidity team. - or even easier on Linux and macOS, you can run: .. code-block:: bash diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 5d5649837..d626626e4 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -57,14 +57,14 @@ and overwrite your number, but the number is still stored in the history of the blockchain. Later, you will see how you can impose access restrictions so that only you can alter the number. -.. note:: - All identifiers (contract names, function names and variable names) are restricted to - the ASCII character set. It is possible to store UTF-8 encoded data in string variables. - .. warning:: Be careful with using Unicode text, as similar looking (or even identical) characters can have different code points and as such are encoded as a different byte array. +.. note:: + All identifiers (contract names, function names and variable names) are restricted to + the ASCII character set. It is possible to store UTF-8 encoded data in string variables. + .. index:: ! subcurrency Subcurrency Example @@ -513,10 +513,10 @@ Deactivate and Self-destruct The only way to remove code from the blockchain is when a contract at that address performs the ``selfdestruct`` operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost. +.. warning:: + Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk. + .. note:: Even if a contract's code does not contain a call to ``selfdestruct``, it can still perform that operation using ``delegatecall`` or ``callcode``. If you want to deactivate your contracts, you should instead **disable** them by changing some internal state which causes all functions to revert. This makes it impossible to use the contract, as it returns Ether immediately. - -.. warning:: - Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 9c082f9b4..096b65d27 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -427,6 +427,9 @@ long as the operands are integers. If any of the two is fractional, bit operatio and exponentiation is disallowed if the exponent is fractional (because that might result in a non-rational number). +.. warning:: + Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``. + .. note:: Solidity has a number literal type for each rational number. Integer literals and rational number literals belong to number literal types. @@ -435,8 +438,6 @@ a non-rational number). types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both belong to the same number literal type for the rational number three. -.. warning:: - Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``. .. note:: Number literal expressions are converted into a non-literal type as soon as they are used with non-literal diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 604386c47..d8b1e9a60 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -163,6 +163,16 @@ Mathematical and Cryptographic Functions ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input +.. warning:: + + If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without + requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed + for _transaction_ signatures (see `EIP-2 `_), but + the ecrecover function remained unchanged. + + This is usually not a problem unless you require signatures to be unique or + use them to identify items. OpenZeppelin have a `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. + .. note:: There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0. @@ -186,16 +196,6 @@ Mathematical and Cryptographic Functions For further details, read `example usage `_. -.. warning:: - - If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without - requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed - for _transaction_ signatures (see `EIP-2 `_), but - the ecrecover function remained unchanged. - - This is usually not a problem unless you require signatures to be unique or - use them to identify items. OpenZeppelin have a `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. - .. note:: When running ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*, you might encounter Out-of-Gas. This is because these functions are implemented as "precompiled contracts" and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net. From d9ce9cab99d71c9c112eb2f7125e71d2e7b011af Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 4 Jun 2019 14:23:44 +0200 Subject: [PATCH 011/115] [SMTChecker] Use smtlib's implies instead of \!a or b --- libsolidity/formal/CVC4Interface.cpp | 2 ++ libsolidity/formal/SolverInterface.h | 8 +++++++- libsolidity/formal/Z3Interface.cpp | 2 ++ test/libsolidity/smtCheckerTestsJSON/multi.json | 4 ++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 71f657471..b46d8ec38 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -145,6 +145,8 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) return arguments[0].andExpr(arguments[1]); else if (n == "or") return arguments[0].orExpr(arguments[1]); + else if (n == "implies") + return m_context.mkExpr(CVC4::kind::IMPLIES, arguments[0], arguments[1]); else if (n == "=") return m_context.mkExpr(CVC4::kind::EQUAL, arguments[0], arguments[1]); else if (n == "<") diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 0c7f1dbdf..5610a5fd6 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -133,6 +133,7 @@ public: {"not", 1}, {"and", 2}, {"or", 2}, + {"implies", 2}, {"=", 2}, {"<", 2}, {"<=", 2}, @@ -160,7 +161,12 @@ public: static Expression implies(Expression _a, Expression _b) { - return !std::move(_a) || std::move(_b); + return Expression( + "implies", + std::move(_a), + std::move(_b), + Kind::Bool + ); } /// select is the SMT representation of an array index access. diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 4d7ea8347..20c7452b5 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -144,6 +144,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) return arguments[0] && arguments[1]; else if (n == "or") return arguments[0] || arguments[1]; + else if (n == "implies") + return z3::implies(arguments[0], arguments[1]); else if (n == "=") return arguments[0] == arguments[1]; else if (n == "<") diff --git a/test/libsolidity/smtCheckerTestsJSON/multi.json b/test/libsolidity/smtCheckerTestsJSON/multi.json index ba4cf2637..f892c28fe 100644 --- a/test/libsolidity/smtCheckerTestsJSON/multi.json +++ b/test/libsolidity/smtCheckerTestsJSON/multi.json @@ -3,8 +3,8 @@ { "smtlib2responses": { - "0x092d52dc5c2b54c1909592f7b3c8efedfd87afc0223ce421a24a1cc7905006b4": "sat\n((|EVALEXPR_0| 1))\n", - "0x8faacfc008b6f2278b5927ff22d76832956dfb46b3c21a64fab96583c241b88f": "unsat\n", + "0x0a0e9583fd983e7ce82e96bd95f7c0eb831e2dd3ce3364035e30bf1d22823b34": "sat\n((|EVALEXPR_0| 1))\n", + "0x15353582486fb1dac47801edbb366ae40a59ef0191ebe7c09ca32bdabecc2f1a": "unsat\n", "0xa66d08de30c873ca7d0e7e9e426f278640e0ee463a1aed2e4e80baee916b6869": "sat\n((|EVALEXPR_0| 0))\n" } } From 9f5340fa537267fb6759f91c4f9f79b69015ade4 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Tue, 4 Jun 2019 14:55:45 +0200 Subject: [PATCH 012/115] Try to find Z3 using CONFIG_MODE before manual search. --- Changelog.md | 4 +++ cmake/FindZ3.cmake | 60 ++++++++++++++++++++++++-------------- libsolidity/CMakeLists.txt | 2 +- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/Changelog.md b/Changelog.md index f89ac5187..c4c063dbe 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,10 @@ Compiler Features: Bugfixes: +Build System: + * Attempt to use stock Z3 cmake files to find Z3 and only fall back to manual discovery. + + ### 0.5.9 (2019-05-28) diff --git a/cmake/FindZ3.cmake b/cmake/FindZ3.cmake index bdd8ce72f..4b63ed4cb 100644 --- a/cmake/FindZ3.cmake +++ b/cmake/FindZ3.cmake @@ -1,29 +1,45 @@ if (USE_Z3) - find_path(Z3_INCLUDE_DIR NAMES z3++.h PATH_SUFFIXES z3) - find_library(Z3_LIBRARY NAMES z3) - find_program(Z3_EXECUTABLE z3 PATH_SUFFIXES bin) - - if(Z3_INCLUDE_DIR AND Z3_LIBRARY AND Z3_EXECUTABLE) - execute_process (COMMAND ${Z3_EXECUTABLE} -version - OUTPUT_VARIABLE libz3_version_str - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - - string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1" - Z3_VERSION_STRING "${libz3_version_str}") - unset(libz3_version_str) - endif() - mark_as_advanced(Z3_VERSION_STRING z3_DIR) + # Save and clear Z3_FIND_VERSION, since the + # Z3 config module cannot handle version requirements. + set(Z3_FIND_VERSION_ORIG ${Z3_FIND_VERSION}) + set(Z3_FIND_VERSION) + # Try to find Z3 using its stock cmake files. + find_package(Z3 QUIET CONFIG) + # Restore Z3_FIND_VERSION for find_package_handle_standard_args. + set(Z3_FIND_VERSION ${Z3_FIND_VERSION_ORIG}) + set(Z3_FIND_VERSION_ORIG) include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Z3 - REQUIRED_VARS Z3_LIBRARY Z3_INCLUDE_DIR - VERSION_VAR Z3_VERSION_STRING) - if (NOT TARGET Z3::Z3) - add_library(Z3::Z3 UNKNOWN IMPORTED) - set_property(TARGET Z3::Z3 PROPERTY IMPORTED_LOCATION ${Z3_LIBRARY}) - set_property(TARGET Z3::Z3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Z3_INCLUDE_DIR}) + if (Z3_FOUND) + set(Z3_VERSION ${Z3_VERSION_STRING}) + find_package_handle_standard_args(Z3 CONFIG_MODE) + else() + find_path(Z3_INCLUDE_DIR NAMES z3++.h PATH_SUFFIXES z3) + find_library(Z3_LIBRARY NAMES z3) + find_program(Z3_EXECUTABLE z3 PATH_SUFFIXES bin) + + if(Z3_INCLUDE_DIR AND Z3_LIBRARY AND Z3_EXECUTABLE) + execute_process (COMMAND ${Z3_EXECUTABLE} -version + OUTPUT_VARIABLE libz3_version_str + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + + string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1" + Z3_VERSION_STRING "${libz3_version_str}") + unset(libz3_version_str) + endif() + mark_as_advanced(Z3_VERSION_STRING z3_DIR) + + find_package_handle_standard_args(Z3 + REQUIRED_VARS Z3_LIBRARY Z3_INCLUDE_DIR + VERSION_VAR Z3_VERSION_STRING) + + if (NOT TARGET z3::libz3) + add_library(z3::libz3 UNKNOWN IMPORTED) + set_property(TARGET z3::libz3 PROPERTY IMPORTED_LOCATION ${Z3_LIBRARY}) + set_property(TARGET z3::libz3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Z3_INCLUDE_DIR}) + endif() endif() else() set(Z3_FOUND FALSE) diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 572038d22..fd9cd772a 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -140,7 +140,7 @@ add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS}) target_link_libraries(solidity PUBLIC yul evmasm langutil devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) if (${Z3_FOUND}) - target_link_libraries(solidity PUBLIC Z3::Z3) + target_link_libraries(solidity PUBLIC z3::libz3) endif() if (${CVC4_FOUND}) From 31ef421fff1f2468165e2abc6c289f2888b7b7ec Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 4 Jun 2019 14:43:26 +0200 Subject: [PATCH 013/115] [SMTChecker] Keep a copy of assertions that are added to the solvers --- libsolidity/formal/SMTPortfolio.cpp | 16 ++++++++++++++++ libsolidity/formal/SMTPortfolio.h | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/libsolidity/formal/SMTPortfolio.cpp b/libsolidity/formal/SMTPortfolio.cpp index 09a311f4f..ea2f3d9ee 100644 --- a/libsolidity/formal/SMTPortfolio.cpp +++ b/libsolidity/formal/SMTPortfolio.cpp @@ -43,18 +43,21 @@ SMTPortfolio::SMTPortfolio(map const& _smtlib2Responses) void SMTPortfolio::reset() { + m_assertions.clear(); for (auto const& s: m_solvers) s->reset(); } void SMTPortfolio::push() { + m_assertions.push_back(Expression(true)); for (auto const& s: m_solvers) s->push(); } void SMTPortfolio::pop() { + m_assertions.pop_back(); for (auto const& s: m_solvers) s->pop(); } @@ -67,10 +70,23 @@ void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort) void SMTPortfolio::addAssertion(Expression const& _expr) { + if (m_assertions.empty()) + m_assertions.push_back(_expr); + else + m_assertions.back() = _expr && move(m_assertions.back()); + for (auto const& s: m_solvers) s->addAssertion(_expr); } +Expression SMTPortfolio::assertions() +{ + if (m_assertions.empty()) + return Expression(true); + + return m_assertions.back(); +} + /* * Broadcasts the SMT query to all solvers and returns a single result. * This comment explains how this result is decided. diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index ebe98261f..1f61ead8d 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -52,6 +52,9 @@ public: void declareVariable(std::string const&, Sort const&) override; void addAssertion(Expression const& _expr) override; + + Expression assertions(); + std::pair> check(std::vector const& _expressionsToEvaluate) override; std::vector unhandledQueries() override; @@ -60,6 +63,8 @@ private: static bool solverAnswered(CheckResult result); std::vector> m_solvers; + + std::vector m_assertions; }; } From 91653526bbcd77ee380cceeddd184d0b6becb981 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 4 Jun 2019 14:51:57 +0200 Subject: [PATCH 014/115] [SMTChecker] Use SMTPortfolio directly instead of pointer to SolverInterface --- libsolidity/formal/SMTChecker.cpp | 62 +++++++++++++++---------------- libsolidity/formal/SMTChecker.h | 10 +++-- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index dbd287876..2ececeeba 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -34,10 +34,10 @@ using namespace langutil; using namespace dev::solidity; SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): - m_interface(make_unique(_smtlib2Responses)), + m_interface(_smtlib2Responses), m_errorReporterReference(_errorReporter), m_errorReporter(m_smtErrors), - m_context(*m_interface) + m_context(m_interface) { #if defined (HAVE_Z3) || defined (HAVE_CVC4) if (!_smtlib2Responses.empty()) @@ -56,11 +56,11 @@ void SMTChecker::analyze(SourceUnit const& _source, shared_ptr const& _ if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) _source.accept(*this); - solAssert(m_interface->solvers() > 0, ""); + solAssert(m_interface.solvers() > 0, ""); // If this check is true, Z3 and CVC4 are not available // and the query answers were not provided, since SMTPortfolio // guarantees that SmtLib2Interface is the first solver. - if (!m_interface->unhandledQueries().empty() && m_interface->solvers() == 1) + if (!m_interface.unhandledQueries().empty() && m_interface.solvers() == 1) { if (!m_noSolverWarning) { @@ -106,7 +106,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) // Not visited by a function call if (m_callStack.empty()) { - m_interface->reset(); + m_interface.reset(); m_context.reset(); m_pathConditions.clear(); m_callStack.clear(); @@ -302,13 +302,13 @@ bool SMTChecker::visit(ForStatement const& _node) checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE."); } - m_interface->push(); + m_interface.push(); if (_node.condition()) - m_interface->addAssertion(expr(*_node.condition())); + m_interface.addAssertion(expr(*_node.condition())); _node.body().accept(*this); if (_node.loopExpression()) _node.loopExpression()->accept(*this); - m_interface->pop(); + m_interface.pop(); auto indicesAfterLoop = copyVariableIndices(); // We reset the execution to before the loop @@ -693,7 +693,7 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) solAssert(value, ""); smt::Expression thisBalance = m_context.balance(); - setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), *m_interface); + setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_interface); checkCondition(thisBalance < expr(*value), _funCall.location(), "Insufficient funds", "address(this).balance", &thisBalance); m_context.transfer(m_context.thisAddress(), expr(address), expr(*value)); @@ -737,7 +737,7 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) // We set the current value to unknown anyway to add type constraints. m_context.setUnknownValue(*symbolicVar); if (index > 0) - m_interface->addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); + m_interface.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); } void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) @@ -819,7 +819,7 @@ void SMTChecker::abstractFunctionCall(FunctionCall const& _funCall) smtArguments.push_back(expr(*arg)); defineExpr(_funCall, (*m_context.expression(_funCall.expression()))(smtArguments)); m_uninterpretedTerms.insert(&_funCall); - setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, *m_interface); + setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, m_interface); } void SMTChecker::endVisit(Identifier const& _identifier) @@ -911,7 +911,7 @@ void SMTChecker::endVisit(Literal const& _literal) auto stringType = TypeProvider::stringMemory(); auto stringLit = dynamic_cast(_literal.annotation().type); solAssert(stringLit, ""); - auto result = smt::newSymbolicVariable(*stringType, stringLit->richIdentifier(), *m_interface); + auto result = smt::newSymbolicVariable(*stringType, stringLit->richIdentifier(), m_interface); m_context.createExpression(_literal, result.second); } m_errorReporter.warning( @@ -936,10 +936,10 @@ void SMTChecker::endVisit(Return const& _return) solAssert(components.size() == returnParams.size(), ""); for (unsigned i = 0; i < returnParams.size(); ++i) if (components.at(i)) - m_interface->addAssertion(expr(*components.at(i)) == m_context.newValue(*returnParams.at(i))); + m_interface.addAssertion(expr(*components.at(i)) == m_context.newValue(*returnParams.at(i))); } else if (returnParams.size() == 1) - m_interface->addAssertion(expr(*_return.expression()) == m_context.newValue(*returnParams.front())); + m_interface.addAssertion(expr(*_return.expression()) == m_context.newValue(*returnParams.front())); } } @@ -981,7 +981,7 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) if (_memberAccess.memberName() == "balance") { defineExpr(_memberAccess, m_context.balance(expr(_memberAccess.expression()))); - setSymbolicUnknownValue(*m_context.expression(_memberAccess), *m_interface); + setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_interface); m_uninterpretedTerms.insert(&_memberAccess); return false; } @@ -1028,7 +1028,7 @@ void SMTChecker::endVisit(IndexAccess const& _indexAccess) setSymbolicUnknownValue( expr(_indexAccess), _indexAccess.annotation().type, - *m_interface + m_interface ); m_uninterpretedTerms.insert(&_indexAccess); } @@ -1080,7 +1080,7 @@ void SMTChecker::arrayIndexAssignment(Expression const& _expr, smt::Expression c expr(*indexAccess.indexExpression()), _rightHandSide ); - m_interface->addAssertion(m_context.newValue(*varDecl) == store); + m_interface.addAssertion(m_context.newValue(*varDecl) == store); // Update the SMT select value after the assignment, // necessary for sound models. defineExpr(indexAccess, smt::Expression::select( @@ -1211,7 +1211,7 @@ smt::Expression SMTChecker::arithmeticOperation( if (_op == Token::Div || _op == Token::Mod) { checkCondition(_right == 0, _location, "Division by zero", "", &_right); - m_interface->addAssertion(_right != 0); + m_interface.addAssertion(_right != 0); } addOverflowTarget( @@ -1393,7 +1393,7 @@ void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expressio addOverflowTarget(OverflowTarget::Type::All, TypeProvider::uint(160), _value, _location); else if (type->category() == Type::Category::Mapping) arrayAssignment(); - m_interface->addAssertion(m_context.newValue(_variable) == _value); + m_interface.addAssertion(m_context.newValue(_variable) == _value); } SMTChecker::VariableIndices SMTChecker::visitBranch(ASTNode const* _statement, smt::Expression _condition) @@ -1422,7 +1422,7 @@ void SMTChecker::checkCondition( smt::Expression const* _additionalValue ) { - m_interface->push(); + m_interface.push(); addPathConjoinedExpression(_condition); vector expressionsToEvaluate; @@ -1534,7 +1534,7 @@ void SMTChecker::checkCondition( m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); break; } - m_interface->pop(); + m_interface.pop(); } void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string const& _description) @@ -1543,15 +1543,15 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co if (dynamic_cast(&_condition)) return; - m_interface->push(); + m_interface.push(); addPathConjoinedExpression(expr(_condition)); auto positiveResult = checkSatisfiable(); - m_interface->pop(); + m_interface.pop(); - m_interface->push(); + m_interface.push(); addPathConjoinedExpression(!expr(_condition)); auto negatedResult = checkSatisfiable(); - m_interface->pop(); + m_interface.pop(); if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver."); @@ -1596,7 +1596,7 @@ SMTChecker::checkSatisfiableAndGenerateModel(vector const& _exp vector values; try { - tie(result, values) = m_interface->check(_expressionsToEvaluate); + tie(result, values) = m_interface.check(_expressionsToEvaluate); } catch (smt::SolverError const& _e) { @@ -1632,7 +1632,7 @@ void SMTChecker::initializeFunctionCallParameters(CallableDeclaration const& _fu for (unsigned i = 0; i < funParams.size(); ++i) if (createVariable(*funParams[i])) { - m_interface->addAssertion(_callArgs[i] == m_context.newValue(*funParams[i])); + m_interface.addAssertion(_callArgs[i] == m_context.newValue(*funParams[i])); if (funParams[i]->annotation().type->category() == Type::Category::Mapping) m_arrayAssignmentHappened = true; } @@ -1698,7 +1698,7 @@ void SMTChecker::mergeVariables(set const& _variable int trueIndex = _indicesEndTrue.at(decl); int falseIndex = _indicesEndFalse.at(decl); solAssert(trueIndex != falseIndex, ""); - m_interface->addAssertion(m_context.newValue(*decl) == smt::Expression::ite( + m_interface.addAssertion(m_context.newValue(*decl) == smt::Expression::ite( _condition, valueAtIndex(*decl, trueIndex), valueAtIndex(*decl, falseIndex)) @@ -1758,7 +1758,7 @@ void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) { createExpr(_e); solAssert(smt::smtKind(_e.annotation().type->category()) != smt::Kind::Function, "Equality operator applied to type that is not fully supported"); - m_interface->addAssertion(expr(_e) == _value); + m_interface.addAssertion(expr(_e) == _value); } void SMTChecker::popPathCondition() @@ -1807,12 +1807,12 @@ void SMTChecker::pushCallStack(CallStackEntry _entry) void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e) { - m_interface->addAssertion(currentPathConditions() && _e); + m_interface.addAssertion(currentPathConditions() && _e); } void SMTChecker::addPathImpliedExpression(smt::Expression const& _e) { - m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e)); + m_interface.addAssertion(smt::Expression::implies(currentPathConditions(), _e)); } bool SMTChecker::isRootFunction() diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 54254c3a6..661dff342 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -19,7 +19,7 @@ #include -#include +#include #include #include @@ -53,7 +53,7 @@ public: /// This is used if the SMT solver is not directly linked into this binary. /// @returns a list of inputs to the SMT solver that were not part of the argument to /// the constructor. - std::vector unhandledQueries() { return m_interface->unhandledQueries(); } + std::vector unhandledQueries() { return m_interface.unhandledQueries(); } /// @returns the FunctionDefinition of a called function if possible and should inline, /// otherwise nullptr. @@ -91,6 +91,10 @@ private: void endVisit(IndexAccess const& _node) override; bool visit(InlineAssembly const& _node) override; + smt::Expression assertions() { return m_interface.assertions(); } + void push() { m_interface.push(); } + void pop() { m_interface.pop(); } + /// Do not visit subtree if node is a RationalNumber. /// Symbolic _expr is the rational literal. bool shortcutRationalNumber(Expression const& _expr); @@ -270,7 +274,7 @@ private: /// @returns the VariableDeclaration referenced by an Identifier or nullptr. VariableDeclaration const* identifierToVariable(Expression const& _expr); - std::unique_ptr m_interface; + smt::SMTPortfolio m_interface; smt::VariableUsage m_variableUsage; bool m_loopExecutionHappened = false; bool m_arrayAssignmentHappened = false; From 4de1e20954bab153e07005403b94198bbc06c9d9 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 4 Jun 2019 14:54:06 +0200 Subject: [PATCH 015/115] [SMTChecker] Exit early if no pragma --- libsolidity/formal/SMTChecker.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index dbd287876..6bd973e19 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -52,9 +52,12 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _ void SMTChecker::analyze(SourceUnit const& _source, shared_ptr const& _scanner) { + if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) + return; + m_scanner = _scanner; - if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) - _source.accept(*this); + + _source.accept(*this); solAssert(m_interface->solvers() > 0, ""); // If this check is true, Z3 and CVC4 are not available From 1978e1d3ff3c64ede94e0322ef7959e59c398f5f Mon Sep 17 00:00:00 2001 From: Vignesh Karthikeyan Date: Wed, 5 Jun 2019 02:02:40 +0530 Subject: [PATCH 016/115] Added example for signextend Added sample assembly code for signextend --- docs/assembly.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 5b475a9b1..881d77650 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -402,7 +402,8 @@ Local Solidity variables are available for assignments, for example: To be safe, always clear the data properly before you use it in a context where this is important: ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` - To clean signed types, you can use the ``signextend`` opcode. + To clean signed types, you can use the ``signextend`` opcode: + ``assembly { signextend(0, x) }`` .. code:: From f281c94b42564d6ac7f52909cc6640bca41b1f3a Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 4 Jun 2019 14:29:35 +0200 Subject: [PATCH 017/115] [SMTChecker] Test that non-Boolean literals are actually integers --- libsolidity/formal/CVC4Interface.cpp | 10 ++++++++-- libsolidity/formal/Z3Interface.cpp | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index b46d8ec38..217aa816f 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -132,8 +132,14 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) else if (n == "false") return m_context.mkConst(false); else - // We assume it is an integer... - return m_context.mkConst(CVC4::Rational(n)); + try + { + return m_context.mkConst(CVC4::Rational(n)); + } + catch (...) + { + solAssert(false, ""); + } } solAssert(_expr.hasCorrectArity(), ""); diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 20c7452b5..fdb04956e 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -131,8 +131,14 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) else if (n == "false") return m_context.bool_val(false); else - // We assume it is an integer... - return m_context.int_val(n.c_str()); + try + { + return m_context.int_val(n.c_str()); + } + catch (...) + { + solAssert(false, ""); + } } solAssert(_expr.hasCorrectArity(), ""); From 5677bedf3483046c91d5fd29d4aa61b1de183f8c Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 5 Jun 2019 11:55:26 +0200 Subject: [PATCH 018/115] Require gcc >= 5.0. --- Changelog.md | 1 + cmake/EthCompilerSettings.cmake | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index c4c063dbe..37da04f32 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,7 @@ Bugfixes: Build System: * Attempt to use stock Z3 cmake files to find Z3 and only fall back to manual discovery. + * Generate a cmake error for gcc versions older than 5.0. diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 3fe98188d..7ffb45964 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -41,11 +41,11 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # Additional GCC-specific compiler settings. if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - # Check that we've got GCC 4.7 or newer. + # Check that we've got GCC 5.0 or newer. execute_process( COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)) - message(FATAL_ERROR "${PROJECT_NAME} requires g++ 4.7 or greater.") + if (NOT (GCC_VERSION VERSION_GREATER 5.0 OR GCC_VERSION VERSION_EQUAL 5.0)) + message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.") endif () # Additional Clang-specific compiler settings. From b80cc42a7cacaf1ebf6d9a1f7692ac002631cf05 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 3 Jun 2019 16:54:03 +0200 Subject: [PATCH 019/115] yul: AsmPrinter fix when appending type name but no type-name is available. --- libyul/AsmPrinter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index a3d9eafc3..71151a0cf 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -266,7 +266,7 @@ string AsmPrinter::formatTypedName(TypedName _variable) const string AsmPrinter::appendTypeName(YulString _type) const { - if (m_yul) + if (m_yul && !_type.empty()) return ":" + _type.str(); return ""; } From f99d78117e80c57335c7f8bff373e39d6b719b6e Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Thu, 30 May 2019 14:34:52 +0200 Subject: [PATCH 020/115] Bring code examples for Style Guide inline with style guide Add back accidentally removed public --- docs/style-guide.rst | 75 ++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 10cbffb67..fb38f2d93 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -757,20 +757,26 @@ No:: pragma solidity >=0.4.0 <0.7.0; + // Base contracts just to make this compile contract B { constructor(uint) public { } } + + contract C { constructor(uint, uint) public { } } + + contract D { constructor(uint) public { } } + contract A is B, C, D { uint x; @@ -778,12 +784,12 @@ No:: B(param1) C(param2, param3) D(param4) - public - { + public { x = param5; } } + contract X is B, C, D { uint x; @@ -792,10 +798,11 @@ No:: C(param2, param3) D(param4) public { - x = param5; - } + x = param5; + } } + When declaring short functions with a single statement, it is permissible to do it on a single line. Permissible:: @@ -973,27 +980,32 @@ Yes:: pragma solidity >=0.4.0 <0.7.0; + // Owned.sol contract Owned { - address public owner; + address public owner; - constructor() public { - owner = msg.sender; - } + constructor() public { + owner = msg.sender; + } - modifier onlyOwner { - require(msg.sender == owner); - _; - } + modifier onlyOwner { + require(msg.sender == owner); + _; + } - function transferOwnership(address newOwner) public onlyOwner { - owner = newOwner; - } + function transferOwnership(address newOwner) public onlyOwner { + owner = newOwner; + } } - // Congress.sol +and in ``Congress.sol``:: + + pragma solidity >=0.4.0 <0.7.0; + import "./Owned.sol"; + contract Congress is Owned, TokenRecipient { //... } @@ -1002,32 +1014,34 @@ No:: pragma solidity >=0.4.0 <0.7.0; + // owned.sol contract owned { - address public owner; + address public owner; - constructor() public { - owner = msg.sender; - } + constructor() public { + owner = msg.sender; + } - modifier onlyOwner { - require(msg.sender == owner); - _; - } + modifier onlyOwner { + require(msg.sender == owner); + _; + } - function transferOwnership(address newOwner) public onlyOwner { - owner = newOwner; - } + function transferOwnership(address newOwner) public onlyOwner { + owner = newOwner; + } } - // Congress.sol +and in ``Congress.sol``:: + import "./owned.sol"; + contract Congress is owned, tokenRecipient { //... } - Struct Names ========================== @@ -1104,6 +1118,7 @@ added looks like the one below:: pragma solidity >=0.4.0 <0.7.0; + /// @author The Solidity Team /// @title A simple storage example contract SimpleStorage { @@ -1126,4 +1141,4 @@ added looks like the one below:: It is recommended that Solidity contracts are fully annontated using `NatSpec `_ for all public interfaces (everything in the ABI). -Please see the section about `NatSpec `_ for a detailed explanation. +Please see the section about `NatSpec `_ for a detailed explanation. \ No newline at end of file From 1221eeebf1eef033e4eca79753ec969f924edbe4 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Wed, 5 Jun 2019 11:51:43 +0200 Subject: [PATCH 021/115] [SMTChecker] Report malformed expressions more precisely --- libsolidity/formal/CVC4Interface.cpp | 144 +++++++++++++++------------ libsolidity/formal/Z3Interface.cpp | 122 ++++++++++++----------- 2 files changed, 147 insertions(+), 119 deletions(-) diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 217aa816f..909202094 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -60,17 +60,21 @@ void CVC4Interface::addAssertion(Expression const& _expr) { m_solver.assertFormula(toCVC4Expr(_expr)); } - catch (CVC4::TypeCheckingException const&) + catch (CVC4::TypeCheckingException const& _e) { - solAssert(false, ""); + solAssert(false, _e.what()); } - catch (CVC4::LogicException const&) + catch (CVC4::LogicException const& _e) { - solAssert(false, ""); + solAssert(false, _e.what()); } - catch (CVC4::UnsafeInterruptException const&) + catch (CVC4::UnsafeInterruptException const& _e) { - solAssert(false, ""); + solAssert(false, _e.what()); + } + catch (CVC4::Exception const& _e) + { + solAssert(false, _e.what()); } } @@ -120,66 +124,82 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) for (auto const& arg: _expr.arguments) arguments.push_back(toCVC4Expr(arg)); - string const& n = _expr.name; - // Function application - if (!arguments.empty() && m_variables.count(_expr.name)) - return m_context.mkExpr(CVC4::kind::APPLY_UF, m_variables.at(n), arguments); - // Literal - else if (arguments.empty()) + try { - if (n == "true") - return m_context.mkConst(true); - else if (n == "false") - return m_context.mkConst(false); - else - try - { - return m_context.mkConst(CVC4::Rational(n)); - } - catch (...) - { - solAssert(false, ""); - } + string const& n = _expr.name; + // Function application + if (!arguments.empty() && m_variables.count(_expr.name)) + return m_context.mkExpr(CVC4::kind::APPLY_UF, m_variables.at(n), arguments); + // Literal + else if (arguments.empty()) + { + if (n == "true") + return m_context.mkConst(true); + else if (n == "false") + return m_context.mkConst(false); + else + try + { + return m_context.mkConst(CVC4::Rational(n)); + } + catch (CVC4::TypeCheckingException const& _e) + { + solAssert(false, _e.what()); + } + catch (CVC4::Exception const& _e) + { + solAssert(false, _e.what()); + } + } + + solAssert(_expr.hasCorrectArity(), ""); + if (n == "ite") + return arguments[0].iteExpr(arguments[1], arguments[2]); + else if (n == "not") + return arguments[0].notExpr(); + else if (n == "and") + return arguments[0].andExpr(arguments[1]); + else if (n == "or") + return arguments[0].orExpr(arguments[1]); + else if (n == "implies") + return m_context.mkExpr(CVC4::kind::IMPLIES, arguments[0], arguments[1]); + else if (n == "=") + return m_context.mkExpr(CVC4::kind::EQUAL, arguments[0], arguments[1]); + else if (n == "<") + return m_context.mkExpr(CVC4::kind::LT, arguments[0], arguments[1]); + else if (n == "<=") + return m_context.mkExpr(CVC4::kind::LEQ, arguments[0], arguments[1]); + else if (n == ">") + return m_context.mkExpr(CVC4::kind::GT, arguments[0], arguments[1]); + else if (n == ">=") + return m_context.mkExpr(CVC4::kind::GEQ, arguments[0], arguments[1]); + else if (n == "+") + return m_context.mkExpr(CVC4::kind::PLUS, arguments[0], arguments[1]); + else if (n == "-") + return m_context.mkExpr(CVC4::kind::MINUS, arguments[0], arguments[1]); + else if (n == "*") + return m_context.mkExpr(CVC4::kind::MULT, arguments[0], arguments[1]); + else if (n == "/") + return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]); + else if (n == "mod") + return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]); + else if (n == "select") + return m_context.mkExpr(CVC4::kind::SELECT, arguments[0], arguments[1]); + else if (n == "store") + return m_context.mkExpr(CVC4::kind::STORE, arguments[0], arguments[1], arguments[2]); + + solAssert(false, ""); + } + catch (CVC4::TypeCheckingException const& _e) + { + solAssert(false, _e.what()); + } + catch (CVC4::Exception const& _e) + { + solAssert(false, _e.what()); } - solAssert(_expr.hasCorrectArity(), ""); - if (n == "ite") - return arguments[0].iteExpr(arguments[1], arguments[2]); - else if (n == "not") - return arguments[0].notExpr(); - else if (n == "and") - return arguments[0].andExpr(arguments[1]); - else if (n == "or") - return arguments[0].orExpr(arguments[1]); - else if (n == "implies") - return m_context.mkExpr(CVC4::kind::IMPLIES, arguments[0], arguments[1]); - else if (n == "=") - return m_context.mkExpr(CVC4::kind::EQUAL, arguments[0], arguments[1]); - else if (n == "<") - return m_context.mkExpr(CVC4::kind::LT, arguments[0], arguments[1]); - else if (n == "<=") - return m_context.mkExpr(CVC4::kind::LEQ, arguments[0], arguments[1]); - else if (n == ">") - return m_context.mkExpr(CVC4::kind::GT, arguments[0], arguments[1]); - else if (n == ">=") - return m_context.mkExpr(CVC4::kind::GEQ, arguments[0], arguments[1]); - else if (n == "+") - return m_context.mkExpr(CVC4::kind::PLUS, arguments[0], arguments[1]); - else if (n == "-") - return m_context.mkExpr(CVC4::kind::MINUS, arguments[0], arguments[1]); - else if (n == "*") - return m_context.mkExpr(CVC4::kind::MULT, arguments[0], arguments[1]); - else if (n == "/") - return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]); - else if (n == "mod") - return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]); - else if (n == "select") - return m_context.mkExpr(CVC4::kind::SELECT, arguments[0], arguments[1]); - else if (n == "store") - return m_context.mkExpr(CVC4::kind::STORE, arguments[0], arguments[1], arguments[2]); - // Cannot reach here. solAssert(false, ""); - return arguments[0]; } CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort) diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index fdb04956e..1039dd1f8 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -116,69 +116,77 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) for (auto const& arg: _expr.arguments) arguments.push_back(toZ3Expr(arg)); - string const& n = _expr.name; - if (m_functions.count(n)) - return m_functions.at(n)(arguments); - else if (m_constants.count(n)) + try { - solAssert(arguments.empty(), ""); - return m_constants.at(n); + string const& n = _expr.name; + if (m_functions.count(n)) + return m_functions.at(n)(arguments); + else if (m_constants.count(n)) + { + solAssert(arguments.empty(), ""); + return m_constants.at(n); + } + else if (arguments.empty()) + { + if (n == "true") + return m_context.bool_val(true); + else if (n == "false") + return m_context.bool_val(false); + else + try + { + return m_context.int_val(n.c_str()); + } + catch (z3::exception const& _e) + { + solAssert(false, _e.msg()); + } + } + + solAssert(_expr.hasCorrectArity(), ""); + if (n == "ite") + return z3::ite(arguments[0], arguments[1], arguments[2]); + else if (n == "not") + return !arguments[0]; + else if (n == "and") + return arguments[0] && arguments[1]; + else if (n == "or") + return arguments[0] || arguments[1]; + else if (n == "implies") + return z3::implies(arguments[0], arguments[1]); + else if (n == "=") + return arguments[0] == arguments[1]; + else if (n == "<") + return arguments[0] < arguments[1]; + else if (n == "<=") + return arguments[0] <= arguments[1]; + else if (n == ">") + return arguments[0] > arguments[1]; + else if (n == ">=") + return arguments[0] >= arguments[1]; + else if (n == "+") + return arguments[0] + arguments[1]; + else if (n == "-") + return arguments[0] - arguments[1]; + else if (n == "*") + return arguments[0] * arguments[1]; + else if (n == "/") + return arguments[0] / arguments[1]; + else if (n == "mod") + return z3::mod(arguments[0], arguments[1]); + else if (n == "select") + return z3::select(arguments[0], arguments[1]); + else if (n == "store") + return z3::store(arguments[0], arguments[1], arguments[2]); + + solAssert(false, ""); } - else if (arguments.empty()) + catch (z3::exception const& _e) { - if (n == "true") - return m_context.bool_val(true); - else if (n == "false") - return m_context.bool_val(false); - else - try - { - return m_context.int_val(n.c_str()); - } - catch (...) - { - solAssert(false, ""); - } + solAssert(false, _e.msg()); } - solAssert(_expr.hasCorrectArity(), ""); - if (n == "ite") - return z3::ite(arguments[0], arguments[1], arguments[2]); - else if (n == "not") - return !arguments[0]; - else if (n == "and") - return arguments[0] && arguments[1]; - else if (n == "or") - return arguments[0] || arguments[1]; - else if (n == "implies") - return z3::implies(arguments[0], arguments[1]); - else if (n == "=") - return arguments[0] == arguments[1]; - else if (n == "<") - return arguments[0] < arguments[1]; - else if (n == "<=") - return arguments[0] <= arguments[1]; - else if (n == ">") - return arguments[0] > arguments[1]; - else if (n == ">=") - return arguments[0] >= arguments[1]; - else if (n == "+") - return arguments[0] + arguments[1]; - else if (n == "-") - return arguments[0] - arguments[1]; - else if (n == "*") - return arguments[0] * arguments[1]; - else if (n == "/") - return arguments[0] / arguments[1]; - else if (n == "mod") - return z3::mod(arguments[0], arguments[1]); - else if (n == "select") - return z3::select(arguments[0], arguments[1]); - else if (n == "store") - return z3::store(arguments[0], arguments[1], arguments[2]); - // Cannot reach here. solAssert(false, ""); - return arguments[0]; } z3::sort Z3Interface::z3Sort(Sort const& _sort) From b7634faa3d818453fd3c5f36ae077af9107a9e22 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Thu, 6 Jun 2019 10:36:41 +0200 Subject: [PATCH 022/115] Improve error message for delegatecall.value --- libsolidity/analysis/TypeChecker.cpp | 5 +++++ .../297_library_functions_do_not_have_value.sol | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index dc05ce18f..8069bd13b 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2065,6 +2065,11 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) { if (funType->kind() == FunctionType::Kind::Creation) errorMsg = "Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available."; + else if ( + funType->kind() == FunctionType::Kind::DelegateCall || + funType->kind() == FunctionType::Kind::BareDelegateCall + ) + errorMsg = "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting."; else errorMsg = "Member \"value\" is only available for payable functions."; } diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol index dfb706417..6e6546c99 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol @@ -5,4 +5,4 @@ contract test { } } // ---- -// TypeError: (87-96): Member "value" is only available for payable functions. +// TypeError: (87-96): Member "value" is not allowed in delegated calls due to "msg.value" persisting. From 6368cd4c82f00a26b93e1799b80c53bc9b033e58 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 13:16:27 +0200 Subject: [PATCH 023/115] AsmParser: disallow trailing commas in function call arguments. --- Changelog.md | 1 + libyul/AsmParser.cpp | 10 ++++++---- ...{empty_fun_arg.sol => empty_fun_arg_beginning.sol} | 3 +-- .../inlineAssembly/invalid/empty_fun_arg_end.sol | 11 +++++++++++ .../inlineAssembly/invalid/empty_fun_arg_middle.sol | 10 ++++++++++ 5 files changed, 29 insertions(+), 6 deletions(-) rename test/libsolidity/syntaxTests/inlineAssembly/invalid/{empty_fun_arg.sol => empty_fun_arg_beginning.sol} (63%) create mode 100644 test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol create mode 100644 test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol diff --git a/Changelog.md b/Changelog.md index 37da04f32..c26815ad8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ Compiler Features: Bugfixes: + * Yul / Inline Assembly Parser: Disallow trailing commas in function call arguments. Build System: diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 3f29f6fa5..9686e7dde 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -609,12 +609,14 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) else ret = std::move(boost::get(_initialOp)); expectToken(Token::LParen); - while (currentToken() != Token::RParen) + if (currentToken() != Token::RParen) { ret.arguments.emplace_back(parseExpression()); - if (currentToken() == Token::RParen) - break; - expectToken(Token::Comma); + while (currentToken() != Token::RParen) + { + expectToken(Token::Comma); + ret.arguments.emplace_back(parseExpression()); + } } ret.location.end = endPosition(); expectToken(Token::RParen); diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol similarity index 63% rename from test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg.sol rename to test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol index 9acac7a64..9d99c8b4d 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol @@ -3,10 +3,9 @@ contract C { assembly { function f(a, b) {} f() - f(1,) f(,1) } } } // ---- -// ParserError: (113-114): Literal, identifier or instruction expected. +// ParserError: (101-102): Literal, identifier or instruction expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol new file mode 100644 index 000000000..0d041492d --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure { + assembly { + function f(a, b) {} + f() + f(1,) + } + } +} +// ---- +// ParserError: (103-104): Literal, identifier or instruction expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol new file mode 100644 index 000000000..09e0ba038 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol @@ -0,0 +1,10 @@ +contract C { + function f() public pure { + assembly { + function f(a, b, c) {} + f(1,,1) + } + } +} +// ---- +// ParserError: (96-97): Literal, identifier or instruction expected. From 2dbdddc7e5ded27b5bcdd9d6bb66367e3ff8c67b Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 13:17:08 +0200 Subject: [PATCH 024/115] Fix argument count for require with message and string literals. --- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index d5975b717..e9adf36a2 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -550,13 +550,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert"); solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert"); + Type const* messageArgumentType = arguments.size() > 1 ? arguments[1]->annotation().type : nullptr; string requireOrAssertFunction = m_utils.requireOrAssertFunction( functionType->kind() == FunctionType::Kind::Assert, - arguments.size() > 1 ? arguments[1]->annotation().type : nullptr + messageArgumentType ); m_code << move(requireOrAssertFunction) << "(" << m_context.variable(*arguments[0]); - if (arguments.size() > 1) + if (messageArgumentType && messageArgumentType->sizeOnStack() > 0) m_code << ", " << m_context.variable(*arguments[1]); m_code << ")\n"; From f21d5ab8394a6256123d6787a8b70c81e9d3a8ba Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 13:50:43 +0200 Subject: [PATCH 025/115] CMake error for big endian systems. --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e8d729f15..5324cf616 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,12 @@ eth_policy() set(PROJECT_VERSION "0.5.10") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) +include(TestBigEndian) +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) +if (IS_BIG_ENDIAN) + message(FATAL_ERROR "${PROJECT_NAME} currently does not support big endian systems.") +endif() + option(LLL "Build LLL" OFF) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) option(LLLC_LINK_STATIC "Link lllc executable statically on supported platforms" OFF) From 5370af50e3203897162afd5ae76f2c2c86199460 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 16:16:02 +0200 Subject: [PATCH 026/115] Attempt to workaround for older cmake versions. --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5324cf616..64433978e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,11 @@ eth_policy() set(PROJECT_VERSION "0.5.10") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) +if (${CMAKE_VERSION} VERSION_LESS "3.9.0") + # needed for the big endian test for older cmake versions + enable_language(C) +endif() + include(TestBigEndian) TEST_BIG_ENDIAN(IS_BIG_ENDIAN) if (IS_BIG_ENDIAN) From a445df63cf514ba09f23ee811801a864bb549a20 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 17:01:49 +0200 Subject: [PATCH 027/115] Require cmake 3.5. --- CMakeLists.txt | 2 +- docs/installing-solidity.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e8d729f15..602522fa5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.5.0) set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory") list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR}) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 71073e29c..77521cca2 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -184,7 +184,7 @@ The following are dependencies for all builds of Solidity: +-----------------------------------+-------------------------------------------------------+ | Software | Notes | +===================================+=======================================================+ -| `CMake`_ | Cross-platform build file generator. | +| `CMake`_ (version 3.5+) | Cross-platform build file generator. | +-----------------------------------+-------------------------------------------------------+ | `Boost`_ (version 1.65+) | C++ libraries. | +-----------------------------------+-------------------------------------------------------+ From 226f8bb64d6ade2615dba6416e6b61ede42c4c30 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 17:18:48 +0200 Subject: [PATCH 028/115] Use imported cmake targets for boost. --- cmake/EthDependencies.cmake | 30 ++++++++++-------------------- libdevcore/CMakeLists.txt | 3 +-- libsolidity/CMakeLists.txt | 2 +- lllc/CMakeLists.txt | 2 +- solc/CMakeLists.txt | 2 +- test/CMakeLists.txt | 2 +- test/tools/CMakeLists.txt | 8 ++++---- 7 files changed, 19 insertions(+), 30 deletions(-) diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 477a604d6..0093be579 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -1,21 +1,3 @@ -# all dependencies that are not directly included in the cpp-ethereum distribution are defined here -# for this to work, download the dependency via the cmake script in extdep or install them manually! - -function(eth_show_dependency DEP NAME) - get_property(DISPLAYED GLOBAL PROPERTY ETH_${DEP}_DISPLAYED) - if (NOT DISPLAYED) - set_property(GLOBAL PROPERTY ETH_${DEP}_DISPLAYED TRUE) - if (NOT("${${DEP}_VERSION}" STREQUAL "")) - message(STATUS "${NAME} version: ${${DEP}_VERSION}") - endif() - message(STATUS "${NAME} headers: ${${DEP}_INCLUDE_DIRS}") - message(STATUS "${NAME} lib : ${${DEP}_LIBRARIES}") - if (NOT("${${DEP}_DLLS}" STREQUAL "")) - message(STATUS "${NAME} dll : ${${DEP}_DLLS}") - endif() - endif() -endfunction() - if (DEFINED MSVC) # by defining CMAKE_PREFIX_PATH variable, cmake will look for dependencies first in our own repository before looking in system paths like /usr/local/ ... # this must be set to point to the same directory as $ETH_DEPENDENCY_INSTALL_DIR in /extdep directory @@ -41,6 +23,14 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts) set(Boost_USE_MULTITHREADED ON) option(Boost_USE_STATIC_LIBS "Link Boost statically" ON) -find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS regex filesystem unit_test_framework program_options system) +set(BOOST_COMPONENTS "regex;filesystem;unit_test_framework;program_options;system") -eth_show_dependency(Boost boost) +find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS}) + +# make sure we actually get all required imported targets for boost +list(APPEND BOOST_COMPONENTS "boost") # header only target +foreach (BOOST_COMPONENT IN LISTS BOOST_COMPONENTS) + if (NOT TARGET Boost::${BOOST_COMPONENT}) + message(FATAL_ERROR "Boost target Boost::${BOOST_COMPONENT} was not defined by cmake.") + endif() +endforeach() diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index 50cbb6a95..a092e3140 100644 --- a/libdevcore/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -34,7 +34,6 @@ set(sources ) add_library(devcore ${sources}) -target_link_libraries(devcore PUBLIC jsoncpp ${Boost_FILESYSTEM_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${Boost_SYSTEM_LIBRARIES} Threads::Threads) +target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system Threads::Threads) target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}") -target_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) add_dependencies(devcore solidity_BuildInfo.h) diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index fd9cd772a..0360dba29 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -137,7 +137,7 @@ if (NOT (${Z3_FOUND} OR ${CVC4_FOUND})) endif() add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS}) -target_link_libraries(solidity PUBLIC yul evmasm langutil devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) +target_link_libraries(solidity PUBLIC yul evmasm langutil devcore Boost::boost Boost::filesystem Boost::system) if (${Z3_FOUND}) target_link_libraries(solidity PUBLIC z3::libz3) diff --git a/lllc/CMakeLists.txt b/lllc/CMakeLists.txt index d6538ee29..4e78207e2 100644 --- a/lllc/CMakeLists.txt +++ b/lllc/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(lllc main.cpp) -target_link_libraries(lllc PRIVATE lll ${Boost_SYSTEM_LIBRARY}) +target_link_libraries(lllc PRIVATE lll Boost::boost Boost::system) if (INSTALL_LLLC) include(GNUInstallDirs) diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt index d9b12a063..3fbdefe6a 100644 --- a/solc/CMakeLists.txt +++ b/solc/CMakeLists.txt @@ -5,7 +5,7 @@ set( ) add_executable(solc ${sources}) -target_link_libraries(solc PRIVATE solidity ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +target_link_libraries(solc PRIVATE solidity Boost::boost Boost::program_options) include(GNUInstallDirs) install(TARGETS solc DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index acc43b68d..30574a4eb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,7 +30,7 @@ add_executable(soltest ${sources} ${headers} ${libsolidity_sources} ${libsolidity_headers} ${libsolidity_util_sources} ${libsolidity_util_headers} ) -target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) +target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore Boost::boost Boost::program_options Boost::unit_test_framework) if (LLL) target_link_libraries(soltest PRIVATE lll) diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 9a7df2489..b9e72d27b 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -2,13 +2,13 @@ add_subdirectory(ossfuzz) add_subdirectory(yulInterpreter) add_executable(yulrun yulrun.cpp) -target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm Boost::boost Boost::program_options) add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp) -target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) +target_link_libraries(solfuzzer PRIVATE libsolc evmasm Boost::boost Boost::program_options Boost::system) add_executable(yulopti yulopti.cpp) -target_link_libraries(yulopti PRIVATE solidity ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) +target_link_libraries(yulopti PRIVATE solidity Boost::boost Boost::program_options Boost::system) add_executable(isoltest isoltest.cpp @@ -31,4 +31,4 @@ add_executable(isoltest ../libyul/YulOptimizerTest.cpp ../libyul/YulInterpreterTest.cpp ) -target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) +target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm Boost::boost Boost::program_options Boost::unit_test_framework) From 2ec45797b24906317b88ed05c68171e7d5dcddaf Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 17:19:18 +0200 Subject: [PATCH 029/115] Update boost to 1.70 for emscripten and cache a proper installation. --- .circleci/config.yml | 2 +- .travis.yml | 2 +- scripts/travis-emscripten/build_emscripten.sh | 20 ++++++------------- scripts/travis-emscripten/install_deps.sh | 18 ++++++++--------- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ea21ff0d..0cc8bc261 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -78,7 +78,7 @@ jobs: name: Save Boost build key: *boost-cache-key paths: - - boost_1_68_0 + - boost_1_70_0_install - store_artifacts: path: emscripten_build/libsolc/soljson.js destination: soljson.js diff --git a/.travis.yml b/.travis.yml index c91daada1..7e1114974 100644 --- a/.travis.yml +++ b/.travis.yml @@ -183,7 +183,7 @@ git: cache: ccache: true directories: - - boost_1_68_0 + - boost_1_70_0_install - $HOME/.local install: diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 5b5540b6d..19554a7ed 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -64,15 +64,14 @@ fi # Boost echo -en 'travis_fold:start:compiling_boost\\r' -cd "$WORKSPACE"/boost_1_68_0 -# if b2 exists, it is a fresh checkout, otherwise it comes from the cache -# and is already compiled -test -e b2 && ( +test -e "$WORKSPACE"/boost_1_70_0_install || ( +cd "$WORKSPACE"/boost_1_70_0 ./b2 toolset=emscripten link=static variant=release threading=single runtime-link=static \ - system regex filesystem unit_test_framework program_options cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" -find . -name 'libboost*.a' -exec cp {} . \; -rm -rf b2 libs doc tools more bin.v2 status + --with-system --with-regex --with-filesystem --with-test --with-program_options cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \ + --prefix="$WORKSPACE"/boost_1_70_0_install install ) +ln -sf "$WORKSPACE"/boost_1_70_0_install/lib/* /emsdk_portable/sdk/system/lib +ln -sf "$WORKSPACE"/boost_1_70_0_install/include/* /emsdk_portable/sdk/system/include echo -en 'travis_fold:end:compiling_boost\\r' echo -en 'travis_fold:start:install_cmake.sh\\r' @@ -87,15 +86,8 @@ cd $BUILD_DIR cmake \ -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/emscripten.cmake \ -DCMAKE_BUILD_TYPE=Release \ - -DBoost_FOUND=1 \ -DBoost_USE_STATIC_LIBS=1 \ -DBoost_USE_STATIC_RUNTIME=1 \ - -DBoost_INCLUDE_DIR="$WORKSPACE"/boost_1_68_0/ \ - -DBoost_FILESYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_filesystem.a \ - -DBoost_PROGRAM_OPTIONS_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_program_options.a \ - -DBoost_REGEX_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_regex.a \ - -DBoost_SYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_system.a \ - -DBoost_UNIT_TEST_FRAMEWORK_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_unit_test_framework.a \ -DTESTS=0 \ .. make -j 4 diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index 5959ef129..8ceea8544 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -30,15 +30,15 @@ set -ev echo -en 'travis_fold:start:installing_dependencies\\r' -test -e boost_1_68_0 -a -e boost_1_68_0/boost || ( -rm -rf boost_1_68_0 -rm -f boost.tar.xz -wget -q 'https://sourceforge.net/projects/boost/files/boost/1.68.0/boost_1_68_0.tar.gz/download'\ - -O boost.tar.xz -test "$(shasum boost.tar.xz)" = "a78cf6ebb111a48385dd0c135e145a6819a8c856 boost.tar.xz" -tar -xzf boost.tar.xz -rm boost.tar.xz -cd boost_1_68_0 +test -e boost_1_70_0_install || ( +rm -rf boost_1_70_0 +rm -f boost.tar.gz +wget -q 'https://sourceforge.net/projects/boost/files/boost/1.70.0/boost_1_70_0.tar.gz/download'\ + -O boost.tar.gz +test "$(shasum boost.tar.gz)" = "7804c782deb00f36ac80b1000b71a3707eadb620 boost.tar.gz" +tar -xzf boost.tar.gz +rm boost.tar.gz +cd boost_1_70_0 ./bootstrap.sh wget -q 'https://raw.githubusercontent.com/tee3/boost-build-emscripten/master/emscripten.jam' test "$(shasum emscripten.jam)" = "a7e13fc2c1e53b0e079ef440622f879aa6da3049 emscripten.jam" From 8e20a5e59acc0541cc755f1609e45021b46fb416 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 18:02:48 +0200 Subject: [PATCH 030/115] Allow extracted semantics tests to run both with and without Yul. --- test/libsolidity/SemanticTest.cpp | 136 ++++++++++++++++++------------ test/libsolidity/SemanticTest.h | 2 + 2 files changed, 83 insertions(+), 55 deletions(-) diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index f7d08e67c..adc3e0650 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -46,8 +46,18 @@ SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, lang m_source = parseSourceAndSettings(file); if (m_settings.count("compileViaYul")) { - m_validatedSettings["compileViaYul"] = m_settings["compileViaYul"]; - m_compileViaYul = true; + if (m_settings["compileViaYul"] == "also") + { + m_validatedSettings["compileViaYul"] = m_settings["compileViaYul"]; + m_runWithYul = true; + m_runWithoutYul = true; + } + else + { + m_validatedSettings["compileViaYul"] = "only"; + m_runWithYul = true; + m_runWithoutYul = false; + } m_settings.erase("compileViaYul"); } parseExpectations(file); @@ -55,68 +65,84 @@ SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, lang TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) { - - - bool success = true; - for (auto& test: m_tests) - test.reset(); - - for (auto& test: m_tests) + for(bool compileViaYul: set{!m_runWithoutYul, m_runWithYul}) { - if (&test == &m_tests.front()) - if (test.call().isConstructor) - deploy("", 0, test.call().arguments.rawBytes()); + bool success = true; + + m_compileViaYul = compileViaYul; + if (compileViaYul) + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running via Yul:" << endl; + + for (auto& test: m_tests) + test.reset(); + + for (auto& test: m_tests) + { + if (&test == &m_tests.front()) + if (test.call().isConstructor) + deploy("", 0, test.call().arguments.rawBytes()); + else + soltestAssert(deploy("", 0, bytes()), "Failed to deploy contract."); else - soltestAssert(deploy("", 0, bytes()), "Failed to deploy contract."); - else - soltestAssert(!test.call().isConstructor, "Constructor has to be the first function call."); + soltestAssert(!test.call().isConstructor, "Constructor has to be the first function call."); - if (test.call().isConstructor) - { - if (m_transactionSuccessful == test.call().expectations.failure) - success = false; + if (test.call().isConstructor) + { + if (m_transactionSuccessful == test.call().expectations.failure) + success = false; - test.setFailure(!m_transactionSuccessful); - test.setRawBytes(bytes()); + test.setFailure(!m_transactionSuccessful); + test.setRawBytes(bytes()); + } + else + { + bytes output = callContractFunctionWithValueNoEncoding( + test.call().signature, + test.call().value, + test.call().arguments.rawBytes() + ); + + if ((m_transactionSuccessful == test.call().expectations.failure) || (output != test.call().expectations.rawBytes())) + success = false; + + test.setFailure(!m_transactionSuccessful); + test.setRawBytes(std::move(output)); + test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName())); + } } - else + + if (!success) { - bytes output = callContractFunctionWithValueNoEncoding( - test.call().signature, - test.call().value, - test.call().arguments.rawBytes() - ); - - if ((m_transactionSuccessful == test.call().expectations.failure) || (output != test.call().expectations.rawBytes())) - success = false; - - test.setFailure(!m_transactionSuccessful); - test.setRawBytes(std::move(output)); - test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName())); + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; + for (auto const& test: m_tests) + { + ErrorReporter errorReporter; + _stream << test.format(errorReporter, _linePrefix, false, _formatted) << endl; + _stream << errorReporter.format(_linePrefix, _formatted); + } + _stream << endl; + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl; + for (auto const& test: m_tests) + { + ErrorReporter errorReporter; + _stream << test.format(errorReporter, _linePrefix, true, _formatted) << endl; + _stream << errorReporter.format(_linePrefix, _formatted); + } + AnsiColorized(_stream, _formatted, {BOLD, RED}) << _linePrefix << endl << _linePrefix + << "Attention: Updates on the test will apply the detected format displayed." << endl; + if (compileViaYul && m_runWithoutYul) + { + _stream << _linePrefix << endl << _linePrefix; + AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) << "Note that the test passed without Yul."; + _stream << endl; + } + else if (!compileViaYul && m_runWithYul) + AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) << _linePrefix << endl << _linePrefix + << "Note that the test also has to pass via Yul." << endl; + return TestResult::Failure; } } - if (!success) - { - AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; - for (auto const& test: m_tests) - { - ErrorReporter errorReporter; - _stream << test.format(errorReporter, _linePrefix, false, _formatted) << endl; - _stream << errorReporter.format(_linePrefix, _formatted); - } - _stream << endl; - AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl; - for (auto const& test: m_tests) - { - ErrorReporter errorReporter; - _stream << test.format(errorReporter, _linePrefix, true, _formatted) << endl; - _stream << errorReporter.format(_linePrefix, _formatted); - } - AnsiColorized(_stream, _formatted, {BOLD, RED}) << _linePrefix << endl << _linePrefix - << "Attention: Updates on the test will apply the detected format displayed." << endl; - return TestResult::Failure; - } return TestResult::Success; } diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index 019ca4b9f..ac9fb7dba 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -65,6 +65,8 @@ public: private: std::string m_source; std::vector m_tests; + bool m_runWithYul = false; + bool m_runWithoutYul = true; }; } From cb9c35f88347f256137e0a4ac00060978696c70d Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 19:20:31 +0200 Subject: [PATCH 031/115] Define imported targets manually, if not provided by find_package(Boost). --- cmake/EthDependencies.cmake | 11 +++++++++-- scripts/travis-emscripten/build_emscripten.sh | 2 +- scripts/travis-emscripten/install_deps.sh | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 0093be579..19b3cf835 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -28,9 +28,16 @@ set(BOOST_COMPONENTS "regex;filesystem;unit_test_framework;program_options;syste find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS}) # make sure we actually get all required imported targets for boost -list(APPEND BOOST_COMPONENTS "boost") # header only target +if (NOT TARGET Boost::boost) # header only target + add_library(Boost::boost INTERFACE IMPORTED) + target_include_directories(Boost::boost INTERFACE ${Boost_INCLUDE_DIRS}) +endif() foreach (BOOST_COMPONENT IN LISTS BOOST_COMPONENTS) if (NOT TARGET Boost::${BOOST_COMPONENT}) - message(FATAL_ERROR "Boost target Boost::${BOOST_COMPONENT} was not defined by cmake.") + add_library(Boost::${BOOST_COMPONENT} UNKNOWN IMPORTED) + string(TOUPPER ${BOOST_COMPONENT} BOOST_COMPONENT_UPPER) + set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARY}) + set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_LINK_LIBRARIES ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARIES}) + set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) endif() endforeach() diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 19554a7ed..a35a4f49d 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -64,7 +64,7 @@ fi # Boost echo -en 'travis_fold:start:compiling_boost\\r' -test -e "$WORKSPACE"/boost_1_70_0_install || ( +test -e "$WORKSPACE"/boost_1_70_0_install/include/boost/version.hpp || ( cd "$WORKSPACE"/boost_1_70_0 ./b2 toolset=emscripten link=static variant=release threading=single runtime-link=static \ --with-system --with-regex --with-filesystem --with-test --with-program_options cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \ diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index 8ceea8544..fc81851c2 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -30,7 +30,7 @@ set -ev echo -en 'travis_fold:start:installing_dependencies\\r' -test -e boost_1_70_0_install || ( +test -e boost_1_70_0_install/include/boost/version.hpp || ( rm -rf boost_1_70_0 rm -f boost.tar.gz wget -q 'https://sourceforge.net/projects/boost/files/boost/1.70.0/boost_1_70_0.tar.gz/download'\ From dcb6b2cb334448d0cc3ecfed3cc34d8c83a4f828 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 6 Jun 2019 20:47:55 +0200 Subject: [PATCH 032/115] Add back comment and output messages. --- cmake/EthDependencies.cmake | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 19b3cf835..5cf9ffcf6 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -1,3 +1,6 @@ +# all dependencies that are not directly included in the cpp-ethereum distribution are defined here +# for this to work, download the dependency via the cmake script in extdep or install them manually! + if (DEFINED MSVC) # by defining CMAKE_PREFIX_PATH variable, cmake will look for dependencies first in our own repository before looking in system paths like /usr/local/ ... # this must be set to point to the same directory as $ETH_DEPENDENCY_INSTALL_DIR in /extdep directory @@ -27,11 +30,17 @@ set(BOOST_COMPONENTS "regex;filesystem;unit_test_framework;program_options;syste find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -# make sure we actually get all required imported targets for boost +# If cmake is older than boost and boost is older than 1.70, +# find_package does not define imported targets, so we have to +# define them manually. + if (NOT TARGET Boost::boost) # header only target add_library(Boost::boost INTERFACE IMPORTED) target_include_directories(Boost::boost INTERFACE ${Boost_INCLUDE_DIRS}) endif() +get_property(LOCATION TARGET Boost::boost PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +message(STATUS "Found Boost headers in ${LOCATION}") + foreach (BOOST_COMPONENT IN LISTS BOOST_COMPONENTS) if (NOT TARGET Boost::${BOOST_COMPONENT}) add_library(Boost::${BOOST_COMPONENT} UNKNOWN IMPORTED) @@ -40,4 +49,6 @@ foreach (BOOST_COMPONENT IN LISTS BOOST_COMPONENTS) set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_LINK_LIBRARIES ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARIES}) set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) endif() + get_property(LOCATION TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION) + message(STATUS "Found Boost::${BOOST_COMPONENT} at ${LOCATION}") endforeach() From 1d9b6b5bbfb9592f2106bc2e933c9b2da6804ff8 Mon Sep 17 00:00:00 2001 From: Vignesh Karthikeyan Date: Fri, 7 Jun 2019 00:47:08 +0530 Subject: [PATCH 033/115] Added Content and Edited for readability The example for signextend requested by @bshastry was added. Codeblock was moved before warning for readability. --- docs/assembly.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 881d77650..295e39b90 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -394,17 +394,6 @@ use ``x_slot``, and to retrieve the byte-offset you use ``x_offset``. Local Solidity variables are available for assignments, for example: -.. warning:: - If you access variables of a type that spans less than 256 bits - (for example ``uint64``, ``address``, ``bytes16`` or ``byte``), - you cannot make any assumptions about bits not part of the - encoding of the type. Especially, do not assume them to be zero. - To be safe, always clear the data properly before you use it - in a context where this is important: - ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` - To clean signed types, you can use the ``signextend`` opcode: - ``assembly { signextend(0, x) }`` - .. code:: pragma solidity >=0.4.11 <0.7.0; @@ -418,6 +407,17 @@ Local Solidity variables are available for assignments, for example: } } +.. warning:: + If you access variables of a type that spans less than 256 bits + (for example ``uint64``, ``address``, ``bytes16`` or ``byte``), + you cannot make any assumptions about bits not part of the + encoding of the type. Especially, do not assume them to be zero. + To be safe, always clear the data properly before you use it + in a context where this is important: + ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` + To clean signed types, you can use the ``signextend`` opcode: + ``assembly { signextend(, x) }`` + Labels ------ From 9d2f1b0eac7b377f2bd444e48bd9046ff359d6f9 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 7 Jun 2019 10:42:18 +0200 Subject: [PATCH 034/115] Add note about boost 1.70+ and solidity<=0.5.9 to the docs. --- docs/installing-solidity.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 77521cca2..c31b21f9e 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -201,6 +201,13 @@ The following are dependencies for all builds of Solidity: .. _CMake: https://cmake.org/download/ .. _z3: https://github.com/Z3Prover/z3 +.. note:: + Solidity versions prior to 0.5.10 can fail to correctly link against Boost versions 1.70+. + A possible workaround is to temporarily rename ``/lib/cmake/Boost-1.70.0`` + prior to running the cmake command to configure solidity. + + Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention. + Prerequisites - macOS --------------------- From a4dfaac53dc8329e054f649c459fdd1350b2332b Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 7 Jun 2019 10:45:01 +0200 Subject: [PATCH 035/115] Changlelog entry. --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index c26815ad8..deed908a4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,6 +15,8 @@ Bugfixes: Build System: * Attempt to use stock Z3 cmake files to find Z3 and only fall back to manual discovery. * Generate a cmake error for gcc versions older than 5.0. + * CMake: use imported targets for boost. + * Emscripten build: upgrade to boost 1.70. From 60332c6469f5a8199632e54a87c837bba3abd72b Mon Sep 17 00:00:00 2001 From: Anurag Dashputre Date: Tue, 4 Jun 2019 13:05:57 +0530 Subject: [PATCH 036/115] Extract semantic tests for 1. Transaction Status 2. Empty Contract 3. Smoke test for Range --- test/libsolidity/SolidityEndToEndTest.cpp | 41 ------------------- .../semanticTests/empty_contract.sol | 6 +++ .../functionCall/transaction_status.sol | 9 ++++ test/libsolidity/semanticTests/smoke_test.sol | 5 +++ 4 files changed, 20 insertions(+), 41 deletions(-) create mode 100644 test/libsolidity/semanticTests/empty_contract.sol create mode 100644 test/libsolidity/semanticTests/functionCall/transaction_status.sol diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7ca924ac1..bee191fcb 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -64,47 +64,6 @@ int constexpr roundTo32(int _num) return (_num + 31) / 32 * 32; } -BOOST_AUTO_TEST_CASE(transaction_status) -{ - char const* sourceCode = R"( - contract test { - function f() public { } - function g() public { revert(); } - function h() public { assert(false); } - } - )"; - compileAndRun(sourceCode); - callContractFunction("f()"); - BOOST_CHECK(m_transactionSuccessful); - callContractFunction("g()"); - BOOST_CHECK(!m_transactionSuccessful); - callContractFunction("h()"); - BOOST_CHECK(!m_transactionSuccessful); -} - - -BOOST_AUTO_TEST_CASE(smoke_test) -{ - char const* sourceCode = R"( - contract test { - function f(uint a) public returns(uint d) { return a * 7; } - } - )"; - compileAndRun(sourceCode); - testContractAgainstCppOnRange("f(uint256)", [](u256 const& a) -> u256 { return a * 7; }, 0, 100); -} - -BOOST_AUTO_TEST_CASE(empty_contract) -{ - char const* sourceCode = R"( - contract test { } - )"; - ALSO_VIA_YUL( - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("i_am_not_there()", bytes()).empty()); - ) -} - BOOST_AUTO_TEST_CASE(exp_operator) { char const* sourceCode = R"( diff --git a/test/libsolidity/semanticTests/empty_contract.sol b/test/libsolidity/semanticTests/empty_contract.sol new file mode 100644 index 000000000..8950f01d1 --- /dev/null +++ b/test/libsolidity/semanticTests/empty_contract.sol @@ -0,0 +1,6 @@ +contract test { +} +// ==== +// compileViaYul: also +// ---- +// i_am_not_there() -> FAILURE diff --git a/test/libsolidity/semanticTests/functionCall/transaction_status.sol b/test/libsolidity/semanticTests/functionCall/transaction_status.sol new file mode 100644 index 000000000..6651630d5 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/transaction_status.sol @@ -0,0 +1,9 @@ +contract test { + function f() public { } + function g() public { revert(); } + function h() public { assert(false); } +} +// ---- +// f() -> +// g() -> FAILURE +// h() -> FAILURE diff --git a/test/libsolidity/semanticTests/smoke_test.sol b/test/libsolidity/semanticTests/smoke_test.sol index 7a87151b3..6e5cd9c6a 100644 --- a/test/libsolidity/semanticTests/smoke_test.sol +++ b/test/libsolidity/semanticTests/smoke_test.sol @@ -33,6 +33,9 @@ contract C { function p() public returns (string memory, uint, string memory) { return ("any", 42, "any"); } + function q(uint a) public returns (uint d) { + return a * 7; + } } // ---- // constructor(): 3 -> @@ -51,3 +54,5 @@ contract C { // n() -> 0x20, 3, "any" // o() -> 0x40, 0x80, 3, "any", 3, "any" // p() -> 0x60, 0x2a, 0xa0, 3, "any", 3, "any" +// q(uint256): 0 -> 0 +// q(uint256): 99 -> 693 From 547173533c13a4c1f5163ede49e756c88ba78567 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Tue, 11 Jun 2019 14:05:45 +0200 Subject: [PATCH 037/115] Unify parsing of simple test expectations and require lines to start with ``//``. --- test/TestCase.cpp | 14 ++++++++++++++ test/TestCase.h | 2 ++ test/libyul/ObjectCompilerTest.cpp | 6 +----- test/libyul/YulInterpreterTest.cpp | 8 +------- test/libyul/YulOptimizerTest.cpp | 7 +------ 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/test/TestCase.cpp b/test/TestCase.cpp index c86635735..76c27d41a 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -95,6 +95,20 @@ string TestCase::parseSourceAndSettings(istream& _stream) return source; } +string TestCase::parseSimpleExpectations(std::istream& _file) +{ + string result; + string line; + while (getline(_file, line)) + if (boost::algorithm::starts_with(line, "// ")) + result += line.substr(3) + "\n"; + else if (line == "//") + result += "\n"; + else + BOOST_THROW_EXCEPTION(runtime_error("Test expectations must start with \"// \".")); + return result; +} + void TestCase::expect(string::iterator& _it, string::iterator _end, string::value_type _c) { if (_it == _end || *_it != _c) diff --git a/test/TestCase.h b/test/TestCase.h index ba5669506..c08f2d77e 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -92,6 +92,8 @@ protected: std::string parseSourceAndSettings(std::istream& _file); static void expect(std::string::iterator& _it, std::string::iterator _end, std::string::value_type _c); + static std::string parseSimpleExpectations(std::istream& _file); + template static void skipWhitespace(IteratorType& _it, IteratorType _end) { diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index f33416c09..de44cc67f 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -55,11 +55,7 @@ ObjectCompilerTest::ObjectCompilerTest(string const& _filename) m_optimize = true; m_source += line + "\n"; } - while (getline(file, line)) - if (boost::algorithm::starts_with(line, "//")) - m_expectation += line.substr((line.size() >= 3 && line[2] == ' ') ? 3 : 2) + "\n"; - else - m_expectation += line + "\n"; + m_expectation = parseSimpleExpectations(file); } TestCase::TestResult ObjectCompilerTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index d473c3a5c..141dd1ae2 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -54,13 +54,7 @@ YulInterpreterTest::YulInterpreterTest(string const& _filename) file.exceptions(ios::badbit); m_source = parseSourceAndSettings(file); - - string line; - while (getline(file, line)) - if (boost::algorithm::starts_with(line, "// ")) - m_expectation += line.substr(3) + "\n"; - else - m_expectation += line + "\n"; + m_expectation = parseSimpleExpectations(file); } TestCase::TestResult YulInterpreterTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index e5552d675..3d2cd50eb 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -99,12 +99,7 @@ YulOptimizerTest::YulOptimizerTest(string const& _filename) m_settings.erase("step"); } - string line; - while (getline(file, line)) - if (boost::algorithm::starts_with(line, "// ")) - m_expectation += line.substr(3) + "\n"; - else - m_expectation += line + "\n"; + m_expectation = parseSimpleExpectations(file); } TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) From ba8ad1a1d9a7c5f180c3d142251ba319085a6721 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Tue, 11 Jun 2019 14:11:27 +0200 Subject: [PATCH 038/115] Use the TestCase settings mechanism for object compiler tests. --- test/libyul/ObjectCompilerTest.cpp | 12 +++++------- test/libyul/objectCompiler/nested_optimizer.yul | 15 ++++++++------- test/libyul/objectCompiler/simple_optimizer.yul | 9 +++++---- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index de44cc67f..9773dedab 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -46,14 +46,12 @@ ObjectCompilerTest::ObjectCompilerTest(string const& _filename) BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\".")); file.exceptions(ios::badbit); - string line; - while (getline(file, line)) + m_source = parseSourceAndSettings(file); + if (m_settings.count("optimize")) { - if (boost::algorithm::starts_with(line, "// ----")) - break; - if (m_source.empty() && boost::algorithm::starts_with(line, "// optimize")) - m_optimize = true; - m_source += line + "\n"; + m_optimize = true; + m_validatedSettings["optimize"] = "true"; + m_settings.erase("optimize"); } m_expectation = parseSimpleExpectations(file); } diff --git a/test/libyul/objectCompiler/nested_optimizer.yul b/test/libyul/objectCompiler/nested_optimizer.yul index 2775c346d..1d5e568f1 100644 --- a/test/libyul/objectCompiler/nested_optimizer.yul +++ b/test/libyul/objectCompiler/nested_optimizer.yul @@ -1,4 +1,3 @@ -// optimize object "a" { code { let x := calldataload(0) @@ -15,24 +14,26 @@ object "a" { } } } +// ==== +// optimize: true // ---- // Assembly: -// /* "source":60:61 */ +// /* "source":48:49 */ // 0x00 // 0x00 -// /* "source":47:62 */ +// /* "source":35:50 */ // calldataload -// /* "source":119:139 */ +// /* "source":107:127 */ // sstore // stop // // sub_0: assembly { -// /* "source":200:201 */ +// /* "source":188:189 */ // 0x00 // 0x00 -// /* "source":187:202 */ +// /* "source":175:190 */ // calldataload -// /* "source":265:285 */ +// /* "source":253:273 */ // sstore // } // Bytecode: 600060003555fe diff --git a/test/libyul/objectCompiler/simple_optimizer.yul b/test/libyul/objectCompiler/simple_optimizer.yul index c757dee71..3d00e45d3 100644 --- a/test/libyul/objectCompiler/simple_optimizer.yul +++ b/test/libyul/objectCompiler/simple_optimizer.yul @@ -1,18 +1,19 @@ -// optimize { let x := calldataload(0) let y := calldataload(0) let z := sub(y, x) sstore(add(x, 0), z) } +// ==== +// optimize: true // ---- // Assembly: -// /* "source":38:39 */ +// /* "source":26:27 */ // 0x00 // 0x00 -// /* "source":25:40 */ +// /* "source":13:28 */ // calldataload -// /* "source":91:111 */ +// /* "source":79:99 */ // sstore // Bytecode: 600060003555 // Opcodes: PUSH1 0x0 PUSH1 0x0 CALLDATALOAD SSTORE From 073777e836102efd620ca69da3a18219d8f98307 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Tue, 11 Jun 2019 15:09:52 +0200 Subject: [PATCH 039/115] Some keccak tests. --- test/libdevcore/Keccak256.cpp | 76 +++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 test/libdevcore/Keccak256.cpp diff --git a/test/libdevcore/Keccak256.cpp b/test/libdevcore/Keccak256.cpp new file mode 100644 index 000000000..b52a458eb --- /dev/null +++ b/test/libdevcore/Keccak256.cpp @@ -0,0 +1,76 @@ +/* + 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 . +*/ +/** + * Unit tests for keccak256. + */ +#include + +#include + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(Keccak256) + +BOOST_AUTO_TEST_CASE(empty) +{ + BOOST_CHECK_EQUAL( + keccak256(bytes()), + FixedHash<32>("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + ); +} + +BOOST_AUTO_TEST_CASE(zeros) +{ + BOOST_CHECK_EQUAL( + keccak256(bytes(1, '\0')), + FixedHash<32>("0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a") + ); + BOOST_CHECK_EQUAL( + keccak256(bytes(2, '\0')), + FixedHash<32>("0x54a8c0ab653c15bfb48b47fd011ba2b9617af01cb45cab344acd57c924d56798") + ); + BOOST_CHECK_EQUAL( + keccak256(bytes(5, '\0')), + FixedHash<32>("0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec") + ); + BOOST_CHECK_EQUAL( + keccak256(bytes(10, '\0')), + FixedHash<32>("0x6bd2dd6bd408cbee33429358bf24fdc64612fbf8b1b4db604518f40ffd34b607") + ); +} + +BOOST_AUTO_TEST_CASE(strings) +{ + BOOST_CHECK_EQUAL( + keccak256("test"), + FixedHash<32>("0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + ); + BOOST_CHECK_EQUAL( + keccak256("longer test string"), + FixedHash<32>("0x47bed17bfbbc08d6b5a0f603eff1b3e932c37c10b865847a7bc73d55b260f32a") + ); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} From 89c435a16730b19520fc41accd12dbb4a944c0a8 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Wed, 5 Jun 2019 19:32:30 +0200 Subject: [PATCH 040/115] [Sol->Yul] Make IRStorageItem work with dynamic offsets --- libsolidity/codegen/YulUtilFunctions.cpp | 151 +++++++++++++++++++++++ libsolidity/codegen/YulUtilFunctions.h | 13 ++ libsolidity/codegen/ir/IRLValue.cpp | 87 +++++++------ libsolidity/codegen/ir/IRLValue.h | 15 ++- 4 files changed, 228 insertions(+), 38 deletions(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 2fe363e1b..43391f66b 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -254,6 +254,27 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits) } } +string YulUtilFunctions::dynamicShiftLeftFunction() +{ + string functionName = "shift_left"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (bits, value) -> newValue { + newValue := + + shl(bits, value) + + mul(value, exp(2, bits)) + + } + )") + ("functionName", functionName) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + .render(); + }); +} + string YulUtilFunctions::shiftRightFunction(size_t _numBits) { solAssert(_numBits < 256, ""); @@ -282,6 +303,30 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) }); } +string YulUtilFunctions::dynamicShiftRightFunction() +{ + // Note that if this is extended with signed shifts, + // the opcodes SAR and SDIV behave differently with regards to rounding! + + string const functionName = "shift_right_unsigned"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (bits, value) -> newValue { + newValue := + + shr(bits, value) + + div(value, exp(2, bits)) + + } + )") + ("functionName", functionName) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + .render(); + }); +} + string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) { solAssert(_numBytes <= 32, ""); @@ -306,6 +351,29 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift }); } +string YulUtilFunctions::dynamicUpdateByteSliceFunction(size_t _numBytes) +{ + solAssert(_numBytes <= 32, ""); + size_t numBits = _numBytes * 8; + string functionName = "update_byte_slice_" + to_string(_numBytes); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value, shiftBytes, toInsert) -> result { + let shiftBits := mul(shiftBytes, 8) + let mask := (shiftBits, ) + toInsert := (shiftBits, toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + )") + ("functionName", functionName) + ("mask", formatNumber((bigint(1) << numBits) - 1)) + ("shl", dynamicShiftLeftFunction()) + .render(); + }); +} + string YulUtilFunctions::roundUpFunction() { string functionName = "round_up_to_mul_of_32"; @@ -613,6 +681,89 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool }); } +string YulUtilFunctions::dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes) +{ + solUnimplementedAssert(!_splitFunctionTypes, ""); + string functionName = + "read_from_storage_" + + string(_splitFunctionTypes ? "split_" : "") + + "_" + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + solAssert(_type.sizeOnStack() == 1, ""); + return Whiskers(R"( + function (slot, offset) -> value { + value := (sload(slot), offset) + } + )") + ("functionName", functionName) + ("extract", dynamicExtractFromStorageValue(_type, false)) + .render(); + }); +} + +string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional const _offset) +{ + string const functionName = + "update_storage_value_" + + (_offset.is_initialized() ? ("offset_" + to_string(*_offset)) : "") + + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&] { + if (_type.isValueType()) + { + solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size."); + solAssert(_type.storageBytes() > 0, "Invalid storage bytes size."); + + return Whiskers(R"( + function (slot, value) { + sstore(slot, (sload(slot), (value))) + } + + )") + ("functionName", functionName) + ("update", + _offset.is_initialized() ? + updateByteSliceFunction(_type.storageBytes(), *_offset) : + dynamicUpdateByteSliceFunction(_type.storageBytes()) + ) + ("offset", _offset.is_initialized() ? "" : "offset, ") + ("prepare", prepareStoreFunction(_type)) + .render(); + } + else + { + if (_type.category() == Type::Category::Array) + solUnimplementedAssert(false, ""); + else if (_type.category() == Type::Category::Struct) + solUnimplementedAssert(false, ""); + else + solAssert(false, "Invalid non-value type for assignment."); + } + }); +} + +string YulUtilFunctions::dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes) +{ + solUnimplementedAssert(!_splitFunctionTypes, ""); + + string functionName = + "extract_from_storage_value_" + + string(_splitFunctionTypes ? "split_" : "") + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + return Whiskers(R"( + function (slot_value, offset) -> value { + value := ((mul(offset, 8), slot_value)) + } + )") + ("functionName", functionName) + ("shr", dynamicShiftRightFunction()) + ("cleanupStorage", cleanupFromStorageFunction(_type, false)) + .render(); + }); +} + string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 245d29c12..b4e0dc727 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -74,13 +74,18 @@ public: std::string leftAlignFunction(Type const& _type); std::string shiftLeftFunction(size_t _numBits); + std::string dynamicShiftLeftFunction(); std::string shiftRightFunction(size_t _numBits); + std::string dynamicShiftRightFunction(); /// @returns the name of a function f(value, toInsert) -> newValue which replaces the /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// byte) by the _numBytes least significant bytes of `toInsert`. std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); + /// signature: (value, shiftBytes, toInsert) -> result + std::string dynamicUpdateByteSliceFunction(size_t _numBytes); + /// @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(); @@ -121,6 +126,7 @@ public: /// @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); + std::string dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes); /// @returns a function that extracts a value type from storage slot that has been /// retrieved already. @@ -128,6 +134,13 @@ public: /// @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); + std::string dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes); + + /// Returns the name of a function will write the given value to + /// the specified slot and offset. If offset is not given, it is expected as + /// runtime parameter. + /// signature: (slot, [offset,] value) + std::string updateStorageValueFunction(Type const& _type, boost::optional const _offset = boost::optional()); /// Performs cleanup after reading from a potentially compressed storage slot. /// The function does not perform any validation, it just masks or sign-extends diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index 4c3fe1acf..ff8e4bb13 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -54,26 +54,37 @@ string IRLocalVariable::setToZero() const IRStorageItem::IRStorageItem( IRGenerationContext& _context, VariableDeclaration const& _varDecl -): - IRLValue(_context, _varDecl.annotation().type) +) +:IRStorageItem( + _context, + *_varDecl.annotation().type, + _context.storageLocationOfVariable(_varDecl) +) +{ } + +IRStorageItem::IRStorageItem( + IRGenerationContext& _context, + Type const& _type, + std::pair slot_offset +) +: IRLValue(_context, &_type), + m_slot(toCompactHexWithPrefix(slot_offset.first)), + m_offset(slot_offset.second) { - u256 slot; - unsigned offset; - std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl); - m_slot = toCompactHexWithPrefix(slot); - m_offset = offset; } IRStorageItem::IRStorageItem( IRGenerationContext& _context, string _slot, - unsigned _offset, + boost::variant _offset, Type const& _type ): IRLValue(_context, &_type), m_slot(move(_slot)), - m_offset(_offset) + m_offset(std::move(_offset)) { + solAssert(!m_offset.empty(), ""); + solAssert(!m_slot.empty(), ""); } string IRStorageItem::retrieveValue() const @@ -81,39 +92,45 @@ string IRStorageItem::retrieveValue() const if (!m_type->isValueType()) return m_slot; solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); - return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")"; + if (m_offset.type() == typeid(string)) + return + m_context.utils().dynamicReadFromStorage(*m_type, false) + + "(" + + m_slot + + ", " + + boost::get(m_offset) + + ")"; + else if (m_offset.type() == typeid(unsigned)) + return + m_context.utils().readFromStorage(*m_type, boost::get(m_offset), false) + + "(" + + m_slot + + ")"; + + solAssert(false, ""); } string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const { if (m_type->isValueType()) - { - solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size."); - solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size."); - solAssert(m_type->storageBytes() + m_offset <= 32, ""); - solAssert(_sourceType == *m_type, "Different type, but might not be an error."); - return Whiskers("sstore(, (sload(), ()))\n") - ("slot", m_slot) - ("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset)) - ("prepare", m_context.utils().prepareStoreFunction(*m_type)) - ("value", _value) - .render(); - } - else - { - solAssert( - _sourceType.category() == m_type->category(), - "Wrong type conversation for assignment." - ); - if (m_type->category() == Type::Category::Array) - solUnimplementedAssert(false, ""); - else if (m_type->category() == Type::Category::Struct) - solUnimplementedAssert(false, ""); - else - solAssert(false, "Invalid non-value type for assignment."); - } + boost::optional offset; + + if (m_offset.type() == typeid(unsigned)) + offset = get(m_offset); + + return + m_context.utils().updateStorageValueFunction(*m_type, offset) + + "(" + + m_slot + + (m_offset.type() == typeid(string) ? + (", " + get(m_offset)) : + "" + ) + + ", " + + _value + + ")\n"; } string IRStorageItem::setToZero() const diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index 98ad987f1..803223fe7 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -20,8 +20,11 @@ #pragma once +#include + #include #include +#include namespace dev { @@ -83,7 +86,7 @@ public: IRStorageItem( IRGenerationContext& _context, std::string _slot, - unsigned _offset, + boost::variant _offset, Type const& _type ); std::string retrieveValue() const override; @@ -91,8 +94,14 @@ public: std::string setToZero() const override; private: - std::string m_slot; - unsigned m_offset; + IRStorageItem( + IRGenerationContext& _context, + Type const& _type, + std::pair slot_offset + ); + + std::string const m_slot; + boost::variant const m_offset; }; From 6a0976ed5eafa3dfb297fdd12903733c5491b1e2 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 6 Jun 2019 17:59:30 +0200 Subject: [PATCH 041/115] [Sol->Yul] Refactor shift functions to be consistent --- libsolidity/codegen/YulUtilFunctions.cpp | 48 ++++++++++-------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 43391f66b..65991f864 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -224,34 +224,24 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits) solAssert(_numBits < 256, ""); string functionName = "shift_left_" + to_string(_numBits); - if (m_evmVersion.hasBitwiseShifting()) - { - return m_functionCollector->createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shl(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return m_functionCollector->createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := mul(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := + + shl(, value) + + mul(value, ) + + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); } string YulUtilFunctions::dynamicShiftLeftFunction() @@ -282,7 +272,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) // Note that if this is extended with signed shifts, // the opcodes SAR and SDIV behave differently with regards to rounding! - string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name(); + string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( From 9e23d6d05e58b1c59052c630a4aff0afc89bb4b7 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 23 May 2019 16:38:11 +0200 Subject: [PATCH 042/115] [Sol->Yul] Compile some tests with yul --- test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol | 2 ++ test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol | 2 ++ test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol | 2 ++ .../semanticTests/functionCall/named_args_overload.sol | 2 ++ test/libsolidity/semanticTests/shifts.sol | 1 + test/libsolidity/semanticTests/smoke_test_multiline.sol | 2 ++ .../libsolidity/semanticTests/smoke_test_multiline_comments.sol | 2 ++ 7 files changed, 13 insertions(+) diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol index d74db942c..f1f57aca9 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol @@ -9,5 +9,7 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f() -> 6 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol index f25a3c06a..96277887e 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol @@ -9,5 +9,7 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f() -> 5 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol index 3a13e9f0e..b8d35bb8f 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol @@ -13,6 +13,8 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f(uint256): 0 -> 2 // f(uint256): 1 -> 18 diff --git a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol index f2016c967..b77e5be69 100644 --- a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol +++ b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol @@ -24,6 +24,8 @@ contract C { return 500; } } +// ==== +// compileViaYul: also // ---- // call(uint256): 0 -> 0 // call(uint256): 1 -> 1 diff --git a/test/libsolidity/semanticTests/shifts.sol b/test/libsolidity/semanticTests/shifts.sol index e202f2889..4f8892250 100644 --- a/test/libsolidity/semanticTests/shifts.sol +++ b/test/libsolidity/semanticTests/shifts.sol @@ -4,6 +4,7 @@ contract C { } } // ==== +// compileViaYul: also // EVMVersion: >=constantinople // ---- // f(uint256): 7 -> 28 diff --git a/test/libsolidity/semanticTests/smoke_test_multiline.sol b/test/libsolidity/semanticTests/smoke_test_multiline.sol index 7395b1c3b..5d02e148e 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline.sol +++ b/test/libsolidity/semanticTests/smoke_test_multiline.sol @@ -3,6 +3,8 @@ contract C { return a + b + c + d + e; } } +// ==== +// compileViaYul: also // ---- // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // -> 5 diff --git a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol b/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol index 17de40fc4..66bdfb915 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol +++ b/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol @@ -3,6 +3,8 @@ contract C { return a + b + c + d + e; } } +// ==== +// compileViaYul: also // ---- // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // # A comment on the function parameters. # From 7a32daadf09dc43cbae9c8f4c7ab469e59c04ea6 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 18:04:08 +0200 Subject: [PATCH 043/115] Make the boost cmake workaround compatible with older versions of cmake. --- cmake/EthDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 5cf9ffcf6..53319e6c8 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -36,7 +36,7 @@ find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS}) if (NOT TARGET Boost::boost) # header only target add_library(Boost::boost INTERFACE IMPORTED) - target_include_directories(Boost::boost INTERFACE ${Boost_INCLUDE_DIRS}) + set_property(TARGET Boost::boost APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) endif() get_property(LOCATION TARGET Boost::boost PROPERTY INTERFACE_INCLUDE_DIRECTORIES) message(STATUS "Found Boost headers in ${LOCATION}") From 2dc405e9481c281cae2c5cbac719c73c2387b4c6 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 19:03:45 +0200 Subject: [PATCH 044/115] Fix command line tests script to properly report errors. --- test/cmdlineTests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index fc3ab9558..89290b357 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -112,6 +112,8 @@ function ask_expectation_update() q* ) exit 1;; esac done + else + exit 1 fi } From dfbdb6c007a6b2483512abe43e13a3f29797a7bf Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 19:04:02 +0200 Subject: [PATCH 045/115] Fix failing command line tests. --- test/cmdlineTests/standard_irOptimized_requested/output.json | 2 +- test/cmdlineTests/standard_ir_requested/output.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cmdlineTests/standard_irOptimized_requested/output.json b/test/cmdlineTests/standard_irOptimized_requested/output.json index 123b975df..ef9af5ab4 100644 --- a/test/cmdlineTests/standard_irOptimized_requested/output.json +++ b/test/cmdlineTests/standard_irOptimized_requested/output.json @@ -1 +1 @@ -{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned_petersburg(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue() { revert(0, 0) }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default { }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n { tail := add(headStart, 0) }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n function shift_right_224_unsigned_petersburg(value) -> newValue\n { newValue := shr(224, value) }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}} +{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue() { revert(0, 0) }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default { }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n { tail := add(headStart, 0) }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n function shift_right_224_unsigned(value) -> newValue\n { newValue := shr(224, value) }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index 1294cdc50..6dad16f40 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -1 +1 @@ -{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned_petersburg(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tfunction shift_right_224_unsigned_petersburg(value) -> newValue {\n\t\t\t\tnewValue :=\n\t\t\t\t\n\t\t\t\t\tshr(224, value)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}} +{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\tnewValue :=\n\t\t\t\t\n\t\t\t\t\tshr(224, value)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}} From 98d852c832510142cea80f55e3697b3d59495da9 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 27 May 2019 15:10:17 +0200 Subject: [PATCH 046/115] Fixes compilation on Visual Studio 2019 (SolidityEndToEndTest.cpp's object file is too large, so we need a special flag to get it still compiling) --- test/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 30574a4eb..a666fb5f4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,6 +32,14 @@ add_executable(soltest ${sources} ${headers} ) target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore Boost::boost Boost::program_options Boost::unit_test_framework) +# Special compilation flag for Visual Studio (version 2019 at least affected) +# in order to compile SolidityEndToEndTest.cpp, which is quite huge. +# We can remove this flag once we've extracted the tests. +# TODO: Remove this option as soon as we have reduced the SLoC's in SolidityEndToEndTest.cpp +if (MSVC) + target_compile_options(soltest PUBLIC "/bigobj") +endif() + if (LLL) target_link_libraries(soltest PRIVATE lll) target_compile_definitions(soltest PRIVATE HAVE_LLL=1) From 2a2f0685424a9e76228d2808db11ae340c691525 Mon Sep 17 00:00:00 2001 From: Fabio Bonfiglio <48284219+FabioBonfiglio@users.noreply.github.com> Date: Thu, 13 Jun 2019 13:12:32 +0200 Subject: [PATCH 047/115] Correct assigned value in natspec example --- docs/natspec-format.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/natspec-format.rst b/docs/natspec-format.rst index d55547050..297d9954c 100644 --- a/docs/natspec-format.rst +++ b/docs/natspec-format.rst @@ -108,7 +108,7 @@ to the end-user as: This function will multiply 10 by 7 -if a function is being called and the input ``a`` is assigned a value of 7. +if a function is being called and the input ``a`` is assigned a value of 10. Specifying these dynamic expressions is outside the scope of the Solidity documentation and you may read more at From 5089d4ac28259fb48ae7840aca4867f9afda336d Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Thu, 13 Jun 2019 16:43:11 +0200 Subject: [PATCH 048/115] Move optimization proofs repo to Solidity repo --- test/formal/README.md | 6 +++ test/formal/combine_div_shl_one_32.py | 27 +++++++++++ test/formal/combine_mul_shl_one_64.py | 33 +++++++++++++ test/formal/combine_shl_shr_by_constant_64.py | 44 ++++++++++++++++++ test/formal/combine_shr_shl_by_constant_64.py | 44 ++++++++++++++++++ test/formal/move_and_across_shl_128.py | 36 +++++++++++++++ test/formal/move_and_across_shr_128.py | 36 +++++++++++++++ test/formal/opcodes.py | 46 +++++++++++++++++++ test/formal/rule.py | 38 +++++++++++++++ test/formal/shl_workaround_8.py | 27 +++++++++++ 10 files changed, 337 insertions(+) create mode 100644 test/formal/README.md create mode 100644 test/formal/combine_div_shl_one_32.py create mode 100644 test/formal/combine_mul_shl_one_64.py create mode 100644 test/formal/combine_shl_shr_by_constant_64.py create mode 100644 test/formal/combine_shr_shl_by_constant_64.py create mode 100644 test/formal/move_and_across_shl_128.py create mode 100644 test/formal/move_and_across_shr_128.py create mode 100644 test/formal/opcodes.py create mode 100644 test/formal/rule.py create mode 100644 test/formal/shl_workaround_8.py diff --git a/test/formal/README.md b/test/formal/README.md new file mode 100644 index 000000000..83bd79e76 --- /dev/null +++ b/test/formal/README.md @@ -0,0 +1,6 @@ +The Solidity compiler implements several [optimization rules](https://github.com/ethereum/solidity/blob/develop/libevmasm/RuleList.h). + +This directory contains an effort to formally prove the correctness of those rules in: + +- HOL with [EthIsabelle](https://github.com/ekpyron/eth-isabelle) +- FOL with SMT solvers using [Integers and BitVectors](http://smtlib.cs.uiowa.edu/theories.shtml) diff --git a/test/formal/combine_div_shl_one_32.py b/test/formal/combine_div_shl_one_32.py new file mode 100644 index 000000000..23c65c8d7 --- /dev/null +++ b/test/formal/combine_div_shl_one_32.py @@ -0,0 +1,27 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +DIV(X, SHL(Y, 1)) -> SHR(Y, X) +Requirements: +""" + +rule = Rule() + +n_bits = 32 + +# Input vars +X = BitVec('X', n_bits) +Y = BitVec('Y', n_bits) + +# Constants +ONE = BitVecVal(1, n_bits) + +# Non optimized result +nonopt = DIV(X, SHL(Y, ONE)) + +# Optimized result +opt = SHR(Y, X) + +rule.check(nonopt, opt) diff --git a/test/formal/combine_mul_shl_one_64.py b/test/formal/combine_mul_shl_one_64.py new file mode 100644 index 000000000..6f87cfcdb --- /dev/null +++ b/test/formal/combine_mul_shl_one_64.py @@ -0,0 +1,33 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +MUL(X, SHL(Y, 1)) -> SHL(Y, X) +MUL(SHL(X, 1), Y) -> SHL(X, Y) +Requirements: +""" + +rule = Rule() + +n_bits = 64 + +# Input vars +X = BitVec('X', n_bits) +Y = BitVec('Y', n_bits) + +# Constants +ONE = BitVecVal(1, n_bits) + +# Requirements + +# Non optimized result +nonopt_1 = MUL(X, SHL(Y, ONE)) +nonopt_2 = MUL(SHL(X, ONE), Y) + +# Optimized result +opt_1 = SHL(Y, X) +opt_2 = SHL(X, Y) + +rule.check(nonopt_1, opt_1) +rule.check(nonopt_2, opt_2) diff --git a/test/formal/combine_shl_shr_by_constant_64.py b/test/formal/combine_shl_shr_by_constant_64.py new file mode 100644 index 000000000..fc7ec64e8 --- /dev/null +++ b/test/formal/combine_shl_shr_by_constant_64.py @@ -0,0 +1,44 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +mask = shlWorkaround(u256(-1), unsigned(A.d())) >> unsigned(B.d()) +SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) +Requirements: +A < BitWidth +B < BitWidth +""" + +rule = Rule() + +n_bits = 64 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(A, BitWidth)) +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt = SHR(B, SHL(A, X)) + +# Optimized result +Mask = SHR(B, SHL(A, Int2BV(IntVal(-1), n_bits))) +opt = If( + UGT(A, B), + AND(SHL(A - B, X), Mask), + If( + UGT(B, A), + AND(SHR(B - A, X), Mask), + AND(X, Mask) + ) + ) + +rule.check(nonopt, opt) diff --git a/test/formal/combine_shr_shl_by_constant_64.py b/test/formal/combine_shr_shl_by_constant_64.py new file mode 100644 index 000000000..31aa60956 --- /dev/null +++ b/test/formal/combine_shr_shl_by_constant_64.py @@ -0,0 +1,44 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +mask = shlWorkaround(u256(-1) >> unsigned(A.d()), unsigned(B.d())) +SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) +Requirements: +A < BitWidth +B < BitWidth +""" + +rule = Rule() + +n_bits = 64 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(A, BitWidth)) +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt = SHL(B, SHR(A, X)) + +# Optimized result +Mask = SHL(B, SHR(A, Int2BV(IntVal(-1), n_bits))) +opt = If( + UGT(A, B), + AND(SHR(a - b, x), Mask), + If( + UGT(B, A), + AND(SHL(B - A, X), Mask), + AND(X, Mask) + ) + ) + +rule.check(nonopt, opt) diff --git a/test/formal/move_and_across_shl_128.py b/test/formal/move_and_across_shl_128.py new file mode 100644 index 000000000..d3f5c3a66 --- /dev/null +++ b/test/formal/move_and_across_shl_128.py @@ -0,0 +1,36 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +SHL(B, AND(X, A)) -> AND(SHL(B, X), A << B) +SHL(B, AND(A, X)) -> AND(SHL(B, X), A << B) +Requirements: +B < BitWidth +""" + +rule = Rule() + +n_bits = 128 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt_1 = SHL(B, AND(X, A)) +nonopt_2 = SHL(B, AND(A, X)) + +# Optimized result +Mask = SHL(B, A) +opt = AND(SHL(B, X), Mask) + +rule.check(nonopt_1, opt) +rule.check(nonopt_2, opt) diff --git a/test/formal/move_and_across_shr_128.py b/test/formal/move_and_across_shr_128.py new file mode 100644 index 000000000..df673ff59 --- /dev/null +++ b/test/formal/move_and_across_shr_128.py @@ -0,0 +1,36 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +SHR(B, AND(X, A)) -> AND(SHR(B, X), A >> B) +SHR(B, AND(A, X)) -> AND(SHR(B, X), A >> B) +Requirements: +B < BitWidth +""" + +rule = Rule() + +n_bits = 128 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt_1 = SHR(B, AND(X, A)); +nonopt_2 = SHR(B, AND(A, X)); + +# Optimized result +Mask = SHR(B, A); +opt = AND(SHR(B, X), Mask); + +rule.check(nonopt_1, opt) +rule.check(nonopt_2, opt) diff --git a/test/formal/opcodes.py b/test/formal/opcodes.py new file mode 100644 index 000000000..faa9de1d9 --- /dev/null +++ b/test/formal/opcodes.py @@ -0,0 +1,46 @@ +from z3 import * + +def ADD(x, y): + return x + y + +def MUL(x, y): + return x * y + +def SUB(x, y): + return x - y + +def DIV(x, y): + return If(y == 0, 0, UDiv(x, y)) + +def SDIV(x, y): + return If(y == 0, 0, x / y) + +def MOD(x, y): + return If(y == 0, 0, URem(x, y)) + +def SMOD(x, y): + return If(y == 0, 0, x % y) + +def LT(x, y): + return ULT(x, y) + +def GT(x, y): + return UGT(x, y) + +def SLT(x, y): + return x < y + +def SGT(x, y): + return x > y + +def AND(x, y): + return x & y + +def SHL(x, y): + return y << x + +def SHR(x, y): + return LShR(y, x) + +def SAR(x, y): + return y >> x diff --git a/test/formal/rule.py b/test/formal/rule.py new file mode 100644 index 000000000..e434fcb3f --- /dev/null +++ b/test/formal/rule.py @@ -0,0 +1,38 @@ +from z3 import * + +class Rule: + def __init__(self): + self.requirements = [] + self.constraints = [] + self.solver = Solver() + self.setTimeout(60000) + + def setTimeout(self, _t): + self.solver.set("timeout", _t) + + def __lshift__(self, _c): + self.constraints.append(_c) + + def require(self, _r): + self.requirements.append(_r) + + def check(self, _nonopt, _opt): + self.solver.add(self.requirements) + result = self.solver.check() + + if result == unknown: + raise BaseException('Unable to satisfy requirements.') + elif result == unsat: + raise BaseException('Requirements are unsatisfiable.') + + self.solver.push() + self.solver.add(self.constraints) + self.solver.add(_nonopt != _opt) + + result = self.solver.check() + if result == unknown: + raise BaseException('Unable to prove rule.') + elif result == sat: + m = self.solver.model() + raise BaseException('Rule is incorrect.\nModel: ' + str(m)) + self.solver.pop() diff --git a/test/formal/shl_workaround_8.py b/test/formal/shl_workaround_8.py new file mode 100644 index 000000000..2ca711cdf --- /dev/null +++ b/test/formal/shl_workaround_8.py @@ -0,0 +1,27 @@ +from rule import Rule +from opcodes import * + +""" +Shift left workaround that Solidity implements +due to a bug in Boost. +""" + +rule = Rule() + +n_bits = 8 +bigint_bits = 16 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', bigint_bits) + +# Compute workaround +workaround = Int2BV( + BV2Int( + (Int2BV(BV2Int(X), bigint_bits) << Int2BV(BV2Int(A), bigint_bits)) & + Int2BV(BV2Int(Int2BV(IntVal(-1), n_bits)), bigint_bits) + ), n_bits +) + +rule.check(workaround, SHL(A, X)) From 4d38df6920119267a31ebf97b548e8195c59c486 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 13 Jun 2019 15:51:15 +0200 Subject: [PATCH 049/115] Set state mutability of function type members ``gas`` and ``value`` to pure. --- Changelog.md | 1 + libsolidity/ast/Types.cpp | 4 ++-- .../viewPureChecker/gas_value_without_call.sol | 13 +++++++++++++ .../viewPureChecker/gas_with_call_nonpayable.sol | 16 ++++++++++++++++ .../viewPureChecker/staticcall_gas_view.sol | 11 +++++++++++ .../value_with_call_nonpayable.sol | 16 ++++++++++++++++ 6 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/gas_value_without_call.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/gas_with_call_nonpayable.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/staticcall_gas_view.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/value_with_call_nonpayable.sol diff --git a/Changelog.md b/Changelog.md index deed908a4..243284272 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Compiler Features: Bugfixes: * Yul / Inline Assembly Parser: Disallow trailing commas in function call arguments. + * Set state mutability of the function type members ``gas`` and ``value`` to pure (while their return type inherits state mutability from the function type). Build System: diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6877ecbd6..65d9abf06 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2912,7 +2912,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con strings(1, ""), Kind::SetValue, false, - StateMutability::NonPayable, + StateMutability::Pure, nullptr, m_gasSet, m_valueSet @@ -2929,7 +2929,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con strings(1, ""), Kind::SetGas, false, - StateMutability::NonPayable, + StateMutability::Pure, nullptr, m_gasSet, m_valueSet diff --git a/test/libsolidity/syntaxTests/viewPureChecker/gas_value_without_call.sol b/test/libsolidity/syntaxTests/viewPureChecker/gas_value_without_call.sol new file mode 100644 index 000000000..2df3bbe4f --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/gas_value_without_call.sol @@ -0,0 +1,13 @@ +contract C { + function f() external payable {} + function g(address a) external pure { + a.call.value(42); + a.call.gas(42); + a.staticcall.gas(42); + a.delegatecall.gas(42); + } + function h() external view { + this.f.value(42); + this.f.gas(42); + } +} diff --git a/test/libsolidity/syntaxTests/viewPureChecker/gas_with_call_nonpayable.sol b/test/libsolidity/syntaxTests/viewPureChecker/gas_with_call_nonpayable.sol new file mode 100644 index 000000000..0a58a516c --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/gas_with_call_nonpayable.sol @@ -0,0 +1,16 @@ +contract C { + function f(address a) external view returns (bool success) { + (success,) = a.call.gas(42)(""); + } + function g(address a) external view returns (bool success) { + (success,) = a.call.gas(42)(""); + } + function h() external payable {} + function i() external view { + this.h.gas(42)(); + } +} +// ---- +// TypeError: (90-108): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (190-208): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (279-295): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/staticcall_gas_view.sol b/test/libsolidity/syntaxTests/viewPureChecker/staticcall_gas_view.sol new file mode 100644 index 000000000..6f8b31bfc --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/staticcall_gas_view.sol @@ -0,0 +1,11 @@ +contract C { + function f() external view {} + function test(address a) external view returns (bool status) { + // This used to incorrectly raise an error about violating the view mutability. + (status,) = a.staticcall.gas(42)(""); + this.f.gas(42)(); + } +} +// ==== +// EVMVersion: >=byzantium +// ---- diff --git a/test/libsolidity/syntaxTests/viewPureChecker/value_with_call_nonpayable.sol b/test/libsolidity/syntaxTests/viewPureChecker/value_with_call_nonpayable.sol new file mode 100644 index 000000000..cf5d885c9 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/value_with_call_nonpayable.sol @@ -0,0 +1,16 @@ +contract C { + function f(address a) external view returns (bool success) { + (success,) = a.call.value(42)(""); + } + function g(address a) external view returns (bool success) { + (success,) = a.call.value(42)(""); + } + function h() external payable {} + function i() external view { + this.h.value(42)(); + } +} +// ---- +// TypeError: (90-110): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (192-212): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (283-301): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. From b5cbb1a3e9c1564b30e0862af5db251a7c614113 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Tue, 26 Mar 2019 10:52:30 +0100 Subject: [PATCH 050/115] For loop with custom init and post blocks and potentially unbounded conditional expression --- test/tools/ossfuzz/protoToYul.cpp | 48 ++++++++++++++++++++++++------- test/tools/ossfuzz/protoToYul.h | 15 ++++++---- test/tools/ossfuzz/yulProto.proto | 8 ++++++ 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 6582d6b4e..f32fda260 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -616,7 +616,9 @@ void ProtoConverter::visit(FunctionCall const& _x) visit(_x.call_zero()); break; case FunctionCall::kCallMultidecl: - visit(_x.call_multidecl()); + // Hack: Disallow (multi) variable declarations until scope extension is implemented for "for-init" + if (!m_inForInitScope) + visit(_x.call_multidecl()); break; case FunctionCall::kCallMultiassign: visit(_x.call_multiassign()); @@ -655,17 +657,38 @@ void ProtoConverter::visit(StoreFunc const& _x) } void ProtoConverter::visit(ForStmt const& _x) +{ + bool wasInForBody = m_inForBodyScope; + bool wasInForInit = m_inForInitScope; + m_inForBodyScope = false; + m_inForInitScope = true; + m_output << "for "; + visit(_x.for_init()); + m_inForInitScope = false; + visit(_x.for_cond()); + visit(_x.for_post()); + m_inForBodyScope = true; + visit(_x.for_body()); + m_inForBodyScope = wasInForBody; + m_inForInitScope = wasInForInit; +} + +void ProtoConverter::visit(BoundedForStmt const& _x) { // Boilerplate for loop that limits the number of iterations to a maximum of 4. - // TODO: Generalize for loop init, condition, and post blocks. std::string loopVarName("i_" + std::to_string(m_numNestedForLoops++)); m_output << "for { let " << loopVarName << " := 0 } " - << "lt(" << loopVarName << ", 0x60) " - << "{ " << loopVarName << " := add(" << loopVarName << ", 0x20) } "; - m_inForScope.push(true); + << "lt(" << loopVarName << ", 0x60) " + << "{ " << loopVarName << " := add(" << loopVarName << ", 0x20) } "; + // Store previous for body scope + bool wasInForBody = m_inForBodyScope; + bool wasInForInit = m_inForInitScope; + m_inForBodyScope = true; + m_inForInitScope = false; visit(_x.for_body()); - m_inForScope.pop(); - --m_numNestedForLoops; + // Restore previous for body scope and init + m_inForBodyScope = wasInForBody; + m_inForInitScope = wasInForInit; } void ProtoConverter::visit(CaseStmt const& _x) @@ -765,7 +788,9 @@ void ProtoConverter::visit(Statement const& _x) switch (_x.stmt_oneof_case()) { case Statement::kDecl: - visit(_x.decl()); + // Hack: Disallow (multi) variable declarations until scope extension is implemented for "for-init" + if (!m_inForInitScope) + visit(_x.decl()); break; case Statement::kAssignment: visit(_x.assignment()); @@ -782,15 +807,18 @@ void ProtoConverter::visit(Statement const& _x) case Statement::kForstmt: visit(_x.forstmt()); break; + case Statement::kBoundedforstmt: + visit(_x.boundedforstmt()); + break; case Statement::kSwitchstmt: visit(_x.switchstmt()); break; case Statement::kBreakstmt: - if (m_inForScope.top()) + if (m_inForBodyScope) m_output << "break\n"; break; case Statement::kContstmt: - if (m_inForScope.top()) + if (m_inForBodyScope) m_output << "continue\n"; break; case Statement::kLogFunc: diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index dc707a0fa..bea31b1e5 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -42,9 +42,10 @@ public: { m_numLiveVars = 0; m_numVarsPerScope.push(m_numLiveVars); - m_numNestedForLoops = 0; - m_inForScope.push(false); m_numFunctionSets = 0; + m_inForBodyScope = false; + m_inForInitScope = false; + m_numNestedForLoops = 0; } ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; @@ -69,6 +70,7 @@ private: void visit(Statement const&); void visit(FunctionDefinition const&); void visit(ForStmt const&); + void visit(BoundedForStmt const&); void visit(CaseStmt const&); void visit(SwitchStmt const&); void visit(TernaryOp const&); @@ -131,9 +133,6 @@ private: std::stack m_numVarsPerScope; // Number of live variables in function scope unsigned m_numLiveVars; - // Number of nested for loops for loop index referencing - unsigned m_numNestedForLoops; - std::stack m_inForScope; // Set that is used for deduplicating switch case literals std::stack> m_switchLiteralSetPerScope; // Total number of function sets. A function set contains one function of each type defined by @@ -146,6 +145,12 @@ private: // mod input/output parameters impose an upper bound on the number of input/output parameters a function may have. static unsigned constexpr modInputParams = 5; static unsigned constexpr modOutputParams = 5; + // predicate to keep track of for body scope + bool m_inForBodyScope; + // Index used for naming loop variable of bounded for loops + unsigned m_numNestedForLoops; + // predicate to keep track of for loop init scope + bool m_inForInitScope; }; } } diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 4793a029f..4a39ab258 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -256,8 +256,15 @@ message IfStmt { required Block if_body = 2; } +message BoundedForStmt { + required Block for_body = 1; +} + message ForStmt { required Block for_body = 1; + required Block for_init = 2; + required Block for_post = 3; + required Expression for_cond = 4; } message CaseStmt { @@ -324,6 +331,7 @@ message Statement { ExtCodeCopy extcode_copy = 12; TerminatingStmt terminatestmt = 13; FunctionCall functioncall = 14; + BoundedForStmt boundedforstmt = 15; } } From 7f322c94335fc93537efdd0beb8bde59e5c3d2f5 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 13 Jun 2019 11:13:26 +0200 Subject: [PATCH 051/115] Add optimization rule SUB(~0, X) -> NOT(X). --- Changelog.md | 1 + libevmasm/RuleList.h | 1 + 2 files changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 243284272..fa920413b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: Compiler Features: + * Optimizer: Add rule to simplify SUB(~0, X) to NOT(X). diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 7c95883b2..c6c454f65 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -124,6 +124,7 @@ std::vector> simplificationRuleListPart2( {{Instruction::ADD, {X, 0}}, [=]{ return X; }, false}, {{Instruction::ADD, {0, X}}, [=]{ return X; }, false}, {{Instruction::SUB, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::SUB, {~u256(0), X}}, [=]() -> Pattern { return {Instruction::NOT, {X}}; }, false}, {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::MUL, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::MUL, {X, 1}}, [=]{ return X; }, false}, From d3293cf0d0b53e241882912810df8f48c9cd9baa Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 14 Jun 2019 14:08:21 +0200 Subject: [PATCH 052/115] Correctness proof for SUB(NOT(0),X)->NOT(X). --- test/formal/sub_not_zero_x_to_not_x_256.py | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 test/formal/sub_not_zero_x_to_not_x_256.py diff --git a/test/formal/sub_not_zero_x_to_not_x_256.py b/test/formal/sub_not_zero_x_to_not_x_256.py new file mode 100644 index 000000000..eb3301100 --- /dev/null +++ b/test/formal/sub_not_zero_x_to_not_x_256.py @@ -0,0 +1,26 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +SUB(~0, X) -> NOT(X) +Requirements: +""" + +rule = Rule() + +n_bits = 256 + +# Input vars +X = BitVec('X', n_bits) + +# Constants +ZERO = BitVecVal(0, n_bits) + +# Non optimized result +nonopt = SUB(~ZERO, X) + +# Optimized result +opt = NOT(X) + +rule.check(nonopt, opt) From 5718072e10ed0ba036b1f509e94e176ca1cf8e58 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 14 Jun 2019 16:57:57 +0200 Subject: [PATCH 053/115] Fix comparison opcodes and minor errors in proof scripts. --- test/formal/combine_div_shl_one_32.py | 5 +---- test/formal/combine_mul_shl_one_64.py | 7 ++----- test/formal/combine_shr_shl_by_constant_64.py | 2 +- test/formal/opcodes.py | 11 +++++++---- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/test/formal/combine_div_shl_one_32.py b/test/formal/combine_div_shl_one_32.py index 23c65c8d7..2ee7d2137 100644 --- a/test/formal/combine_div_shl_one_32.py +++ b/test/formal/combine_div_shl_one_32.py @@ -15,11 +15,8 @@ n_bits = 32 X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) -# Constants -ONE = BitVecVal(1, n_bits) - # Non optimized result -nonopt = DIV(X, SHL(Y, ONE)) +nonopt = DIV(X, SHL(Y, 1)) # Optimized result opt = SHR(Y, X) diff --git a/test/formal/combine_mul_shl_one_64.py b/test/formal/combine_mul_shl_one_64.py index 6f87cfcdb..44d031b98 100644 --- a/test/formal/combine_mul_shl_one_64.py +++ b/test/formal/combine_mul_shl_one_64.py @@ -16,14 +16,11 @@ n_bits = 64 X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) -# Constants -ONE = BitVecVal(1, n_bits) - # Requirements # Non optimized result -nonopt_1 = MUL(X, SHL(Y, ONE)) -nonopt_2 = MUL(SHL(X, ONE), Y) +nonopt_1 = MUL(X, SHL(Y, 1)) +nonopt_2 = MUL(SHL(X, 1), Y) # Optimized result opt_1 = SHL(Y, X) diff --git a/test/formal/combine_shr_shl_by_constant_64.py b/test/formal/combine_shr_shl_by_constant_64.py index 31aa60956..c011a2616 100644 --- a/test/formal/combine_shr_shl_by_constant_64.py +++ b/test/formal/combine_shr_shl_by_constant_64.py @@ -33,7 +33,7 @@ nonopt = SHL(B, SHR(A, X)) Mask = SHL(B, SHR(A, Int2BV(IntVal(-1), n_bits))) opt = If( UGT(A, B), - AND(SHR(a - b, x), Mask), + AND(SHR(A - B, X), Mask), If( UGT(B, A), AND(SHL(B - A, X), Mask), diff --git a/test/formal/opcodes.py b/test/formal/opcodes.py index faa9de1d9..ca6282f70 100644 --- a/test/formal/opcodes.py +++ b/test/formal/opcodes.py @@ -22,16 +22,19 @@ def SMOD(x, y): return If(y == 0, 0, x % y) def LT(x, y): - return ULT(x, y) + return If(ULT(x, y), BitVecVal(1, x.size()), BitVecVal(0, x.size())) def GT(x, y): - return UGT(x, y) + return If(UGT(x, y), BitVecVal(1, x.size()), BitVecVal(0, x.size())) def SLT(x, y): - return x < y + return If(x < y, BitVecVal(1, x.size()), BitVecVal(0, x.size())) def SGT(x, y): - return x > y + return If(x > y, BitVecVal(1, x.size()), BitVecVal(0, x.size())) + +def ISZERO(x): + return If(x == 0, BitVecVal(1, x.size()), BitVecVal(0, x.size())) def AND(x, y): return x & y From 0465803b2c5c8b9e880aa6267a4d9360f6187d97 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Sun, 16 Jun 2019 09:33:40 +0100 Subject: [PATCH 054/115] Fix typo in miscellaneous doc As reported by @Marenz --- docs/miscellaneous.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 22eac11f6..c83360ca9 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -11,7 +11,7 @@ Layout of State Variables in Storage Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position ``0``. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules: - The first item in a storage slot is stored lower-order aligned. -- Elementary types use only that many bytes that are necessary to store them. +- Elementary types use only as many bytes that are necessary to store them. - If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot. - Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules). From aa6e29a7a004a86c177cea4f4a9169294cff28e0 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Mon, 17 Jun 2019 11:14:11 +0100 Subject: [PATCH 055/115] Update docs/miscellaneous.rst Co-Authored-By: Mathias L. Baumann --- docs/miscellaneous.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index c83360ca9..69a08bdf9 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -11,7 +11,7 @@ Layout of State Variables in Storage Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position ``0``. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules: - The first item in a storage slot is stored lower-order aligned. -- Elementary types use only as many bytes that are necessary to store them. +- Elementary types use only as many bytes as are necessary to store them. - If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot. - Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules). From ee937ea7b3c6c1860506932157c955da62f8483a Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Mon, 17 Jun 2019 13:14:00 +0200 Subject: [PATCH 056/115] Fix typo to make CI happy --- 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 11aecf04a..3e744803c 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -164,7 +164,7 @@ transactions. To listen for this event, you could use the following JavaScript code, which uses `web3.js `_ to create the ``Coin`` contract object, -and any user interface calls the automatically generated ``balances`` function from aboves:: +and any user interface calls the automatically generated ``balances`` function from above:: Coin.Sent().watch({}, '', function(error, result) { if (!error) { From b90d57a9fc3d9fed94ca071d9750f7a849f8d18d Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 17 Jun 2019 13:39:20 +0200 Subject: [PATCH 057/115] [DOC] Fix signextend example. --- docs/assembly.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 295e39b90..2f5734f33 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -416,7 +416,7 @@ Local Solidity variables are available for assignments, for example: in a context where this is important: ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` To clean signed types, you can use the ``signextend`` opcode: - ``assembly { signextend(, x) }`` + ``assembly { signextend(, x) }`` Labels ------ From e96003e709e3b12657cb2ca34c49f0f86577e299 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 17 Jun 2019 13:41:48 +0200 Subject: [PATCH 058/115] optimizer: Remove redundant rule from the optimizer rule list --- libevmasm/RuleList.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 7c95883b2..6c171a039 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -89,7 +89,6 @@ std::vector> simplificationRuleListPart1( {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }, false}, {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }, false}, {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }, false}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }, false}, {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { if (A.d() >= 31) return B.d(); From ca3afea1d74b06908ca810b76d0ad3225228c1d9 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 27 May 2019 10:13:27 -0400 Subject: [PATCH 059/115] Add Steve Johnson-style parser recovery rules: SourceUnit = Error $ Block = '{' Error '}' ContractDefinition = '{' Error '}' Statement = Error ';' Co-Authored-By: chriseth --- Changelog.md | 2 +- liblangutil/CharStream.cpp | 9 +- liblangutil/CharStream.h | 6 + liblangutil/ErrorReporter.cpp | 5 + liblangutil/ErrorReporter.h | 4 +- liblangutil/ParserBase.cpp | 106 ++++++-- liblangutil/ParserBase.h | 28 +- liblangutil/Scanner.cpp | 7 + liblangutil/Scanner.h | 3 + libsolidity/interface/CompilerStack.cpp | 2 +- libsolidity/interface/CompilerStack.h | 9 + libsolidity/parsing/Parser.cpp | 254 +++++++++++------- libsolidity/parsing/Parser.h | 5 +- solc/CommandLineInterface.cpp | 5 + test/InteractiveTests.h | 2 +- test/liblangutil/CharStream.cpp | 53 ++++ test/libsolidity/AnalysisFramework.cpp | 5 +- test/libsolidity/AnalysisFramework.h | 3 +- test/libsolidity/SMTChecker.cpp | 6 +- test/libsolidity/SyntaxTest.cpp | 4 +- test/libsolidity/SyntaxTest.h | 11 +- .../constructor_recovers.sol | 20 ++ .../errorRecoveryTests/contract_recovery.sol | 7 + .../do_not_delete_at_error.sol | 13 + .../errorRecoveryTests/error_to_eos.sol | 23 ++ .../errorRecoveryTests/missing_rhs.sol | 11 + .../errorRecoveryTests/multiple_errors.sol | 29 ++ 27 files changed, 492 insertions(+), 140 deletions(-) create mode 100644 test/liblangutil/CharStream.cpp create mode 100644 test/libsolidity/errorRecoveryTests/constructor_recovers.sol create mode 100644 test/libsolidity/errorRecoveryTests/contract_recovery.sol create mode 100644 test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol create mode 100644 test/libsolidity/errorRecoveryTests/error_to_eos.sol create mode 100644 test/libsolidity/errorRecoveryTests/missing_rhs.sol create mode 100644 test/libsolidity/errorRecoveryTests/multiple_errors.sol diff --git a/Changelog.md b/Changelog.md index fa920413b..994cff927 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Language Features: Compiler Features: * Optimizer: Add rule to simplify SUB(~0, X) to NOT(X). + * Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch. @@ -45,7 +46,6 @@ Compiler Features: * Yul Optimizer: Do not inline recursive functions. * Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used. - Bugfixes: * Code Generator: Explicitly turn uninitialized internal function pointers into invalid functions when loaded from storage. * Code Generator: Fix assertion failure when assigning structs containing array of mapping. diff --git a/liblangutil/CharStream.cpp b/liblangutil/CharStream.cpp index 2c10bd608..58edced3b 100644 --- a/liblangutil/CharStream.cpp +++ b/liblangutil/CharStream.cpp @@ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount) return get(); } +char CharStream::setPosition(size_t _location) +{ + solAssert(_location <= m_source.size(), "Attempting to set position past end of source."); + m_position = _location; + return get(); +} + string CharStream::lineAtPosition(int _position) const { // if _position points to \n, it returns the line before the \n @@ -106,5 +113,3 @@ tuple CharStream::translatePositionToLineColumn(int _position) const } return tuple(lineNumber, searchPosition - lineStart); } - - diff --git a/liblangutil/CharStream.h b/liblangutil/CharStream.h index c11340b21..504c39da5 100644 --- a/liblangutil/CharStream.h +++ b/liblangutil/CharStream.h @@ -76,7 +76,13 @@ public: char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; } char advanceAndGet(size_t _chars = 1); + /// Sets scanner position to @ _amount characters backwards in source text. + /// @returns The character of the current location after update is returned. char rollback(size_t _amount); + /// Sets scanner position to @ _location if it refers a valid offset in m_source. + /// If not, nothing is done. + /// @returns The character of the current location after update is returned. + char setPosition(size_t _location); void reset() { m_position = 0; } diff --git a/liblangutil/ErrorReporter.cpp b/liblangutil/ErrorReporter.cpp index 0bf3a4167..7f8a77f1a 100644 --- a/liblangutil/ErrorReporter.cpp +++ b/liblangutil/ErrorReporter.cpp @@ -86,6 +86,11 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se m_errorList.push_back(err); } +bool ErrorReporter::hasExcessiveErrors() const +{ + return m_errorCount > c_maxErrorsAllowed; +} + bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) { if (_type == Error::Type::Warning) diff --git a/liblangutil/ErrorReporter.h b/liblangutil/ErrorReporter.h index 30b494a0f..6d6b4ef41 100644 --- a/liblangutil/ErrorReporter.h +++ b/liblangutil/ErrorReporter.h @@ -118,6 +118,9 @@ public: return m_errorCount > 0; } + // @returns true if the maximum error count has been reached. + bool hasExcessiveErrors() const; + private: void error( Error::Type _type, @@ -149,4 +152,3 @@ private: }; } - diff --git a/liblangutil/ParserBase.cpp b/liblangutil/ParserBase.cpp index edd23fbaf..2432ac711 100644 --- a/liblangutil/ParserBase.cpp +++ b/liblangutil/ParserBase.cpp @@ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const return m_scanner->peekNextToken(); } -std::string ParserBase::currentLiteral() const +string ParserBase::currentLiteral() const { return m_scanner->currentLiteral(); } @@ -57,34 +57,83 @@ Token ParserBase::advance() return m_scanner->next(); } +string ParserBase::tokenName(Token _token) +{ + if (_token == Token::Identifier) + return "identifier"; + else if (_token == Token::EOS) + return "end of source"; + else if (TokenTraits::isReservedKeyword(_token)) + return "reserved keyword '" + TokenTraits::friendlyName(_token) + "'"; + else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting + { + ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); + return "'" + elemTypeName.toString() + "'"; + } + else + return "'" + TokenTraits::friendlyName(_token) + "'"; +} + void ParserBase::expectToken(Token _value, bool _advance) { Token tok = m_scanner->currentToken(); if (tok != _value) { - auto tokenName = [this](Token _token) - { - if (_token == Token::Identifier) - return string("identifier"); - else if (_token == Token::EOS) - return string("end of source"); - else if (TokenTraits::isReservedKeyword(_token)) - return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'"; - else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting - { - ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); - return string("'") + elemTypeName.toString() + "'"; - } - else - return string("'") + TokenTraits::friendlyName(_token) + "'"; - }; - - fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok)); + string const expectedToken = ParserBase::tokenName(_value); + if (m_parserErrorRecovery) + parserError("Expected " + expectedToken + " but got " + tokenName(tok)); + else + fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok)); + // Do not advance so that recovery can sync or make use of the current token. + // This is especially useful if the expected token + // is the only one that is missing and is at the end of a construct. + // "{ ... ; }" is such an example. + // ^ + _advance = false; } if (_advance) m_scanner->next(); } +void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance) +{ + Token tok = m_scanner->currentToken(); + if (tok != _value) + { + int startPosition = position(); + SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()}; + while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS) + m_scanner->next(); + + string const expectedToken = ParserBase::tokenName(_value); + string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + "instead."; + if (m_scanner->currentToken() == Token::EOS) + { + // rollback to where the token started, and raise exception to be caught at a higher level. + m_scanner->setPosition(startPosition); + m_inParserRecovery = true; + fatalParserError(errorLoc, msg); + } + else + { + if (m_inParserRecovery) + parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + "."); + else + parserError(errorLoc, msg + "Recovered at next " + expectedToken); + m_inParserRecovery = false; + } + } + else if (m_inParserRecovery) + { + string expectedToken = ParserBase::tokenName(_value); + parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + "."); + m_inParserRecovery = false; + } + + if (_advance) + m_scanner->next(); +} + void ParserBase::increaseRecursionDepth() { m_recursionDepth++; @@ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth() m_recursionDepth--; } +void ParserBase::parserWarning(string const& _description) +{ + m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description); +} + +void ParserBase::parserError(SourceLocation const& _location, string const& _description) +{ + m_errorReporter.parserError(_location, _description); +} + void ParserBase::parserError(string const& _description) { - m_errorReporter.parserError(SourceLocation{position(), endPosition(), source()}, _description); + parserError(SourceLocation{position(), endPosition(), source()}, _description); } void ParserBase::fatalParserError(string const& _description) { - m_errorReporter.fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); + fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); +} + +void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description) +{ + m_errorReporter.fatalParserError(_location, _description); } diff --git a/liblangutil/ParserBase.h b/liblangutil/ParserBase.h index 1c6f298c1..98123b813 100644 --- a/liblangutil/ParserBase.h +++ b/liblangutil/ParserBase.h @@ -36,7 +36,14 @@ class Scanner; class ParserBase { public: - explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} + /// Set @a _parserErrorRecovery to true for additional error + /// recovery. This is experimental and intended for use + /// by front-end tools that need partial AST information even + /// when errors occur. + explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter) + { + m_parserErrorRecovery = _parserErrorRecovery; + } std::shared_ptr source() const { return m_scanner->charStream(); } @@ -62,10 +69,17 @@ protected: ///@{ ///@name Helper functions - /// If current token value is not _value, throw exception otherwise advance token. + /// If current token value is not @a _value, throw exception otherwise advance token + // @a if _advance is true and error recovery is in effect. void expectToken(Token _value, bool _advance = true); + + /// Like expectToken but if there is an error ignores tokens until + /// the expected token or EOS is seen. If EOS is encountered, back up to the error point, + /// and throw an exception so that a higher grammar rule has an opportunity to recover. + void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true); Token currentToken() const; Token peekNextToken() const; + std::string tokenName(Token _token); std::string currentLiteral() const; Token advance(); ///@} @@ -77,16 +91,26 @@ protected: /// Creates a @ref ParserError and annotates it with the current position and the /// given @a _description. void parserError(std::string const& _description); + void parserError(SourceLocation const& _location, std::string const& _description); + + /// Creates a @ref ParserWarning and annotates it with the current position and the + /// given @a _description. + void parserWarning(std::string const& _description); /// Creates a @ref ParserError and annotates it with the current position and the /// given @a _description. Throws the FatalError. void fatalParserError(std::string const& _description); + void fatalParserError(SourceLocation const& _location, std::string const& _description); std::shared_ptr m_scanner; /// The reference to the list of errors and warning to add errors/warnings during parsing ErrorReporter& m_errorReporter; /// Current recursion depth during parsing. size_t m_recursionDepth = 0; + /// True if we are in parser error recovery. Usually this means we are scanning for + /// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages. + bool m_inParserRecovery = false; + bool m_parserErrorRecovery = false; }; } diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 852a92e9b..a317c79b6 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -156,6 +156,13 @@ void Scanner::reset() next(); } +void Scanner::setPosition(size_t _offset) +{ + m_char = m_source->setPosition(_offset); + scanToken(); + next(); +} + void Scanner::supportPeriodInIdentifier(bool _value) { m_supportPeriodInIdentifier = _value; diff --git a/liblangutil/Scanner.h b/liblangutil/Scanner.h index 9a4a170a6..555575129 100644 --- a/liblangutil/Scanner.h +++ b/liblangutil/Scanner.h @@ -110,6 +110,9 @@ public: /// @returns the next token and advances input Token next(); + /// Set scanner to a specific offset. This is used in error recovery. + void setPosition(size_t _offset); + ///@{ ///@name Information about the current token diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 2910a9d95..c7846c8e2 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -217,7 +217,7 @@ bool CompilerStack::parse() string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); - source.ast = Parser(m_errorReporter, m_evmVersion).parse(source.scanner); + source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner); if (!source.ast) solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); else diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index de90553cd..25718e698 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -132,6 +132,14 @@ public: /// Must be set before parsing. void setOptimiserSettings(OptimiserSettings _settings); + /// Set whether or not parser error is desired. + /// When called without an argument it will revert to the default. + /// Must be set before parsing. + void setParserErrorRecovery(bool _wantErrorRecovery = false) + { + m_parserErrorRecovery = _wantErrorRecovery; + } + /// Set the EVM version used before running compile. /// When called without an argument it will revert to the default version. /// Must be set before parsing. @@ -386,6 +394,7 @@ private: langutil::ErrorList m_errorList; langutil::ErrorReporter m_errorReporter; bool m_metadataLiteralSources = false; + bool m_parserErrorRecovery = false; State m_stackState = Empty; bool m_release = VersionIsRelease; }; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 5b5c56523..d8e0fa4bc 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -258,57 +258,75 @@ ASTPointer Parser::parseContractDefinition() { RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); + ASTPointer name = nullptr; ASTPointer docString; - if (m_scanner->currentCommentLiteral() != "") - docString = make_shared(m_scanner->currentCommentLiteral()); - ContractDefinition::ContractKind contractKind = parseContractKind(); - ASTPointer name = expectIdentifierToken(); vector> baseContracts; - if (m_scanner->currentToken() == Token::Is) - do - { - m_scanner->next(); - baseContracts.push_back(parseInheritanceSpecifier()); - } - while (m_scanner->currentToken() == Token::Comma); vector> subNodes; - expectToken(Token::LBrace); - while (true) + ContractDefinition::ContractKind contractKind = ContractDefinition::ContractKind::Contract; + try { - Token currentTokenValue = m_scanner->currentToken(); - if (currentTokenValue == Token::RBrace) - break; - else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor) - // This can be a function or a state variable of function type (especially - // complicated to distinguish fallback function from function type state variable) - subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable()); - else if (currentTokenValue == Token::Struct) - subNodes.push_back(parseStructDefinition()); - else if (currentTokenValue == Token::Enum) - subNodes.push_back(parseEnumDefinition()); - else if ( - currentTokenValue == Token::Identifier || - currentTokenValue == Token::Mapping || - TokenTraits::isElementaryTypeName(currentTokenValue) - ) + if (m_scanner->currentCommentLiteral() != "") + docString = make_shared(m_scanner->currentCommentLiteral()); + contractKind = parseContractKind(); + name = expectIdentifierToken(); + if (m_scanner->currentToken() == Token::Is) + do + { + m_scanner->next(); + baseContracts.push_back(parseInheritanceSpecifier()); + } + while (m_scanner->currentToken() == Token::Comma); + expectToken(Token::LBrace); + while (true) { - VarDeclParserOptions options; - options.isStateVariable = true; - options.allowInitialValue = true; - subNodes.push_back(parseVariableDeclaration(options)); - expectToken(Token::Semicolon); + Token currentTokenValue = m_scanner->currentToken(); + if (currentTokenValue == Token::RBrace) + break; + else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor) + // This can be a function or a state variable of function type (especially + // complicated to distinguish fallback function from function type state variable) + subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable()); + else if (currentTokenValue == Token::Struct) + subNodes.push_back(parseStructDefinition()); + else if (currentTokenValue == Token::Enum) + subNodes.push_back(parseEnumDefinition()); + else if ( + currentTokenValue == Token::Identifier || + currentTokenValue == Token::Mapping || + TokenTraits::isElementaryTypeName(currentTokenValue) + ) + { + VarDeclParserOptions options; + options.isStateVariable = true; + options.allowInitialValue = true; + subNodes.push_back(parseVariableDeclaration(options)); + expectToken(Token::Semicolon); + } + else if (currentTokenValue == Token::Modifier) + subNodes.push_back(parseModifierDefinition()); + else if (currentTokenValue == Token::Event) + subNodes.push_back(parseEventDefinition()); + else if (currentTokenValue == Token::Using) + subNodes.push_back(parseUsingDirective()); + else + fatalParserError(string("Function, variable, struct or modifier declaration expected.")); } - else if (currentTokenValue == Token::Modifier) - subNodes.push_back(parseModifierDefinition()); - else if (currentTokenValue == Token::Event) - subNodes.push_back(parseEventDefinition()); - else if (currentTokenValue == Token::Using) - subNodes.push_back(parseUsingDirective()); - else - fatalParserError(string("Function, variable, struct or modifier declaration expected.")); + } + catch (FatalError const&) + { + if ( + !m_errorReporter.hasErrors() || + !m_parserErrorRecovery || + m_errorReporter.hasExcessiveErrors() + ) + BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ + m_inParserRecovery = true; } nodeFactory.markEndPosition(); - expectToken(Token::RBrace); + if (m_inParserRecovery) + expectTokenOrConsumeUntil(Token::RBrace, "ContractDefinition"); + else + expectToken(Token::RBrace); return nodeFactory.createNode( name, docString, @@ -959,10 +977,26 @@ ASTPointer Parser::parseBlock(ASTPointer const& _docString) ASTNodeFactory nodeFactory(*this); expectToken(Token::LBrace); vector> statements; - while (m_scanner->currentToken() != Token::RBrace) - statements.push_back(parseStatement()); - nodeFactory.markEndPosition(); - expectToken(Token::RBrace); + try + { + while (m_scanner->currentToken() != Token::RBrace) + statements.push_back(parseStatement()); + nodeFactory.markEndPosition(); + } + catch (FatalError const&) + { + if ( + !m_errorReporter.hasErrors() || + !m_parserErrorRecovery || + m_errorReporter.hasExcessiveErrors() + ) + BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ + m_inParserRecovery = true; + } + if (m_parserErrorRecovery) + expectTokenOrConsumeUntil(Token::RBrace, "Block"); + else + expectToken(Token::RBrace); return nodeFactory.createNode(_docString, statements); } @@ -970,67 +1004,83 @@ ASTPointer Parser::parseStatement() { RecursionGuard recursionGuard(*this); ASTPointer docString; - if (m_scanner->currentCommentLiteral() != "") - docString = make_shared(m_scanner->currentCommentLiteral()); ASTPointer statement; - switch (m_scanner->currentToken()) + try { - case Token::If: - return parseIfStatement(docString); - case Token::While: - return parseWhileStatement(docString); - case Token::Do: - return parseDoWhileStatement(docString); - case Token::For: - return parseForStatement(docString); - case Token::LBrace: - return parseBlock(docString); - // starting from here, all statements must be terminated by a semicolon - case Token::Continue: - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - break; - case Token::Break: - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - break; - case Token::Return: - { - ASTNodeFactory nodeFactory(*this); - ASTPointer expression; - if (m_scanner->next() != Token::Semicolon) + if (m_scanner->currentCommentLiteral() != "") + docString = make_shared(m_scanner->currentCommentLiteral()); + switch (m_scanner->currentToken()) { - expression = parseExpression(); - nodeFactory.setEndPositionFromNode(expression); - } - statement = nodeFactory.createNode(docString, expression); - break; - } - case Token::Throw: - { - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - break; - } - case Token::Assembly: - return parseInlineAssembly(docString); - case Token::Emit: - statement = parseEmitStatement(docString); - break; - case Token::Identifier: - if (m_insideModifier && m_scanner->currentLiteral() == "_") - { - statement = ASTNodeFactory(*this).createNode(docString); + case Token::If: + return parseIfStatement(docString); + case Token::While: + return parseWhileStatement(docString); + case Token::Do: + return parseDoWhileStatement(docString); + case Token::For: + return parseForStatement(docString); + case Token::LBrace: + return parseBlock(docString); + // starting from here, all statements must be terminated by a semicolon + case Token::Continue: + statement = ASTNodeFactory(*this).createNode(docString); m_scanner->next(); - } - else + break; + case Token::Break: + statement = ASTNodeFactory(*this).createNode(docString); + m_scanner->next(); + break; + case Token::Return: + { + ASTNodeFactory nodeFactory(*this); + ASTPointer expression; + if (m_scanner->next() != Token::Semicolon) + { + expression = parseExpression(); + nodeFactory.setEndPositionFromNode(expression); + } + statement = nodeFactory.createNode(docString, expression); + break; + } + case Token::Throw: + { + statement = ASTNodeFactory(*this).createNode(docString); + m_scanner->next(); + break; + } + case Token::Assembly: + return parseInlineAssembly(docString); + case Token::Emit: + statement = parseEmitStatement(docString); + break; + case Token::Identifier: + if (m_insideModifier && m_scanner->currentLiteral() == "_") + { + statement = ASTNodeFactory(*this).createNode(docString); + m_scanner->next(); + } + else + statement = parseSimpleStatement(docString); + break; + default: statement = parseSimpleStatement(docString); - break; - default: - statement = parseSimpleStatement(docString); - break; + break; + } } - expectToken(Token::Semicolon); + catch (FatalError const&) + { + if ( + !m_errorReporter.hasErrors() || + !m_parserErrorRecovery || + m_errorReporter.hasExcessiveErrors() + ) + BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ + m_inParserRecovery = true; + } + if (m_inParserRecovery) + expectTokenOrConsumeUntil(Token::Semicolon, "Statement"); + else + expectToken(Token::Semicolon); return statement; } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 51b3b5be4..cb3d75f51 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -41,9 +41,10 @@ class Parser: public langutil::ParserBase public: explicit Parser( langutil::ErrorReporter& _errorReporter, - langutil::EVMVersion _evmVersion + langutil::EVMVersion _evmVersion, + bool _errorRecovery = false ): - ParserBase(_errorReporter), + ParserBase(_errorReporter, _errorRecovery), m_evmVersion(_evmVersion) {} diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 764376236..a6e2f6025 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -112,6 +112,7 @@ static string const g_strBinaryRuntime = "bin-runtime"; static string const g_strCombinedJson = "combined-json"; static string const g_strCompactJSON = "compact-format"; static string const g_strContracts = "contracts"; +static string const g_strErrorRecovery = "error-recovery"; static string const g_strEVM = "evm"; static string const g_strEVM15 = "evm15"; static string const g_strEVMVersion = "evm-version"; @@ -163,6 +164,7 @@ static string const g_argBinary = g_strBinary; static string const g_argBinaryRuntime = g_strBinaryRuntime; static string const g_argCombinedJson = g_strCombinedJson; static string const g_argCompactJSON = g_strCompactJSON; +static string const g_argErrorRecovery = g_strErrorRecovery; static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; static string const g_argInputFile = g_strInputFile; @@ -689,6 +691,7 @@ Allowed options)", (g_argColor.c_str(), "Force colored output.") (g_argNoColor.c_str(), "Explicitly disable colored output, disabling terminal auto-detection.") (g_argNewReporter.c_str(), "Enables new diagnostics reporter.") + (g_argErrorRecovery.c_str(), "Enables additional parser error recovery.") (g_argIgnoreMissingFiles.c_str(), "Ignore missing files."); po::options_description outputComponents("Output Components"); outputComponents.add_options() @@ -923,6 +926,8 @@ bool CommandLineInterface::processInput() m_compiler->setSources(m_sourceCodes); if (m_args.count(g_argLibraries)) m_compiler->setLibraries(m_libraries); + if (m_args.count(g_argErrorRecovery)) + m_compiler->setParserErrorRecovery(true); m_compiler->setEVMVersion(m_evmVersion); // TODO: Perhaps we should not compile unless requested diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 24a5ad69e..09cc08a29 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -56,6 +56,7 @@ Testsuite const g_interactiveTestsuites[] = { {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, + {"ErrorRecovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SyntaxTest::create}, @@ -66,4 +67,3 @@ Testsuite const g_interactiveTestsuites[] = { } } } - diff --git a/test/liblangutil/CharStream.cpp b/test/liblangutil/CharStream.cpp new file mode 100644 index 000000000..abd3d02db --- /dev/null +++ b/test/liblangutil/CharStream.cpp @@ -0,0 +1,53 @@ +/* + 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 . +*/ +/** + * @author Rocky Bernstein + * @date 2019 + * Unit tests for the CharStream class. + */ + +#include +#include + +#include + +namespace langutil +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(CharStreamtest) + +BOOST_AUTO_TEST_CASE(test_fail) +{ + auto const source = std::make_shared("now is the time for testing", "source"); + + BOOST_CHECK('n' == source->get()); + BOOST_CHECK('n' == source->get()); + BOOST_CHECK('o' == source->advanceAndGet()); + BOOST_CHECK('n' == source->rollback(1)); + BOOST_CHECK('w' == source->setPosition(2)); + BOOST_REQUIRE_THROW( + source->setPosition(200), + ::langutil::InternalCompilerError + ); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} // end namespaces diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index faebfc3f5..c94e6c745 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -44,12 +44,15 @@ AnalysisFramework::parseAnalyseAndReturnError( string const& _source, bool _reportWarnings, bool _insertVersionPragma, - bool _allowMultipleErrors + bool _allowMultipleErrors, + bool _allowRecoveryErrors ) { compiler().reset(); compiler().setSources({{"", _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source}}); compiler().setEVMVersion(dev::test::Options::get().evmVersion()); + compiler().setParserErrorRecovery(_allowRecoveryErrors); + _allowMultipleErrors = _allowMultipleErrors || _allowRecoveryErrors; if (!compiler().parse()) { BOOST_FAIL("Parsing contract failed in analysis test suite:" + formatErrors()); diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h index cb590674f..3fcb9e74a 100644 --- a/test/libsolidity/AnalysisFramework.h +++ b/test/libsolidity/AnalysisFramework.h @@ -50,7 +50,8 @@ protected: std::string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true, - bool _allowMultipleErrors = false + bool _allowMultipleErrors = false, + bool _allowRecoveryErrors = false ); virtual ~AnalysisFramework() = default; diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index a08721f17..4cb7b6667 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -42,14 +42,16 @@ protected: std::string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true, - bool _allowMultipleErrors = false + bool _allowMultipleErrors = false, + bool _allowRecoveryErrors = false ) { return AnalysisFramework::parseAnalyseAndReturnError( "pragma experimental SMTChecker;\n" + _source, _reportWarnings, _insertVersionPragma, - _allowMultipleErrors + _allowMultipleErrors, + _allowRecoveryErrors ); } }; diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index 61d1d20e6..8845cd7e6 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 _evmVersion): m_evmVersion(_evmVersion) +SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion, bool _errorRecovery): m_evmVersion(_evmVersion) { ifstream file(_filename); if (!file) @@ -67,6 +67,7 @@ SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion m_settings.erase("optimize-yul"); } m_expectations = parseExpectations(file); + m_errorRecovery = _errorRecovery; } TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) @@ -75,6 +76,7 @@ TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix compiler().reset(); compiler().setSources({{"", versionPragma + m_source}}); compiler().setEVMVersion(m_evmVersion); + compiler().setParserErrorRecovery(m_errorRecovery); compiler().setOptimiserSettings( m_optimiseYul ? OptimiserSettings::full() : diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index 37a078107..dcf922de7 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -54,8 +54,14 @@ class SyntaxTest: AnalysisFramework, public EVMVersionRestrictedTestCase { public: static std::unique_ptr create(Config const& _config) - { return std::make_unique(_config.filename, _config.evmVersion); } - SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion); + { + return std::make_unique(_config.filename, _config.evmVersion, false); + } + static std::unique_ptr createErrorRecovery(Config const& _config) + { + return std::make_unique(_config.filename, _config.evmVersion, true); + } + SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion, bool _errorRecovery = false); TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; @@ -84,6 +90,7 @@ protected: std::vector m_errorList; bool m_optimiseYul = false; langutil::EVMVersion const m_evmVersion; + bool m_errorRecovery = false; }; } diff --git a/test/libsolidity/errorRecoveryTests/constructor_recovers.sol b/test/libsolidity/errorRecoveryTests/constructor_recovers.sol new file mode 100644 index 000000000..804bb7283 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/constructor_recovers.sol @@ -0,0 +1,20 @@ +pragma solidity >=0.0.0; + +contract Error1 { + constructor() public { + balances[tx.origin] = ; // missing RHS. + } + + // Without error recovery we stop due to the above error. + // Error recovery however recovers at the above ';' + // There should be an AST for the above, albeit with error + // nodes. + + // This function parses properly and should give AST info. + function five() public view returns(uint) { + return 5; + } +} +// ---- +// ParserError: (95-96): Expected primary expression. +// Warning: (95-96): Recovered in Statement at ';'. diff --git a/test/libsolidity/errorRecoveryTests/contract_recovery.sol b/test/libsolidity/errorRecoveryTests/contract_recovery.sol new file mode 100644 index 000000000..4047adb31 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/contract_recovery.sol @@ -0,0 +1,7 @@ +contract Errort6 { + using foo for ; // missing type name +} + +// ---- +// ParserError: (36-37): Expected type name +// Warning: (59-60): Recovered in ContractDefinition at '}'. diff --git a/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol b/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol new file mode 100644 index 000000000..a7e4254b0 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol @@ -0,0 +1,13 @@ +pragma solidity >=0.0.0; + +// Example to show why deleting the token at the +// is bad when error recovery is in effect. Here, ")" is missing +// and there is a ";" instead. That causes us to +// not be able to synchronize to ';'. Advance again and +// '}' is deleted and then we can't synchronize the contract. +// There should be an an AST created this contract (with errors). +contract Error2 { + mapping (address => uint balances; // missing ) before "balances" +} +// ---- +// ParserError: (417-425): Expected ')' but got identifier diff --git a/test/libsolidity/errorRecoveryTests/error_to_eos.sol b/test/libsolidity/errorRecoveryTests/error_to_eos.sol new file mode 100644 index 000000000..d396207b0 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/error_to_eos.sol @@ -0,0 +1,23 @@ +// Example which where scanning hits EOS, so we reset. +// Here we recover in the contractDefinition. +// There should be an an AST created this contract (with errors). +contract Error2 { + mapping (address => uint balances) // missing ; +} + +// There is no error in this contract +contract SendCoin { + function sendCoin(address receiver, uint amount) public returns(bool sufficient) { + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount; + balances[receiver] += amount; + emit Transfer(msg.sender, receiver, amount); + return true; + } +} + +// ---- +// ParserError: (212-220): Expected ')' but got identifier +// ParserError: (220-221): Expected ';' but got ')' +// ParserError: (220-221): Function, variable, struct or modifier declaration expected. +// Warning: (235-236): Recovered in ContractDefinition at '}'. diff --git a/test/libsolidity/errorRecoveryTests/missing_rhs.sol b/test/libsolidity/errorRecoveryTests/missing_rhs.sol new file mode 100644 index 000000000..991525b46 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/missing_rhs.sol @@ -0,0 +1,11 @@ +pragma solidity >=0.0.0; + +contract Error3 { + constructor() public { + balances[tx.origin] = ; // missing RHS. + } + +} +// ---- +// ParserError: (95-96): Expected primary expression. +// Warning: (95-96): Recovered in Statement at ';'. diff --git a/test/libsolidity/errorRecoveryTests/multiple_errors.sol b/test/libsolidity/errorRecoveryTests/multiple_errors.sol new file mode 100644 index 000000000..9127ef387 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/multiple_errors.sol @@ -0,0 +1,29 @@ +// An example with multiple errors. +// Most are caught by inserting an expected token. +// However some us S C Johnson recovery to +// skip over tokens. + +pragma solidity >=0.0.0; + +contract Error4 { + constructor() public { + balances[tx.origin] = 1 2; // missing operator + } + + function sendCoin(address receiver, uint amount) public returns(bool sufficient) { + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount // Missing ";" + balances[receiver] += amount // Another missing ";" + emit Transfer(msg.sender // truncated line + return true; + } + + +} +// ---- +// ParserError: (249-250): Expected ';' but got 'Number' +// ParserError: (471-479): Expected ';' but got identifier +// ParserError: (529-533): Expected ';' but got 'emit' +// ParserError: (577-583): Expected ',' but got 'return' +// ParserError: (577-583): Expected primary expression. +// Warning: (588-589): Recovered in Statement at ';'. From 0b65cf8af5af7ee3aa9ddad331f0733ec8361d2e Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 17 Jun 2019 12:01:31 +0200 Subject: [PATCH 060/115] Fixes stack-too-deep errors (soltest) on Windows by reducing recursion depth accordingly. (Caused by introducing try/catch blocks increased stack frame size) --- liblangutil/ParserBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liblangutil/ParserBase.cpp b/liblangutil/ParserBase.cpp index 2432ac711..7d00fedc4 100644 --- a/liblangutil/ParserBase.cpp +++ b/liblangutil/ParserBase.cpp @@ -137,7 +137,7 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN void ParserBase::increaseRecursionDepth() { m_recursionDepth++; - if (m_recursionDepth >= 2560) + if (m_recursionDepth >= 1200) fatalParserError("Maximum recursion depth reached during parsing."); } From 8ca27c2bb0a862439db36937cdbbd2dd755c862c Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Mon, 17 Jun 2019 15:26:40 +0200 Subject: [PATCH 061/115] [Sol->Yul] Style fixes addresses the points @chriseth mentioned in #6909 --- libsolidity/codegen/YulUtilFunctions.cpp | 32 ++++++++++++------------ libsolidity/codegen/YulUtilFunctions.h | 10 ++++---- libsolidity/codegen/ir/IRLValue.cpp | 10 ++++---- libsolidity/codegen/ir/IRLValue.h | 3 +++ 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 65991f864..7724dd57e 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -244,9 +244,9 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits) }); } -string YulUtilFunctions::dynamicShiftLeftFunction() +string YulUtilFunctions::shiftLeftFunctionDynamic() { - string functionName = "shift_left"; + string functionName = "shift_left_dynamic"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( @@ -293,12 +293,12 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) }); } -string YulUtilFunctions::dynamicShiftRightFunction() +string YulUtilFunctions::shiftRightFunctionDynamic() { // Note that if this is extended with signed shifts, // the opcodes SAR and SDIV behave differently with regards to rounding! - string const functionName = "shift_right_unsigned"; + string const functionName = "shift_right_unsigned_dynamic"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( @@ -341,11 +341,11 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift }); } -string YulUtilFunctions::dynamicUpdateByteSliceFunction(size_t _numBytes) +string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes) { solAssert(_numBytes <= 32, ""); size_t numBits = _numBytes * 8; - string functionName = "update_byte_slice_" + to_string(_numBytes); + string functionName = "update_byte_slice_dynamic" + to_string(_numBytes); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( @@ -359,7 +359,7 @@ string YulUtilFunctions::dynamicUpdateByteSliceFunction(size_t _numBytes) )") ("functionName", functionName) ("mask", formatNumber((bigint(1) << numBits) - 1)) - ("shl", dynamicShiftLeftFunction()) + ("shl", shiftLeftFunctionDynamic()) .render(); }); } @@ -671,11 +671,11 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool }); } -string YulUtilFunctions::dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes) +string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); string functionName = - "read_from_storage_" + + "read_from_storage_dynamic" + string(_splitFunctionTypes ? "split_" : "") + "_" + _type.identifier(); @@ -687,7 +687,7 @@ string YulUtilFunctions::dynamicReadFromStorage(Type const& _type, bool _splitFu } )") ("functionName", functionName) - ("extract", dynamicExtractFromStorageValue(_type, false)) + ("extract", extractFromStorageValueDynamic(_type, _splitFunctionTypes)) .render(); }); } @@ -715,7 +715,7 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::op ("update", _offset.is_initialized() ? updateByteSliceFunction(_type.storageBytes(), *_offset) : - dynamicUpdateByteSliceFunction(_type.storageBytes()) + updateByteSliceFunctionDynamic(_type.storageBytes()) ) ("offset", _offset.is_initialized() ? "" : "offset, ") ("prepare", prepareStoreFunction(_type)) @@ -733,12 +733,12 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::op }); } -string YulUtilFunctions::dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes) +string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); string functionName = - "extract_from_storage_value_" + + "extract_from_storage_value_dynamic" + string(_splitFunctionTypes ? "split_" : "") + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { @@ -748,8 +748,8 @@ string YulUtilFunctions::dynamicExtractFromStorageValue(Type const& _type, bool } )") ("functionName", functionName) - ("shr", dynamicShiftRightFunction()) - ("cleanupStorage", cleanupFromStorageFunction(_type, false)) + ("shr", shiftRightFunctionDynamic()) + ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes)) .render(); }); } @@ -772,7 +772,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs )") ("functionName", functionName) ("shr", shiftRightFunction(_offset * 8)) - ("cleanupStorage", cleanupFromStorageFunction(_type, false)) + ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes)) .render(); }); } diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index b4e0dc727..9b512f767 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -74,9 +74,9 @@ public: std::string leftAlignFunction(Type const& _type); std::string shiftLeftFunction(size_t _numBits); - std::string dynamicShiftLeftFunction(); + std::string shiftLeftFunctionDynamic(); std::string shiftRightFunction(size_t _numBits); - std::string dynamicShiftRightFunction(); + std::string shiftRightFunctionDynamic(); /// @returns the name of a function f(value, toInsert) -> newValue which replaces the /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant @@ -84,7 +84,7 @@ public: std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); /// signature: (value, shiftBytes, toInsert) -> result - std::string dynamicUpdateByteSliceFunction(size_t _numBytes); + std::string updateByteSliceFunctionDynamic(size_t _numBytes); /// @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. @@ -126,7 +126,7 @@ public: /// @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); - std::string dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes); + std::string readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes); /// @returns a function that extracts a value type from storage slot that has been /// retrieved already. @@ -134,7 +134,7 @@ public: /// @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); - std::string dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes); + std::string extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes); /// Returns the name of a function will write the given value to /// the specified slot and offset. If offset is not given, it is expected as diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index ff8e4bb13..defabd081 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -54,8 +54,8 @@ string IRLocalVariable::setToZero() const IRStorageItem::IRStorageItem( IRGenerationContext& _context, VariableDeclaration const& _varDecl -) -:IRStorageItem( +): + IRStorageItem( _context, *_varDecl.annotation().type, _context.storageLocationOfVariable(_varDecl) @@ -66,8 +66,8 @@ IRStorageItem::IRStorageItem( IRGenerationContext& _context, Type const& _type, std::pair slot_offset -) -: IRLValue(_context, &_type), +): + IRLValue(_context, &_type), m_slot(toCompactHexWithPrefix(slot_offset.first)), m_offset(slot_offset.second) { @@ -94,7 +94,7 @@ string IRStorageItem::retrieveValue() const solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); if (m_offset.type() == typeid(string)) return - m_context.utils().dynamicReadFromStorage(*m_type, false) + + m_context.utils().readFromStorageDynamic(*m_type, false) + "(" + m_slot + ", " + diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index 803223fe7..3c9338ebd 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -101,6 +101,9 @@ private: ); std::string const m_slot; + /// unsigned: Used when the offset is known at compile time, uses optimized + /// functions + /// string: Used when the offset is determined at run time boost::variant const m_offset; }; From df31461c5bb56b196b7f9f11832fc551c9fbd159 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Thu, 16 May 2019 12:50:32 +0200 Subject: [PATCH 062/115] Start to clarify revert vs require usage Start rewrite of require vs revert clarification Small clarification --- docs/control-structures.rst | 179 +++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 83 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 1505a8548..85e594707 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -66,28 +66,6 @@ as the actual contract has not been created yet. Functions of other contracts have to be called externally. For an external call, all function arguments have to be copied to memory. -.. warning:: - Be careful that ``feed.info.value(10).gas(800)`` only locally sets the ``value`` and amount of ``gas`` sent with the function call, and the parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost. - -Function calls cause exceptions if the called contract does not exist (in the -sense that the account does not contain code) or if the called contract itself -throws an exception or goes out of gas. - -.. warning:: - Any interaction with another contract imposes a potential danger, especially - if the source code of the contract is not known in advance. The - current contract hands over control to the called contract and that may potentially - do just about anything. Even if the called contract inherits from a known parent contract, - the inheriting contract is only required to have a correct interface. The - implementation of the contract, however, can be completely arbitrary and thus, - pose a danger. In addition, be prepared in case it calls into other contracts of - your system or even back into the calling contract before the first - call returns. This means - that the called contract can change state variables of the calling contract - via its functions. Write your functions in a way that, for example, calls to - external functions happen after any changes to state variables in your contract - so your contract is not vulnerable to a reentrancy exploit. - .. note:: A function call from one contract to another does not create its own transaction, it is a message call as part of the overall transaction. @@ -111,6 +89,28 @@ When calling functions of other contracts, you can specify the amount of Wei or You need to use the modifier ``payable`` with the ``info`` function because otherwise, the ``.value()`` option would not be available. +.. warning:: + Be careful that ``feed.info.value(10).gas(800)`` only locally sets the ``value`` and amount of ``gas`` sent with the function call, and the parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost. + +Function calls cause exceptions if the called contract does not exist (in the +sense that the account does not contain code) or if the called contract itself +throws an exception or goes out of gas. + +.. warning:: + Any interaction with another contract imposes a potential danger, especially + if the source code of the contract is not known in advance. The + current contract hands over control to the called contract and that may potentially + do just about anything. Even if the called contract inherits from a known parent contract, + the inheriting contract is only required to have a correct interface. The + implementation of the contract, however, can be completely arbitrary and thus, + pose a danger. In addition, be prepared in case it calls into other contracts of + your system or even back into the calling contract before the first + call returns. This means + that the called contract can change state variables of the calling contract + via its functions. Write your functions in a way that, for example, calls to + external functions happen after any changes to state variables in your contract + so your contract is not vulnerable to a reentrancy exploit. + Named Calls and Anonymous Function Parameters --------------------------------------------- @@ -247,16 +247,16 @@ groupings of expressions. It is not possible to mix variable declarations and non-declaration assignments, i.e. the following is not valid: ``(x, uint y) = (1, 2);`` -.. warning:: - Be careful when assigning to multiple variables at the same time when - reference types are involved, because it could lead to unexpected - copying behaviour. - .. note:: Prior to version 0.5.0 it was possible to assign to tuples of smaller size, either filling up on the left or on the right side (which ever was empty). This is now disallowed, so both sides have to have the same number of components. +.. warning:: + Be careful when assigning to multiple variables at the same time when + reference types are involved, because it could lead to unexpected + copying behaviour. + Complications for Arrays and Structs ------------------------------------ @@ -271,20 +271,19 @@ because only a reference and not a copy is passed. pragma solidity >=0.4.16 <0.7.0; - - contract C { + contract C { uint[20] x; - function f() public { + function f() public { g(x); h(x); } - function g(uint[20] memory y) internal pure { + function g(uint[20] memory y) internal pure { y[2] = 3; } - function h(uint[20] storage y) internal { + function h(uint[20] storage y) internal { y[3] = 4; } } @@ -355,7 +354,7 @@ In any case, you will get a warning about the outer variable being shadowed. for the entire function, regardless where it was declared. The following example shows a code snippet that used to compile but leads to an error starting from version 0.5.0. -:: + :: pragma solidity >=0.5.0 <0.7.0; // This will not compile @@ -374,33 +373,53 @@ In any case, you will get a warning about the outer variable being shadowed. Error handling: Assert, Require, Revert and Exceptions ====================================================== -Solidity uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the -state in the current call (and all its sub-calls) and also flag an error to the caller. -The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception -if the condition is not met. The ``assert`` function should only be used to test for internal errors, and to check invariants. -The ``require`` function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts. -If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix. +Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the +state in the current call (and all its sub-calls) and flags an error to the caller. -There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and -revert the current call. It is possible to provide a string message containing details about the error -that will be passed back to the caller. - -.. warning:: - The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. - -.. note:: - There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which - was deprecated in version 0.4.13 and removed in version 0.5.0. - -When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send`` -and the low-level functions ``call``, ``delegatecall`` and ``staticcall`` -- those return ``false`` as their first return value in case +When exceptions happen in a sub-call, they "bubble up" (i.e., exceptions are rethrown) automatically. Exceptions to this rule are ``send`` +and the low-level functions ``call``, ``delegatecall`` and ``staticcall``, they return ``false`` as their first return value in case of an exception instead of "bubbling up". -Catching exceptions is not yet possible. +.. warning:: + The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed. -In the following example, you can see how ``require`` can be used to easily check conditions on inputs -and how ``assert`` can be used for internal error checking. Note that you can optionally provide -a message string for ``require``, but not for ``assert``. +It is not yet possible to catch exceptions with Solidity. + +``assert`` and ``require`` +-------------------------- + +The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception +if the condition is not met. + +The ``assert`` function should only be used to test for internal errors, and to check invariants. Properly functioning code should never reach a failing ``assert`` statement; if this happens there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. + +An ``assert``-style exception is generated in the following situations: + +#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). +#. If you access a fixed-length ``bytesN`` at a too large or negative index. +#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). +#. If you shift by a negative amount. +#. If you convert a value too big or negative into an enum type. +#. If you call a zero-initialized variable of internal function type. +#. If you call ``assert`` with an argument that evaluates to false. + +The ``require`` function should be used to ensure valid conditions that cannot be detected until execution time. +These conditions include inputs, or contract state variables are met, or to validate return values from calls to external contracts. + +A ``require``-style exception is generated in the following situations: + +#. Calling ``require`` with an argument that evaluates to ``false``. +#. If you call a function via a message call but it does not finish properly (i.e., it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. +#. If you create a contract using the ``new`` keyword but the contract creation :ref:`does not finish properly`. +#. If you perform an external function call targeting a contract that contains no code. +#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). +#. If your contract receives Ether via a public getter function. +#. If a ``.transfer()`` fails. + +You can optionally provide a message string for ``require``, but not for ``assert``. + +The following example shows how you can use ``require`` to check conditions on inputs +and ``assert`` for internal error checking. :: @@ -419,34 +438,23 @@ a message string for ``require``, but not for ``assert``. } } -An ``assert``-style exception is generated in the following situations: - -#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). -#. If you access a fixed-length ``bytesN`` at a too large or negative index. -#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). -#. If you shift by a negative amount. -#. If you convert a value too big or negative into an enum type. -#. If you call a zero-initialized variable of internal function type. -#. If you call ``assert`` with an argument that evaluates to false. - -A ``require``-style exception is generated in the following situations: - -#. Calling ``require`` with an argument that evaluates to ``false``. -#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. -#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). -#. If you perform an external function call targeting a contract that contains no code. -#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). -#. If your contract receives Ether via a public getter function. -#. If a ``.transfer()`` fails. - Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation (instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect -did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction -(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while -``require``-style exceptions will not consume any gas starting from the Metropolis release. +did not occur. Because we want to keep the atomicity of transactions, the safest action is to revert all changes and make the whole transaction +(or at least call) without effect. -The following example shows how an error string can be used together with revert and require: +.. note:: + + ``assert``-style exceptions consume all gas available to the call, while ``require``-style exceptions do not consume any gas starting from the Metropolis release. + +``revert`` +---------- + +The ``revert`` function is another way to trigger exceptions from within other code blocks to flag an error and +revert the current call. The function takes an optional string message containing details about the error that is passed back to the caller. + +The following example shows how to use an error string together with ``revert`` and the equivalent ``require``: :: @@ -465,9 +473,10 @@ The following example shows how an error string can be used together with revert } } -The provided string will be :ref:`abi-encoded ` as if it were a call to a function ``Error(string)``. -In the above example, ``revert("Not enough Ether provided.");`` will cause the following hexadecimal data be -set as error return data: +The two syntax options are equivalent, it's developer preference which to use. + +The provided string is :ref:`abi-encoded ` as if it were a call to a function ``Error(string)``. +In the above example, ``revert("Not enough Ether provided.");`` returns the following hexadecimal as error return data: .. code:: @@ -475,3 +484,7 @@ set as error return data: 0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset 0x000000000000000000000000000000000000000000000000000000000000001a // String length 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data + +.. note:: + There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which + was deprecated in version 0.4.13 and removed in version 0.5.0. From 7e4896740daef2c777f5cb434edb0e95e82e3430 Mon Sep 17 00:00:00 2001 From: Chris Ward Date: Thu, 16 May 2019 12:27:30 +0200 Subject: [PATCH 063/115] Clarify that public applies to state variables --- docs/miscellaneous.rst | 2 +- docs/natspec-format.rst | 17 ++++++++++------- docs/types/mapping-types.rst | 2 +- docs/types/value-types.rst | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 69a08bdf9..137513b83 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -269,7 +269,7 @@ Tips and Tricks * Use ``delete`` on arrays to delete all its elements. * Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple ``SSTORE`` operations might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! -* Make your state variables public - the compiler will create :ref:`getters ` for you automatically. +* Make your state variables public - the compiler creates :ref:`getters ` for you automatically. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});`` diff --git a/docs/natspec-format.rst b/docs/natspec-format.rst index 297d9954c..2ba6d2188 100644 --- a/docs/natspec-format.rst +++ b/docs/natspec-format.rst @@ -36,12 +36,16 @@ Documentation is inserted above each ``class``, ``interface`` and documentation `__. The following example shows a contract and a function using all available tags. -Note: NatSpec currently does NOT apply to public state variables (see -`solidity#3418 `__), -even if they are declared public and therefore do affect the ABI. Note: -The Solidity compiler only interprets tags if they are external or -public. You are welcome to use similar comments for your internal and -private functions, but those will not be parsed. + +.. note:: + + NatSpec currently does NOT apply to public state variables (see + `solidity#3418 `__), + even if they are declared public and therefore do affect the ABI. + + The Solidity compiler only interprets tags if they are external or + public. You are welcome to use similar comments for your internal and + private functions, but those will not be parsed. .. code:: solidity @@ -196,4 +200,3 @@ file should also be produced and should look like this: }, "title" : "A simulator for trees" } - diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index 2414687c5..afee61202 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -25,7 +25,7 @@ in functions, or as parameters for library functions. They cannot be used as parameters or return parameters of contract functions that are publicly visible. -You can mark variables of mapping type as ``public`` and Solidity creates a +You can mark state variables of mapping type as ``public`` and Solidity creates a :ref:`getter ` for you. The ``_KeyType`` becomes a parameter for the getter. If ``_ValueType`` is a value type or a struct, the getter returns ``_ValueType``. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 507338f12..b8d67399a 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -333,7 +333,7 @@ type and this type is also used in the :ref:`ABI`. Contracts do not support any operators. The members of contract types are the external functions of the contract -including public state variables. +including any state variables marked as ``public``. For a contract ``C`` you can use ``type(C)`` to access :ref:`type information` about the contract. From 17e82ee17608c6974e144ddf4f02f34be0fd3b2d Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Mon, 17 Jun 2019 13:28:08 +0100 Subject: [PATCH 064/115] Clarify that interfaces only support enum since 0.5.0 --- docs/contracts/interfaces.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst index 43c8637ee..91e7d5947 100644 --- a/docs/contracts/interfaces.rst +++ b/docs/contracts/interfaces.rst @@ -34,3 +34,8 @@ Contracts can inherit interfaces as they would inherit other contracts. Types defined inside interfaces and other contract-like structures can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. + +.. warning: + + Interfaces have supported ``enum`` types since :doc:`Solidity version 0.5.0 <050-breaking-changes>`, make + sure the pragma version specifies this version as a minimum. \ No newline at end of file From 6cb6fe35efdb186562024979a736944a43b9cb02 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 28 May 2019 12:57:15 +0200 Subject: [PATCH 065/115] Make Yul optimizer not fail for wasm. --- Changelog.md | 1 + libsolidity/codegen/CompilerContext.cpp | 2 +- libyul/AssemblyStack.cpp | 21 +-- libyul/CMakeLists.txt | 6 +- libyul/CompilabilityChecker.cpp | 44 ++++--- libyul/Dialect.h | 3 + .../evm}/ConstantOptimiser.cpp | 6 +- .../evm}/ConstantOptimiser.h | 0 libyul/backends/evm/EVMDialect.h | 3 + libyul/backends/evm/EVMMetrics.cpp | 123 ++++++++++++++++++ libyul/backends/evm/EVMMetrics.h | 101 ++++++++++++++ libyul/backends/wasm/WasmDialect.h | 2 + libyul/optimiser/ControlFlowSimplifier.cpp | 51 ++++++-- libyul/optimiser/Metrics.cpp | 87 ------------- libyul/optimiser/Metrics.h | 72 ---------- libyul/optimiser/Suite.cpp | 5 +- test/libyul/YulOptimizerTest.cpp | 4 +- 17 files changed, 321 insertions(+), 210 deletions(-) rename libyul/{optimiser => backends/evm}/ConstantOptimiser.cpp (97%) rename libyul/{optimiser => backends/evm}/ConstantOptimiser.h (100%) create mode 100644 libyul/backends/evm/EVMMetrics.cpp create mode 100644 libyul/backends/evm/EVMMetrics.h diff --git a/Changelog.md b/Changelog.md index 994cff927..71b1378d0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ Language Features: Compiler Features: * Optimizer: Add rule to simplify SUB(~0, X) to NOT(X). * Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch. + * Yul Optimizer: Make the optimizer work for all dialects of Yul including eWasm. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index e208a8c3c..851038421 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -32,8 +32,8 @@ #include #include #include +#include #include -#include #include #include diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index dec7d21e8..1723e1fc2 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -146,15 +147,17 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation) for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast(subNode.get())) optimize(*subObject, false); - EVMDialect const& dialect = dynamic_cast(languageToDialect(m_language, m_evmVersion)); - GasMeter meter(dialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); - OptimiserSuite::run( - dialect, - meter, - *_object.code, - *_object.analysisInfo, - m_optimiserSettings.optimizeStackAllocation - ); + if (EVMDialect const* dialect = dynamic_cast(&languageToDialect(m_language, m_evmVersion))) + { + GasMeter meter(*dialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); + OptimiserSuite::run( + *dialect, + meter, + *_object.code, + *_object.analysisInfo, + m_optimiserSettings.optimizeStackAllocation + ); + } } MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 12a31c2bb..bf09207b6 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -28,6 +28,8 @@ add_library(yul backends/evm/AbstractAssembly.h backends/evm/AsmCodeGen.h backends/evm/AsmCodeGen.cpp + backends/evm/ConstantOptimiser.cpp + backends/evm/ConstantOptimiser.h backends/evm/EVMAssembly.cpp backends/evm/EVMAssembly.h backends/evm/EVMCodeTransform.cpp @@ -36,6 +38,8 @@ add_library(yul backends/evm/EVMDialect.h backends/evm/EVMObjectCompiler.cpp backends/evm/EVMObjectCompiler.h + backends/evm/EVMMetrics.cpp + backends/evm/EVMMetrics.h backends/evm/NoOutputAssembly.h backends/evm/NoOutputAssembly.cpp backends/wasm/EWasmCodeTransform.cpp @@ -58,8 +62,6 @@ add_library(yul optimiser/BlockHasher.h optimiser/CommonSubexpressionEliminator.cpp optimiser/CommonSubexpressionEliminator.h - optimiser/ConstantOptimiser.cpp - optimiser/ConstantOptimiser.h optimiser/ControlFlowSimplifier.cpp optimiser/ControlFlowSimplifier.h optimiser/DataFlowAnalyzer.cpp diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 7f065e884..c25b33c98 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -43,27 +43,31 @@ map CompilabilityChecker::run( solAssert(_dialect.flavour == AsmFlavour::Strict, ""); - solAssert(dynamic_cast(&_dialect), ""); - NoOutputEVMDialect noOutputDialect(dynamic_cast(_dialect)); - BuiltinContext builtinContext; - - yul::AsmAnalysisInfo analysisInfo = - yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast); - - NoOutputAssembly assembly; - CodeTransform transform(assembly, analysisInfo, _ast, noOutputDialect, builtinContext, _optimizeStackAllocation); - try + if (EVMDialect const* evmDialect = dynamic_cast(&_dialect)) { - transform(_ast); - } - catch (StackTooDeepError const&) - { - solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored."); - } + NoOutputEVMDialect noOutputDialect(*evmDialect); + BuiltinContext builtinContext; - std::map functions; - for (StackTooDeepError const& error: transform.stackErrors()) - functions[error.functionName] = max(error.depth, functions[error.functionName]); + yul::AsmAnalysisInfo analysisInfo = + yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast); - return functions; + NoOutputAssembly assembly; + CodeTransform transform(assembly, analysisInfo, _ast, noOutputDialect, builtinContext, _optimizeStackAllocation); + try + { + transform(_ast); + } + catch (StackTooDeepError const&) + { + solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored."); + } + + std::map functions; + for (StackTooDeepError const& error: transform.stackErrors()) + functions[error.functionName] = max(error.depth, functions[error.functionName]); + + return functions; + } + else + return {}; } diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 4fc33edbc..6e2fb6585 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -66,6 +66,9 @@ struct Dialect: boost::noncopyable /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; } + virtual BuiltinFunction const* discardFunction() const { return nullptr; } + virtual BuiltinFunction const* equalityFunction() const { return nullptr; } + Dialect(AsmFlavour _flavour): flavour(_flavour) {} virtual ~Dialect() = default; diff --git a/libyul/optimiser/ConstantOptimiser.cpp b/libyul/backends/evm/ConstantOptimiser.cpp similarity index 97% rename from libyul/optimiser/ConstantOptimiser.cpp rename to libyul/backends/evm/ConstantOptimiser.cpp index 2fa200e73..f8b4854d8 100644 --- a/libyul/optimiser/ConstantOptimiser.cpp +++ b/libyul/backends/evm/ConstantOptimiser.cpp @@ -18,14 +18,12 @@ * Optimisation stage that replaces constants by expressions that compute them. */ -#include +#include #include -#include +#include #include -#include #include -#include #include diff --git a/libyul/optimiser/ConstantOptimiser.h b/libyul/backends/evm/ConstantOptimiser.h similarity index 100% rename from libyul/optimiser/ConstantOptimiser.h rename to libyul/backends/evm/ConstantOptimiser.h diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 56dc99f87..bcea185f4 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -68,6 +68,9 @@ struct EVMDialect: public Dialect /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. BuiltinFunctionForEVM const* builtin(YulString _name) const override; + BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); } + BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); } + static EVMDialect const& looseAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _version); diff --git a/libyul/backends/evm/EVMMetrics.cpp b/libyul/backends/evm/EVMMetrics.cpp new file mode 100644 index 000000000..5211e5132 --- /dev/null +++ b/libyul/backends/evm/EVMMetrics.cpp @@ -0,0 +1,123 @@ +/* + 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 . +*/ +/** +* Module providing metrics for the EVM optimizer. +*/ + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace yul; + +size_t GasMeter::costs(Expression const& _expression) const +{ + return combineCosts(GasMeterVisitor::costs(_expression, m_dialect, m_isCreation)); +} + +size_t GasMeter::instructionCosts(eth::Instruction _instruction) const +{ + return combineCosts(GasMeterVisitor::instructionCosts(_instruction, m_dialect, m_isCreation)); +} + +size_t GasMeter::combineCosts(std::pair _costs) const +{ + return _costs.first * m_runs + _costs.second; +} + + +pair GasMeterVisitor::costs( + Expression const& _expression, + EVMDialect const& _dialect, + bool _isCreation +) +{ + GasMeterVisitor gmv(_dialect, _isCreation); + gmv.visit(_expression); + return {gmv.m_runGas, gmv.m_dataGas}; +} + +pair GasMeterVisitor::instructionCosts( + dev::eth::Instruction _instruction, + EVMDialect const& _dialect, + bool _isCreation +) +{ + GasMeterVisitor gmv(_dialect, _isCreation); + gmv.instructionCostsInternal(_instruction); + return {gmv.m_runGas, gmv.m_dataGas}; +} + +void GasMeterVisitor::operator()(FunctionCall const& _funCall) +{ + ASTWalker::operator()(_funCall); + if (BuiltinFunctionForEVM const* f = m_dialect.builtin(_funCall.functionName.name)) + if (f->instruction) + { + instructionCostsInternal(*f->instruction); + return; + } + yulAssert(false, "Functions not implemented."); +} + +void GasMeterVisitor::operator()(FunctionalInstruction const& _fun) +{ + ASTWalker::operator()(_fun); + instructionCostsInternal(_fun.instruction); +} + +void GasMeterVisitor::operator()(Literal const& _lit) +{ + m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::PUSH1); + m_dataGas += + singleByteDataGas() + + size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation)); +} + +void GasMeterVisitor::operator()(Identifier const&) +{ + m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::DUP1); + m_dataGas += singleByteDataGas(); +} + +size_t GasMeterVisitor::singleByteDataGas() const +{ + if (m_isCreation) + return dev::eth::GasCosts::txDataNonZeroGas; + else + return dev::eth::GasCosts::createDataGas; +} + +void GasMeterVisitor::instructionCostsInternal(dev::eth::Instruction _instruction) +{ + if (_instruction == eth::Instruction::EXP) + m_runGas += dev::eth::GasCosts::expGas + dev::eth::GasCosts::expByteGas(m_dialect.evmVersion()); + else + m_runGas += dev::eth::GasMeter::runGas(_instruction); + m_dataGas += singleByteDataGas(); +} diff --git a/libyul/backends/evm/EVMMetrics.h b/libyul/backends/evm/EVMMetrics.h new file mode 100644 index 000000000..8a8987b94 --- /dev/null +++ b/libyul/backends/evm/EVMMetrics.h @@ -0,0 +1,101 @@ +/* + 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 . +*/ +/** + * Module providing metrics for the optimizer. + */ + +#pragma once + +#include +#include +#include + +namespace yul +{ + +struct EVMDialect; + +/** + * Gas meter for expressions only involving literals, identifiers and + * EVM instructions. + * + * Assumes that EXP is not used with exponents larger than a single byte. + * Is not particularly exact for anything apart from arithmetic. + */ +class GasMeter +{ +public: + GasMeter(EVMDialect const& _dialect, bool _isCreation, size_t _runs): + m_dialect(_dialect), + m_isCreation{_isCreation}, + m_runs(_runs) + {} + + /// @returns the full combined costs of deploying and evaluating the expression. + size_t costs(Expression const& _expression) const; + /// @returns the combined costs of deploying and running the instruction, not including + /// the costs for its arguments. + size_t instructionCosts(dev::eth::Instruction _instruction) const; + +private: + size_t combineCosts(std::pair _costs) const; + + EVMDialect const& m_dialect; + bool m_isCreation = false; + size_t m_runs; +}; + +class GasMeterVisitor: public ASTWalker +{ +public: + static std::pair costs( + Expression const& _expression, + EVMDialect const& _dialect, + bool _isCreation + ); + + static std::pair instructionCosts( + dev::eth::Instruction _instruction, + EVMDialect const& _dialect, + bool _isCreation = false + ); + +public: + GasMeterVisitor(EVMDialect const& _dialect, bool _isCreation): + m_dialect(_dialect), + m_isCreation{_isCreation} + {} + + void operator()(FunctionCall const& _funCall) override; + void operator()(FunctionalInstruction const& _instr) override; + void operator()(Literal const& _literal) override; + void operator()(Identifier const& _identifier) override; + +private: + size_t singleByteDataGas() const; + /// Computes the cost of storing and executing the single instruction (excluding its arguments). + /// For EXP, it assumes that the exponent is at most 255. + /// Does not work particularly exact for anything apart from arithmetic. + void instructionCostsInternal(dev::eth::Instruction _instruction); + + EVMDialect const& m_dialect; + bool m_isCreation = false; + size_t m_runGas = 0; + size_t m_dataGas = 0; +}; + +} diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 0948a8049..4ade0be54 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -45,6 +45,8 @@ struct WasmDialect: public Dialect WasmDialect(); BuiltinFunction const* builtin(YulString _name) const override; + BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); } + BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); } static WasmDialect const& instance(); diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp index ccd88051f..7c826aa28 100644 --- a/libyul/optimiser/ControlFlowSimplifier.cpp +++ b/libyul/optimiser/ControlFlowSimplifier.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -33,11 +34,16 @@ using OptionalStatements = boost::optional>; namespace { -ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _location, Expression&& _expression) +ExpressionStatement makeDiscardCall( + langutil::SourceLocation const& _location, + Dialect const& _dialect, + Expression&& _expression +) { + yulAssert(_dialect.discardFunction(), "No discard function available."); return {_location, FunctionCall{ _location, - Identifier{_location, "pop"_yulstring}, + Identifier{_location, _dialect.discardFunction()->name}, {std::move(_expression)} }}; } @@ -66,36 +72,55 @@ void removeEmptyCasesFromSwitch(Switch& _switchStmt) ); } -OptionalStatements reduceNoCaseSwitch(Switch& _switchStmt) +OptionalStatements reduceNoCaseSwitch(Dialect const& _dialect, Switch& _switchStmt) { yulAssert(_switchStmt.cases.empty(), "Expected no case!"); + if (!_dialect.discardFunction()) + return {}; auto loc = locationOf(*_switchStmt.expression); - return make_vector(makePopExpressionStatement(loc, std::move(*_switchStmt.expression))); + return make_vector(makeDiscardCall( + loc, + _dialect, + std::move(*_switchStmt.expression) + )); } -OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt) +OptionalStatements reduceSingleCaseSwitch(Dialect const& _dialect, Switch& _switchStmt) { yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!"); auto& switchCase = _switchStmt.cases.front(); auto loc = locationOf(*_switchStmt.expression); if (switchCase.value) + { + if (!_dialect.equalityFunction()) + return {}; return make_vector(If{ std::move(_switchStmt.location), make_unique(FunctionCall{ loc, - Identifier{loc, "eq"_yulstring}, + Identifier{loc, _dialect.equalityFunction()->name}, {std::move(*switchCase.value), std::move(*_switchStmt.expression)} }), std::move(switchCase.body) }); + } else + { + if (!_dialect.discardFunction()) + return {}; + return make_vector( - makePopExpressionStatement(loc, std::move(*_switchStmt.expression)), + makeDiscardCall( + loc, + _dialect, + std::move(*_switchStmt.expression) + ), std::move(switchCase.body) ); + } } } @@ -151,10 +176,14 @@ void ControlFlowSimplifier::simplify(std::vector& _statements) { GenericFallbackReturnsVisitor const visitor( [&](If& _ifStmt) -> OptionalStatements { - if (_ifStmt.body.statements.empty()) + if (_ifStmt.body.statements.empty() && m_dialect.discardFunction()) { OptionalStatements s = vector{}; - s->emplace_back(makePopExpressionStatement(_ifStmt.location, std::move(*_ifStmt.condition))); + s->emplace_back(makeDiscardCall( + _ifStmt.location, + m_dialect, + std::move(*_ifStmt.condition) + )); return s; } return {}; @@ -164,9 +193,9 @@ void ControlFlowSimplifier::simplify(std::vector& _statements) removeEmptyCasesFromSwitch(_switchStmt); if (_switchStmt.cases.empty()) - return reduceNoCaseSwitch(_switchStmt); + return reduceNoCaseSwitch(m_dialect, _switchStmt); else if (_switchStmt.cases.size() == 1) - return reduceSingleCaseSwitch(_switchStmt); + return reduceSingleCaseSwitch(m_dialect, _switchStmt); return {}; } diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index a8f9807df..dfc8d98b5 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -169,93 +169,6 @@ void CodeCost::addInstructionCost(eth::Instruction _instruction) m_cost += 49; } -size_t GasMeter::costs(Expression const& _expression) const -{ - return combineCosts(GasMeterVisitor::costs(_expression, m_dialect, m_isCreation)); -} - -size_t GasMeter::instructionCosts(eth::Instruction _instruction) const -{ - return combineCosts(GasMeterVisitor::instructionCosts(_instruction, m_dialect, m_isCreation)); -} - -size_t GasMeter::combineCosts(std::pair _costs) const -{ - return _costs.first * m_runs + _costs.second; -} - - -pair GasMeterVisitor::costs( - Expression const& _expression, - EVMDialect const& _dialect, - bool _isCreation -) -{ - GasMeterVisitor gmv(_dialect, _isCreation); - gmv.visit(_expression); - return {gmv.m_runGas, gmv.m_dataGas}; -} - -pair GasMeterVisitor::instructionCosts( - dev::eth::Instruction _instruction, - EVMDialect const& _dialect, - bool _isCreation -) -{ - GasMeterVisitor gmv(_dialect, _isCreation); - gmv.instructionCostsInternal(_instruction); - return {gmv.m_runGas, gmv.m_dataGas}; -} - -void GasMeterVisitor::operator()(FunctionCall const& _funCall) -{ - ASTWalker::operator()(_funCall); - if (BuiltinFunctionForEVM const* f = m_dialect.builtin(_funCall.functionName.name)) - if (f->instruction) - { - instructionCostsInternal(*f->instruction); - return; - } - yulAssert(false, "Functions not implemented."); -} - -void GasMeterVisitor::operator()(FunctionalInstruction const& _fun) -{ - ASTWalker::operator()(_fun); - instructionCostsInternal(_fun.instruction); -} - -void GasMeterVisitor::operator()(Literal const& _lit) -{ - m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::PUSH1); - m_dataGas += - singleByteDataGas() + - size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation)); -} - -void GasMeterVisitor::operator()(Identifier const&) -{ - m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::DUP1); - m_dataGas += singleByteDataGas(); -} - -size_t GasMeterVisitor::singleByteDataGas() const -{ - if (m_isCreation) - return dev::eth::GasCosts::txDataNonZeroGas; - else - return dev::eth::GasCosts::createDataGas; -} - -void GasMeterVisitor::instructionCostsInternal(dev::eth::Instruction _instruction) -{ - if (_instruction == eth::Instruction::EXP) - m_runGas += dev::eth::GasCosts::expGas + dev::eth::GasCosts::expByteGas(m_dialect.evmVersion()); - else - m_runGas += dev::eth::GasMeter::runGas(_instruction); - m_dataGas += singleByteDataGas(); -} - void AssignmentCounter::operator()(Assignment const& _assignment) { for (auto const& variable: _assignment.variableNames) diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index 51c6df9a7..5fe3eef7b 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -22,9 +22,6 @@ #include #include -#include - -#include namespace yul { @@ -96,75 +93,6 @@ private: size_t m_cost = 0; }; -/** - * Gas meter for expressions only involving literals, identifiers and - * EVM instructions. - * - * Assumes that EXP is not used with exponents larger than a single byte. - * Is not particularly exact for anything apart from arithmetic. - */ -class GasMeter -{ -public: - GasMeter(EVMDialect const& _dialect, bool _isCreation, size_t _runs): - m_dialect(_dialect), - m_isCreation{_isCreation}, - m_runs(_runs) - {} - - /// @returns the full combined costs of deploying and evaluating the expression. - size_t costs(Expression const& _expression) const; - /// @returns the combined costs of deploying and running the instruction, not including - /// the costs for its arguments. - size_t instructionCosts(dev::eth::Instruction _instruction) const; - -private: - size_t combineCosts(std::pair _costs) const; - - EVMDialect const& m_dialect; - bool m_isCreation = false; - size_t m_runs; -}; - -class GasMeterVisitor: public ASTWalker -{ -public: - static std::pair costs( - Expression const& _expression, - EVMDialect const& _dialect, - bool _isCreation - ); - - static std::pair instructionCosts( - dev::eth::Instruction _instruction, - EVMDialect const& _dialect, - bool _isCreation = false - ); - -public: - GasMeterVisitor(EVMDialect const& _dialect, bool _isCreation): - m_dialect(_dialect), - m_isCreation{_isCreation} - {} - - void operator()(FunctionCall const& _funCall) override; - void operator()(FunctionalInstruction const& _instr) override; - void operator()(Literal const& _literal) override; - void operator()(Identifier const& _identifier) override; - -private: - size_t singleByteDataGas() const; - /// Computes the cost of storing and executing the single instruction (excluding its arguments). - /// For EXP, it assumes that the exponent is at most 255. - /// Does not work particularly exact for anything apart from arithmetic. - void instructionCostsInternal(dev::eth::Instruction _instruction); - - EVMDialect const& m_dialect; - bool m_isCreation = false; - size_t m_runGas = 0; - size_t m_dataGas = 0; -}; - /** * Counts the number of assignments to every variable. * Only works after running the Disambiguator. diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 4772bbb3a..05b4b1b40 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -209,7 +209,8 @@ void OptimiserSuite::run( FunctionGrouper{}(ast); - ConstantOptimiser{dynamic_cast(_dialect), _meter}(ast); + if (EVMDialect const* dialect = dynamic_cast(&_dialect)) + ConstantOptimiser{*dialect, _meter}(ast); VarNameCleaner{ast, _dialect, reservedIdentifiers}(ast); yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, ast); diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 3d2cd50eb..496f0837c 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -47,8 +46,9 @@ #include #include #include -#include +#include #include +#include #include #include #include From 05a67c486ee80ab7e96af327faead150bd2fd6f6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 29 May 2019 22:26:18 +0200 Subject: [PATCH 066/115] Use "what" in exception reports. --- libsolidity/interface/StandardCompiler.cpp | 9 +++++++++ solc/CommandLineInterface.cpp | 22 ++++++++++++++++++++++ test/tools/isoltest.cpp | 6 ++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 8cd8dea8a..28bb4174a 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -769,6 +769,15 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting "Exception during compilation: " + boost::diagnostic_information(_exception) )); } + catch (std::exception const& _e) + { + errors.append(formatError( + false, + "Exception", + "general", + "Unknown exception during compilation" + (_e.what() ? ": " + string(_e.what()) : ".") + )); + } catch (...) { errors.append(formatError( diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index a6e2f6025..b88284b08 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -989,6 +989,13 @@ bool CommandLineInterface::processInput() serr() << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl; return false; } + catch (std::exception const& _e) + { + serr() << "Unknown exception during compilation" << ( + _e.what() ? ": " + string(_e.what()) : "." + ) << endl; + return false; + } catch (...) { serr() << "Unknown exception during compilation." << endl; @@ -1279,6 +1286,14 @@ bool CommandLineInterface::assemble( serr() << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl; return false; } + catch (std::exception const& _e) + { + serr() << + "Unknown exception during compilation" << + (_e.what() ? ": " + string(_e.what()) : ".") << + endl; + return false; + } catch (...) { serr() << "Unknown exception in assembler." << endl; @@ -1329,6 +1344,13 @@ bool CommandLineInterface::assemble( serr() << "Exception while assembling: " << boost::diagnostic_information(_exception) << endl; return false; } + catch (std::exception const& _e) + { + serr() << "Unknown exception during compilation" << ( + _e.what() ? ": " + string(_e.what()) : "." + ) << endl; + return false; + } catch (...) { serr() << "Unknown exception while assembling." << endl; diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index a127525c1..f5c365e57 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -182,7 +182,7 @@ TestTool::Result TestTool::process() else return Result::Skipped; } - catch(boost::exception const& _e) + catch (boost::exception const& _e) { AnsiColorized(cout, formatted, {BOLD, RED}) << "Exception during test: " << boost::diagnostic_information(_e) << endl; @@ -191,7 +191,9 @@ TestTool::Result TestTool::process() catch (std::exception const& _e) { AnsiColorized(cout, formatted, {BOLD, RED}) << - "Exception during test: " << _e.what() << endl; + "Exception during test" << + (_e.what() ? ": " + string(_e.what()) : ".") << + endl; return Result::Exception; } catch (...) From aed979604c84fc90330c0ca501ef6cc2a34e336b Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 29 May 2019 23:23:51 +0200 Subject: [PATCH 067/115] Fix unused pruner. --- libyul/optimiser/UnusedPruner.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index 00ccbc8e5..624f9d335 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -92,12 +93,10 @@ void UnusedPruner::operator()(Block& _block) subtractReferences(ReferencesCounter::countReferences(*varDecl.value)); statement = Block{std::move(varDecl.location), {}}; } - else if (varDecl.variables.size() == 1) - // In pure Yul, this should be replaced by a function call to `drop` - // instead of `pop`. - statement = ExpressionStatement{varDecl.location, FunctionalInstruction{ + else if (varDecl.variables.size() == 1 && m_dialect.discardFunction()) + statement = ExpressionStatement{varDecl.location, FunctionCall{ varDecl.location, - dev::eth::Instruction::POP, + {varDecl.location, m_dialect.discardFunction()->name}, {*std::move(varDecl.value)} }}; } From 211cd2a0b3b331ab092f0af656ecac852f60e108 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 18 Jun 2019 18:12:30 +0200 Subject: [PATCH 068/115] Fix Asm parser. --- libyul/AsmParser.cpp | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 9686e7dde..ae3056f21 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -384,17 +384,10 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() case Token::Identifier: case Token::Return: case Token::Byte: + case Token::Bool: case Token::Address: { - YulString literal; - if (currentToken() == Token::Return) - literal = "return"_yulstring; - else if (currentToken() == Token::Byte) - literal = "byte"_yulstring; - else if (currentToken() == Token::Address) - literal = "address"_yulstring; - else - literal = YulString{currentLiteral()}; + YulString literal{currentLiteral()}; // first search the set of builtins, then the instructions. if (m_dialect.builtin(literal)) { @@ -648,26 +641,25 @@ TypedName Parser::parseTypedName() YulString Parser::expectAsmIdentifier() { - YulString name = YulString{currentLiteral()}; - if (m_dialect.flavour == AsmFlavour::Yul) + YulString name{currentLiteral()}; + switch (currentToken()) { - switch (currentToken()) - { - case Token::Return: - case Token::Byte: - case Token::Address: - case Token::Bool: - advance(); - return name; - default: - break; - } + case Token::Return: + case Token::Byte: + case Token::Address: + case Token::Bool: + case Token::Identifier: + break; + default: + expectToken(Token::Identifier); + break; } - else if (m_dialect.builtin(name)) + + if (m_dialect.builtin(name)) fatalParserError("Cannot use builtin function name \"" + name.str() + "\" as identifier name."); - else if (instructions().count(name.str())) - fatalParserError("Cannot use instruction names for identifier names."); - expectToken(Token::Identifier); + else if (m_dialect.flavour == AsmFlavour::Loose && instructions().count(name.str())) + fatalParserError("Cannot use instruction name \"" + name.str() + "\" as identifier name."); + advance(); return name; } From 1a7e09ab7a7fa0f18bb588a03b61802236fdb74d Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 30 May 2019 10:35:42 -0400 Subject: [PATCH 069/115] Add errorRecovery boolean to StandardCompiler --- libsolidity/interface/StandardCompiler.cpp | 10 +++++- libsolidity/interface/StandardCompiler.h | 1 + test/libsolidity/StandardCompiler.cpp | 38 +++++++++++++++++++++- test/libsolidity/SyntaxTest.cpp | 6 ++-- test/libsolidity/SyntaxTest.h | 4 +-- 5 files changed, 52 insertions(+), 7 deletions(-) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 8cd8dea8a..d708835c8 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -301,7 +301,7 @@ boost::optional checkAuxiliaryInputKeys(Json::Value const& _input) boost::optional checkSettingsKeys(Json::Value const& _input) { - static set keys{"evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; + static set keys{"parserErrorRecovery", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; return checkKeys(_input, keys, "settings"); } @@ -576,6 +576,13 @@ boost::variant StandardCompile if (auto result = checkSettingsKeys(settings)) return *result; + if (settings.isMember("parserErrorRecovery")) + { + if (!settings["parserErrorRecovery"].isBool()) + return formatFatalError("JSONError", "\"settings.parserErrorRecovery\" must be a Boolean."); + ret.parserErrorRecovery = settings["parserErrorRecovery"].asBool(); + } + if (settings.isMember("evmVersion")) { if (!settings["evmVersion"].isString()) @@ -675,6 +682,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses) compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); + compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery); compilerStack.setRemappings(_inputsAndSettings.remappings); compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); compilerStack.setLibraries(_inputsAndSettings.libraries); diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index daae7797c..9d0320abb 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -60,6 +60,7 @@ private: { std::string language; Json::Value errors; + bool parserErrorRecovery = false; std::map sources; std::map smtLib2Responses; langutil::EVMVersion evmVersion; diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 067aa8883..c6dd11b6d 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -209,7 +209,6 @@ BOOST_AUTO_TEST_CASE(unexpected_trailing_test) BOOST_CHECK(containsError(result, "JSONError", "* Line 10, Column 2\n Extra non-whitespace after JSON value.\n")); } - BOOST_AUTO_TEST_CASE(smoke_test) { char const* input = R"( @@ -226,6 +225,43 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_CHECK(containsAtMostWarnings(result)); } +BOOST_AUTO_TEST_CASE(error_recovery_field) +{ + auto input = R"( + { + "language": "Solidity", + "settings": { + "parserErrorRecovery": "1" + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"settings.parserErrorRecovery\" must be a Boolean.")); + + input = R"( + { + "language": "Solidity", + "settings": { + "parserErrorRecovery": true + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + + result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); +} + BOOST_AUTO_TEST_CASE(optimizer_enabled_not_boolean) { char const* input = R"( diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index 8845cd7e6..45ca1e81d 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 _evmVersion, bool _errorRecovery): m_evmVersion(_evmVersion) +SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion, bool _parserErrorRecovery): m_evmVersion(_evmVersion) { ifstream file(_filename); if (!file) @@ -67,7 +67,7 @@ SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion m_settings.erase("optimize-yul"); } m_expectations = parseExpectations(file); - m_errorRecovery = _errorRecovery; + m_parserErrorRecovery = _parserErrorRecovery; } TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) @@ -76,7 +76,7 @@ TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix compiler().reset(); compiler().setSources({{"", versionPragma + m_source}}); compiler().setEVMVersion(m_evmVersion); - compiler().setParserErrorRecovery(m_errorRecovery); + compiler().setParserErrorRecovery(m_parserErrorRecovery); compiler().setOptimiserSettings( m_optimiseYul ? OptimiserSettings::full() : diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index dcf922de7..62a85d864 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -61,7 +61,7 @@ public: { return std::make_unique(_config.filename, _config.evmVersion, true); } - SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion, bool _errorRecovery = false); + SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion, bool _parserErrorRecovery = false); TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; @@ -90,7 +90,7 @@ protected: std::vector m_errorList; bool m_optimiseYul = false; langutil::EVMVersion const m_evmVersion; - bool m_errorRecovery = false; + bool m_parserErrorRecovery = false; }; } From bfe074b2b1c886d648b9efa76ea421e8a3ec4aab Mon Sep 17 00:00:00 2001 From: mingchuan Date: Wed, 19 Jun 2019 10:46:05 +0800 Subject: [PATCH 070/115] Fix storage array abi encoding Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots --- Changelog.md | 4 ++++ libsolidity/codegen/ABIFunctions.h | 2 +- libsolidity/codegen/YulUtilFunctions.cpp | 29 ++++++++++++++++-------- libsolidity/codegen/YulUtilFunctions.h | 2 +- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Changelog.md b/Changelog.md index 243284272..0474ad0d0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ ### 0.5.10 (unreleased) +Important Bugfixes: + * Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots + + Language Features: diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index a54b403d9..eba01d765 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -154,7 +154,7 @@ private: EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given memory array or - /// a given storage array with one item per slot. + /// a given storage array with every item occupies one or multiple full slots. std::string abiEncodingFunctionSimpleArray( ArrayType const& _givenType, ArrayType const& _targetType, diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 65991f864..4f9c4a109 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -589,18 +589,29 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) } )"); templ("functionName", functionName); - if (_type.location() == DataLocation::Memory) + switch (_type.location()) + { + case DataLocation::Memory: templ("advance", "0x20"); - else if (_type.location() == DataLocation::Storage) - templ("advance", "1"); - else if (_type.location() == DataLocation::CallData) - templ("advance", toCompactHexWithPrefix( + break; + case DataLocation::Storage: + { + u256 size = _type.baseType()->storageSize(); + solAssert(size >= 1, ""); + templ("advance", toCompactHexWithPrefix(size)); + break; + } + case DataLocation::CallData: + { + u256 size = _type.baseType()->isDynamicallyEncoded() ? 32 : - _type.baseType()->calldataEncodedSize() - )); - else - solAssert(false, ""); + _type.baseType()->calldataEncodedSize(); + solAssert(size >= 32 && size % 32 == 0, ""); + templ("advance", toCompactHexWithPrefix(size)); + break; + } + } return templ.render(); }); } diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index b4e0dc727..c33a91ce1 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -113,7 +113,7 @@ public: /// for the data position of an array which is stored in that slot / memory area / calldata area. std::string arrayDataAreaFunction(ArrayType const& _type); /// @returns the name of a function that advances an array data pointer to the next element. - /// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot. + /// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots. std::string nextArrayElementFunction(ArrayType const& _type); /// @returns the name of a function that performs index access for mappings. From 13c140966952d37f7a1b78072780101301c98a41 Mon Sep 17 00:00:00 2001 From: mingchuan Date: Wed, 19 Jun 2019 10:46:47 +0800 Subject: [PATCH 071/115] Add test case that abi.encode nested storage arrays --- .../abiEncoderV2/storage_array_encoding.sol | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol diff --git a/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol b/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol new file mode 100644 index 000000000..a046a4376 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol @@ -0,0 +1,21 @@ +pragma experimental ABIEncoderV2; + +// tests encoding from storage arrays + +contract C { + uint256[2][] tmp_h; + function h(uint256[2][] calldata s) external returns (bytes memory) { + tmp_h = s; + return abi.encode(tmp_h); + } + uint256[2][2] tmp_i; + function i(uint256[2][2] calldata s) external returns (bytes memory) { + tmp_i = s; + return abi.encode(tmp_i); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// h(uint256[2][]) : 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324 +// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224 From 6a5fabd0040062ac048a401fe199fbed9dfa8dba Mon Sep 17 00:00:00 2001 From: mingchuan Date: Wed, 19 Jun 2019 10:47:44 +0800 Subject: [PATCH 072/115] Add ABIEncoderV2 storage array encoding bug to buglist --- docs/bugs.json | 11 +++++++++++ docs/bugs_by_version.json | 24 +++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/bugs.json b/docs/bugs.json index 3eb5d1807..19117fd08 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,15 @@ [ + { + "name": "ABIEncoderV2StorageArrayWithMultiSlotElement", + "summary": "Storage arrays containing structs or other arrays are not read properly when directly encoded in function calls or in abi.encode*.", + "description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable.", + "introduced": "0.4.16", + "fixed": "0.5.10", + "severity": "low", + "conditions": { + "ABIEncoderV2": true + } + }, { "name": "DynamicConstructorArgumentsClippedABIV2", "summary": "A contract's constructor that takes structs or arrays that contain dynamically-sized arrays reverts or decodes to invalid data.", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 16c949f65..b3731f0b9 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -452,6 +452,7 @@ }, "0.4.16": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -463,6 +464,7 @@ }, "0.4.17": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -475,6 +477,7 @@ }, "0.4.18": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -486,6 +489,7 @@ }, "0.4.19": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -514,6 +518,7 @@ }, "0.4.20": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -526,6 +531,7 @@ }, "0.4.21": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -538,6 +544,7 @@ }, "0.4.22": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -550,6 +557,7 @@ }, "0.4.23": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -561,6 +569,7 @@ }, "0.4.24": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -572,6 +581,7 @@ }, "0.4.25": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -581,6 +591,7 @@ }, "0.4.26": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2" ], "released": "2019-04-29" @@ -689,6 +700,7 @@ }, "0.5.0": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -698,6 +710,7 @@ }, "0.5.1": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -707,6 +720,7 @@ }, "0.5.2": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -716,6 +730,7 @@ }, "0.5.3": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -725,6 +740,7 @@ }, "0.5.4": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -734,6 +750,7 @@ }, "0.5.5": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -745,6 +762,7 @@ }, "0.5.6": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -755,6 +773,7 @@ }, "0.5.7": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries" @@ -763,12 +782,15 @@ }, "0.5.8": { "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2" ], "released": "2019-04-30" }, "0.5.9": { - "bugs": [], + "bugs": [ + "ABIEncoderV2StorageArrayWithMultiSlotElement" + ], "released": "2019-05-28" } } \ No newline at end of file From 5d6cbd97dfa4eedcdaf27109a8b5b1aa75df79d3 Mon Sep 17 00:00:00 2001 From: Vedant Agarwala Date: Sat, 25 May 2019 18:37:51 +0800 Subject: [PATCH 073/115] [Yul] Directly jump over a series of function definitions Implement a AbstractAssembly::setStackHeight function Update the tests Update Changelog --- Changelog.md | 1 + libyul/backends/evm/AbstractAssembly.h | 1 + libyul/backends/evm/AsmCodeGen.cpp | 5 +++ libyul/backends/evm/AsmCodeGen.h | 1 + libyul/backends/evm/EVMAssembly.h | 1 + libyul/backends/evm/EVMCodeTransform.cpp | 35 ++++++++++++++----- libyul/backends/evm/NoOutputAssembly.h | 1 + test/libyul/StackReuseCodegen.cpp | 11 +++--- .../libyul/objectCompiler/function_series.yul | 28 +++++++++++++++ 9 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 test/libyul/objectCompiler/function_series.yul diff --git a/Changelog.md b/Changelog.md index 994cff927..dde2a7709 100644 --- a/Changelog.md +++ b/Changelog.md @@ -85,6 +85,7 @@ Compiler Features: * Yul: Adds break and continue keywords to for-loop syntax. * Yul: Support ``.`` as part of identifiers. * Yul Optimizer: Adds steps for detecting and removing of dead code. + * Yul Code Generator: Directly jump over a series of function definitions (instead of jumping over each one) Bugfixes: diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index a531c0624..b87710522 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -62,6 +62,7 @@ public: /// Retrieve the current height of the stack. This does not have to be zero /// at the beginning. virtual int stackHeight() const = 0; + virtual void setStackHeight(int height) = 0; /// Append an EVM instruction. virtual void appendInstruction(dev::eth::Instruction _instruction) = 0; /// Append a constant. diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index e96481dba..999d215a3 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -57,6 +57,11 @@ int EthAssemblyAdapter::stackHeight() const return m_assembly.deposit(); } +void EthAssemblyAdapter::setStackHeight(int height) +{ + m_assembly.setDeposit(height); +} + void EthAssemblyAdapter::appendInstruction(dev::eth::Instruction _instruction) { m_assembly.append(_instruction); diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index 3d2e9e191..ffaea6221 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -44,6 +44,7 @@ public: explicit EthAssemblyAdapter(dev::eth::Assembly& _assembly); void setSourceLocation(langutil::SourceLocation const& _location) override; int stackHeight() const override; + void setStackHeight(int height) override; void appendInstruction(dev::eth::Instruction _instruction) override; void appendConstant(dev::u256 const& _constant) override; void appendLabel(LabelID _labelId) override; diff --git a/libyul/backends/evm/EVMAssembly.h b/libyul/backends/evm/EVMAssembly.h index a2318aea7..9b4242a2c 100644 --- a/libyul/backends/evm/EVMAssembly.h +++ b/libyul/backends/evm/EVMAssembly.h @@ -45,6 +45,7 @@ public: /// Retrieve the current height of the stack. This does not have to be zero /// at the beginning. int stackHeight() const override { return m_stackHeight; } + void setStackHeight(int height) override { m_stackHeight = height; } /// Append an EVM instruction. void appendInstruction(dev::eth::Instruction _instruction) override; /// Append a constant. diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index e8f5ec613..9ca079f07 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -490,19 +490,15 @@ void CodeTransform::operator()(FunctionDefinition const& _function) } m_assembly.setSourceLocation(_function.location); - int stackHeightBefore = m_assembly.stackHeight(); - AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId(); + int const stackHeightBefore = m_assembly.stackHeight(); if (m_evm15) - { - m_assembly.appendJumpTo(afterFunction, -stackHeightBefore); m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size()); - } else - { - m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height); m_assembly.appendLabel(functionEntryID(_function.name, function)); - } + + m_assembly.setStackHeight(height); + m_stackAdjustment += localStackAdjustment; for (auto const& v: _function.returnVariables) @@ -592,8 +588,8 @@ void CodeTransform::operator()(FunctionDefinition const& _function) else m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size()); m_stackAdjustment -= localStackAdjustment; - m_assembly.appendLabel(afterFunction); checkStackHeight(&_function); + m_assembly.setStackHeight(stackHeightBefore); } void CodeTransform::operator()(ForLoop const& _forLoop) @@ -727,11 +723,32 @@ void CodeTransform::visitExpression(Expression const& _expression) void CodeTransform::visitStatements(vector const& _statements) { + // Workaround boost bug: + // https://www.boost.org/doc/libs/1_63_0/libs/optional/doc/html/boost_optional/tutorial/gotchas/false_positive_with__wmaybe_uninitialized.html + boost::optional jumpTarget = boost::make_optional(false, AbstractAssembly::LabelID()); + for (auto const& statement: _statements) { freeUnusedVariables(); + auto const* functionDefinition = boost::get(&statement); + if (functionDefinition && !jumpTarget) + { + m_assembly.setSourceLocation(locationOf(statement)); + jumpTarget = m_assembly.newLabelId(); + m_assembly.appendJumpTo(*jumpTarget, 0); + } + else if (!functionDefinition && jumpTarget) + { + m_assembly.appendLabel(*jumpTarget); + jumpTarget = boost::none; + } + boost::apply_visitor(*this, statement); } + // we may have a leftover jumpTarget + if (jumpTarget) + m_assembly.appendLabel(*jumpTarget); + freeUnusedVariables(); } diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 7700599e7..d938a4e45 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -49,6 +49,7 @@ public: void setSourceLocation(langutil::SourceLocation const&) override {} int stackHeight() const override { return m_stackHeight; } + void setStackHeight(int height) override { m_stackHeight = height; } void appendInstruction(dev::eth::Instruction _instruction) override; void appendConstant(dev::u256 const& _constant) override; void appendLabel(LabelID _labelId) override; diff --git a/test/libyul/StackReuseCodegen.cpp b/test/libyul/StackReuseCodegen.cpp index d386c83f8..a1331f68c 100644 --- a/test/libyul/StackReuseCodegen.cpp +++ b/test/libyul/StackReuseCodegen.cpp @@ -277,16 +277,15 @@ BOOST_AUTO_TEST_CASE(functions_multi_return) let unused := 7 })"; BOOST_CHECK_EQUAL(assemble(in), - "PUSH1 0xB JUMP " + "PUSH1 0x13 JUMP " "JUMPDEST PUSH1 0x0 SWAP3 SWAP2 POP POP JUMP " // f - "JUMPDEST PUSH1 0x17 JUMP " "JUMPDEST PUSH1 0x0 PUSH1 0x0 SWAP1 SWAP2 JUMP " // g - "JUMPDEST PUSH1 0x21 PUSH1 0x2 PUSH1 0x1 PUSH1 0x3 JUMP " // f(1, 2) - "JUMPDEST PUSH1 0x2B PUSH1 0x4 PUSH1 0x3 PUSH1 0x3 JUMP " // f(3, 4) + "JUMPDEST PUSH1 0x1D PUSH1 0x2 PUSH1 0x1 PUSH1 0x3 JUMP " // f(1, 2) + "JUMPDEST PUSH1 0x27 PUSH1 0x4 PUSH1 0x3 PUSH1 0x3 JUMP " // f(3, 4) "JUMPDEST SWAP1 POP " // assignment to x "POP " // remove x - "PUSH1 0x34 PUSH1 0xF JUMP " // g() - "JUMPDEST PUSH1 0x3A PUSH1 0xF JUMP " // g() + "PUSH1 0x30 PUSH1 0xB JUMP " // g() + "JUMPDEST PUSH1 0x36 PUSH1 0xB JUMP " // g() "JUMPDEST SWAP2 POP SWAP2 POP " // assignments "POP POP " // removal of y and z "PUSH1 0x7 POP " diff --git a/test/libyul/objectCompiler/function_series.yul b/test/libyul/objectCompiler/function_series.yul new file mode 100644 index 000000000..75754b920 --- /dev/null +++ b/test/libyul/objectCompiler/function_series.yul @@ -0,0 +1,28 @@ +object "Contract" { + code { + function f() {} + function g() {} + sstore(0, 1) + } +} + +// ---- +// Assembly: +// /* "source":33:48 */ +// jump(tag_1) +// tag_2: +// /* "source":46:48 */ +// jump +// /* "source":53:68 */ +// tag_3: +// /* "source":66:68 */ +// jump +// tag_1: +// /* "source":83:84 */ +// 0x01 +// /* "source":80:81 */ +// 0x00 +// /* "source":73:85 */ +// sstore +// Bytecode: 6007565b565b565b6001600055 +// Opcodes: PUSH1 0x7 JUMP JUMPDEST JUMP JUMPDEST JUMP JUMPDEST PUSH1 0x1 PUSH1 0x0 SSTORE From cc5045a56e31670d9c58cb0569457f7d536b09f2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 29 May 2019 23:14:06 +0200 Subject: [PATCH 074/115] [Yul] Run optimizer on all dialects. --- libsolidity/codegen/CompilerContext.cpp | 3 ++- libyul/AssemblyStack.cpp | 25 ++++++++++----------- libyul/Dialect.h | 3 +++ libyul/backends/wasm/EWasmCodeTransform.cpp | 4 ++-- libyul/backends/wasm/WasmDialect.h | 2 ++ libyul/optimiser/Suite.cpp | 8 +++++-- libyul/optimiser/Suite.h | 2 +- test/libyul/YulOptimizerTest.cpp | 2 +- 8 files changed, 29 insertions(+), 20 deletions(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 851038421..8c65b8a19 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -422,9 +422,10 @@ void CompilerContext::appendInlineAssembly( if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) { bool const isCreation = m_runtimeContext != nullptr; + yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); yul::OptimiserSuite::run( dialect, - yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment), + &meter, *parserResult, analysisInfo, _optimiserSettings.optimizeStackAllocation, diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index 1723e1fc2..549a599fe 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -94,8 +94,6 @@ void AssemblyStack::optimize() if (!m_optimiserSettings.runYulOptimiser) return; - if (m_language != Language::StrictAssembly) - solUnimplemented("Optimizer for both loose assembly and Yul is not yet implemented"); solAssert(m_analysisSuccessful, "Analysis was not successful."); m_analysisSuccessful = false; @@ -147,17 +145,18 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation) for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast(subNode.get())) optimize(*subObject, false); - if (EVMDialect const* dialect = dynamic_cast(&languageToDialect(m_language, m_evmVersion))) - { - GasMeter meter(*dialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); - OptimiserSuite::run( - *dialect, - meter, - *_object.code, - *_object.analysisInfo, - m_optimiserSettings.optimizeStackAllocation - ); - } + + Dialect const& dialect = languageToDialect(m_language, m_evmVersion); + unique_ptr meter; + if (EVMDialect const* evmDialect = dynamic_cast(&dialect)) + meter = make_unique(*evmDialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); + OptimiserSuite::run( + dialect, + meter.get(), + *_object.code, + *_object.analysisInfo, + m_optimiserSettings.optimizeStackAllocation + ); } MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 6e2fb6585..9dd41ca34 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -25,6 +25,7 @@ #include #include +#include namespace yul { @@ -69,6 +70,8 @@ struct Dialect: boost::noncopyable virtual BuiltinFunction const* discardFunction() const { return nullptr; } virtual BuiltinFunction const* equalityFunction() const { return nullptr; } + virtual std::set fixedFunctionNames() const { return {}; } + Dialect(AsmFlavour _flavour): flavour(_flavour) {} virtual ~Dialect() = default; diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp index c25e5a1c9..90ca28d1c 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.cpp +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -115,9 +115,9 @@ wasm::Expression EWasmCodeTransform::operator()(Label const&) return {}; } -wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const&) +wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const& _f) { - yulAssert(false, ""); + yulAssert(false, "EVM instruction in ewasm code: " + eth::instructionInfo(_f.instruction).name); return {}; } diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 4ade0be54..71f7c52ad 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -48,6 +48,8 @@ struct WasmDialect: public Dialect BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); } BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); } + std::set fixedFunctionNames() const override { return {"main"_yulstring}; } + static WasmDialect const& instance(); private: diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 05b4b1b40..b8bb1ccf8 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -60,7 +60,7 @@ using namespace yul; void OptimiserSuite::run( Dialect const& _dialect, - GasMeter const& _meter, + GasMeter const* _meter, Block& _ast, AsmAnalysisInfo const& _analysisInfo, bool _optimizeStackAllocation, @@ -68,6 +68,7 @@ void OptimiserSuite::run( ) { set reservedIdentifiers = _externallyUsedIdentifiers; + reservedIdentifiers += _dialect.fixedFunctionNames(); Block ast = boost::get(Disambiguator(_dialect, _analysisInfo, reservedIdentifiers)(_ast)); @@ -210,7 +211,10 @@ void OptimiserSuite::run( FunctionGrouper{}(ast); if (EVMDialect const* dialect = dynamic_cast(&_dialect)) - ConstantOptimiser{*dialect, _meter}(ast); + { + yulAssert(_meter, ""); + ConstantOptimiser{*dialect, *_meter}(ast); + } VarNameCleaner{ast, _dialect, reservedIdentifiers}(ast); yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, ast); diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 03806eee6..3f35b572a 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -41,7 +41,7 @@ class OptimiserSuite public: static void run( Dialect const& _dialect, - GasMeter const& _meter, + GasMeter const* _meter, Block& _ast, AsmAnalysisInfo const& _analysisInfo, bool _optimizeStackAllocation, diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 496f0837c..ece57267b 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -292,7 +292,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line else if (m_optimizerStep == "fullSuite") { GasMeter meter(dynamic_cast(*m_dialect), false, 200); - OptimiserSuite::run(*m_dialect, meter, *m_ast, *m_analysisInfo, true); + OptimiserSuite::run(*m_dialect, &meter, *m_ast, *m_analysisInfo, true); } else { From 622adf55c20aec6e338579dbe9c1410738bd9a2f Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Wed, 19 Jun 2019 11:03:10 +0200 Subject: [PATCH 075/115] Fix missplaced warnings --- docs/installing-solidity.rst | 8 ++++---- docs/units-and-global-variables.rst | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 8a55e9aea..b19532d39 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -305,10 +305,6 @@ You might want to install ccache to speed up repeated builds. CMake will pick it up automatically. Building Solidity is quite similar on Linux, macOS and other Unices: -.. warning:: - - BSD builds should work, but are untested by the Solidity team. - .. code-block:: bash mkdir build @@ -322,6 +318,10 @@ or even easier on Linux and macOS, you can run: #note: this will install binaries solc and soltest at usr/local/bin ./scripts/build.sh +.. warning:: + + BSD builds should work, but are untested by the Solidity team. + And for Windows: .. code-block:: bash diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 4f5a3ee83..8b8c06735 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -165,16 +165,6 @@ Mathematical and Cryptographic Functions ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input -.. warning:: - - If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without - requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed - for _transaction_ signatures (see `EIP-2 `_), but - the ecrecover function remained unchanged. - - This is usually not a problem unless you require signatures to be unique or - use them to identify items. OpenZeppelin have a `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. - .. note:: There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0. @@ -198,6 +188,16 @@ Mathematical and Cryptographic Functions For further details, read `example usage `_. +.. warning:: + + If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without + requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed + for _transaction_ signatures (see `EIP-2 `_), but + the ecrecover function remained unchanged. + + This is usually not a problem unless you require signatures to be unique or + use them to identify items. OpenZeppelin have a `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. + .. note:: When running ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*, you might encounter Out-of-Gas. This is because these functions are implemented as "precompiled contracts" and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net. From 492f9459731521961874a321d4a16ae86ab96934 Mon Sep 17 00:00:00 2001 From: mingchuan Date: Mon, 3 Jun 2019 18:01:11 +0800 Subject: [PATCH 076/115] Coding style fix --- libsolidity/codegen/ABIFunctions.cpp | 3 ++- libsolidity/codegen/ArrayUtils.cpp | 25 ++++++++++++------------ libsolidity/codegen/CompilerUtils.cpp | 17 ++++++++++------ libsolidity/codegen/ContractCompiler.cpp | 14 +++++++++---- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index d0c197b1f..1583a8d63 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -414,7 +414,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( fromArrayType.isByteArray() || *fromArrayType.baseType() == *TypeProvider::uint256() || *fromArrayType.baseType() == FixedBytesType(32), - ""); + "" + ); solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), ""); solAssert( diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 08d30f537..6a07ae102 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -1055,28 +1055,27 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b switch (location) { case DataLocation::Memory: + // stack: + if (!_arrayType.isByteArray()) + m_context << u256(_arrayType.memoryHeadSize()) << Instruction::MUL; + if (_arrayType.isDynamicallySized()) + m_context << u256(32) << Instruction::ADD; + if (_keepReference) + m_context << Instruction::DUP2; + m_context << Instruction::ADD; + break; case DataLocation::CallData: if (!_arrayType.isByteArray()) { - if (location == DataLocation::CallData) - { - if (_arrayType.baseType()->isDynamicallyEncoded()) - m_context << u256(0x20); - else - m_context << _arrayType.baseType()->calldataEncodedSize(); - } + if (_arrayType.baseType()->isDynamicallyEncoded()) + m_context << u256(0x20); else - m_context << u256(_arrayType.memoryHeadSize()); + m_context << _arrayType.baseType()->calldataEncodedSize(); m_context << Instruction::MUL; } // stack: - - if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) - m_context << u256(32) << Instruction::ADD; - if (_keepReference) m_context << Instruction::DUP2; - m_context << Instruction::ADD; break; case DataLocation::Storage: diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index d6be20328..77534ee34 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -816,7 +816,12 @@ void CompilerUtils::convertType( } else { - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract || targetTypeCategory == Type::Category::Address, ""); + solAssert( + targetTypeCategory == Type::Category::Integer || + targetTypeCategory == Type::Category::Contract || + targetTypeCategory == Type::Category::Address, + "" + ); IntegerType addressType(160); IntegerType const& targetType = targetTypeCategory == Type::Category::Integer ? dynamic_cast(_targetType) : addressType; @@ -923,7 +928,6 @@ void CompilerUtils::convertType( // stack: (variably sized) if (targetType.baseType()->isValueType()) { - solAssert(typeOnStack.baseType()->isValueType(), ""); copyToStackTop(2 + stackSize, stackSize); ArrayUtils(m_context).copyArrayToMemory(typeOnStack); } @@ -957,10 +961,11 @@ void CompilerUtils::convertType( } case DataLocation::CallData: solAssert( - targetType.isByteArray() && - typeOnStack.isByteArray() && - typeOnStack.location() == DataLocation::CallData, - "Invalid conversion to calldata type."); + targetType.isByteArray() && + typeOnStack.isByteArray() && + typeOnStack.location() == DataLocation::CallData, + "Invalid conversion to calldata type." + ); break; } break; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index d50e4f4d5..6e2e74e45 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -54,7 +54,13 @@ class StackHeightChecker public: explicit StackHeightChecker(CompilerContext const& _context): m_context(_context), stackHeight(m_context.stackHeight()) {} - void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight)); } + void check() + { + solAssert( + m_context.stackHeight() == stackHeight, + std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight) + ); + } private: CompilerContext const& m_context; unsigned stackHeight; @@ -893,9 +899,9 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar // Local variable slots are reserved when their declaration is visited, // and freed in the end of their scope. - for (auto _decl: _variableDeclarationStatement.declarations()) - if (_decl) - appendStackVariableInitialisation(*_decl); + for (auto decl: _variableDeclarationStatement.declarations()) + if (decl) + appendStackVariableInitialisation(*decl); StackHeightChecker checker(m_context); if (Expression const* expression = _variableDeclarationStatement.initialValue()) From ae273ff3bda86df699ab00185e2c409bad128d4a Mon Sep 17 00:00:00 2001 From: mingchuan Date: Wed, 19 Jun 2019 17:22:11 +0800 Subject: [PATCH 077/115] Move code that access calldata array element to separate function --- libsolidity/codegen/ArrayUtils.cpp | 44 ++++++++++++++++++++++ libsolidity/codegen/ArrayUtils.h | 4 ++ libsolidity/codegen/ExpressionCompiler.cpp | 40 +------------------- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 6a07ae102..7c01a8c52 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -1131,6 +1131,50 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b } } +void ArrayUtils::accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck) const +{ + solAssert(_arrayType.location() == DataLocation::CallData, ""); + if (_arrayType.baseType()->isDynamicallyEncoded()) + { + // stack layout: + ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck, true); + // stack layout: + + CompilerUtils(m_context).accessCalldataTail(*_arrayType.baseType()); + // stack layout: [length] + } + else + { + ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck); + if (_arrayType.baseType()->isValueType()) + { + solAssert(_arrayType.baseType()->storageBytes() <= 32, ""); + if ( + !_arrayType.isByteArray() && + _arrayType.baseType()->storageBytes() < 32 && + m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) + ) + { + m_context << u256(32); + CompilerUtils(m_context).abiDecodeV2({_arrayType.baseType()}, false); + } + else + CompilerUtils(m_context).loadFromMemoryDynamic( + *_arrayType.baseType(), + true, + !_arrayType.isByteArray(), + false + ); + } + else + solAssert( + _arrayType.baseType()->category() == Type::Category::Struct || + _arrayType.baseType()->category() == Type::Category::Array, + "Invalid statically sized non-value base type on array access." + ); + } +} + void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const { solAssert(_byteSize < 32, ""); diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index 4c382a5ca..9cb5338ae 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -104,6 +104,10 @@ public: /// Stack post (storage): [reference] storage_slot byte_offset /// Stack post: [reference] memory/calldata_offset void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const; + /// Access calldata array's element and put it on stack. + /// Stack pre: reference [length] index + /// Stack post: value + void accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; private: /// Adds the given number of bytes to a storage byte offset counter and also increments diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index bde4a167b..f393c6a6a 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1588,45 +1588,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); break; case DataLocation::CallData: - if (arrayType.baseType()->isDynamicallyEncoded()) - { - // stack layout: - ArrayUtils(m_context).accessIndex(arrayType, true, true); - // stack layout: - - CompilerUtils(m_context).accessCalldataTail(*arrayType.baseType()); - // stack layout: [length] - } - else - { - ArrayUtils(m_context).accessIndex(arrayType, true); - if (arrayType.baseType()->isValueType()) - { - solAssert(arrayType.baseType()->storageBytes() <= 32, ""); - if ( - !arrayType.isByteArray() && - arrayType.baseType()->storageBytes() < 32 && - m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) - ) - { - m_context << u256(32); - CompilerUtils(m_context).abiDecodeV2({arrayType.baseType()}, false); - } - else - CompilerUtils(m_context).loadFromMemoryDynamic( - *arrayType.baseType(), - true, - !arrayType.isByteArray(), - false - ); - } - else - solAssert( - arrayType.baseType()->category() == Type::Category::Struct || - arrayType.baseType()->category() == Type::Category::Array, - "Invalid statically sized non-value base type on array access." - ); - } + ArrayUtils(m_context).accessCallDataArrayElement(arrayType); break; } } From d7b366ff46e88df9c615b9c00e2f5ae1a74a6c32 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Jun 2019 11:37:22 +0200 Subject: [PATCH 078/115] Name displacer. --- libdevcore/CommonData.h | 16 ++++ libyul/CMakeLists.txt | 2 + libyul/optimiser/ASTWalker.cpp | 2 + libyul/optimiser/NameDispenser.cpp | 4 +- libyul/optimiser/NameDispenser.h | 6 +- libyul/optimiser/NameDisplacer.cpp | 86 +++++++++++++++++++ libyul/optimiser/NameDisplacer.h | 70 +++++++++++++++ libyul/optimiser/Suite.cpp | 2 +- test/libyul/YulOptimizerTest.cpp | 10 +++ .../nameDisplacer/funtion_call.yul | 25 ++++++ .../nameDisplacer/variables.yul | 11 +++ .../variables_inside_functions.yul | 16 ++++ 12 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 libyul/optimiser/NameDisplacer.cpp create mode 100644 libyul/optimiser/NameDisplacer.h create mode 100644 test/libyul/yulOptimizerTests/nameDisplacer/funtion_call.yul create mode 100644 test/libyul/yulOptimizerTests/nameDisplacer/variables.yul create mode 100644 test/libyul/yulOptimizerTests/nameDisplacer/variables_inside_functions.yul diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 0a13e7940..2fca07171 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -81,6 +81,22 @@ inline std::vector operator+(std::vector&& _a, std::vector&& _b) ret += std::move(_b); return ret; } +/// Concatenate something to a sets of elements. +template +inline std::set operator+(std::set const& _a, U&& _b) +{ + std::set ret(_a); + ret += std::forward(_b); + return ret; +} +/// Concatenate something to a sets of elements, move variant. +template +inline std::set operator+(std::set&& _a, U&& _b) +{ + std::set ret(std::move(_a)); + ret += std::forward(_b); + return ret; +} namespace dev { diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index bf09207b6..f85f4e7c4 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -102,6 +102,8 @@ add_library(yul optimiser/NameCollector.h optimiser/NameDispenser.cpp optimiser/NameDispenser.h + optimiser/NameDisplacer.cpp + optimiser/NameDisplacer.h optimiser/OptimizerUtilities.cpp optimiser/OptimizerUtilities.h optimiser/RedundantAssignEliminator.cpp diff --git a/libyul/optimiser/ASTWalker.cpp b/libyul/optimiser/ASTWalker.cpp index 85234c4c7..523cc8a92 100644 --- a/libyul/optimiser/ASTWalker.cpp +++ b/libyul/optimiser/ASTWalker.cpp @@ -35,6 +35,7 @@ void ASTWalker::operator()(FunctionalInstruction const& _instr) void ASTWalker::operator()(FunctionCall const& _funCall) { + // Does not visit _funCall.functionName on purpose walkVector(_funCall.arguments | boost::adaptors::reversed); } @@ -108,6 +109,7 @@ void ASTModifier::operator()(FunctionalInstruction& _instr) void ASTModifier::operator()(FunctionCall& _funCall) { + // Does not visit _funCall.functionName on purpose walkVector(_funCall.arguments | boost::adaptors::reversed); } diff --git a/libyul/optimiser/NameDispenser.cpp b/libyul/optimiser/NameDispenser.cpp index 2a0c1e44f..f5bc1314a 100644 --- a/libyul/optimiser/NameDispenser.cpp +++ b/libyul/optimiser/NameDispenser.cpp @@ -32,8 +32,8 @@ using namespace std; using namespace dev; using namespace yul; -NameDispenser::NameDispenser(Dialect const& _dialect, Block const& _ast): - NameDispenser(_dialect, NameCollector(_ast).names()) +NameDispenser::NameDispenser(Dialect const& _dialect, Block const& _ast, set _reservedNames): + NameDispenser(_dialect, NameCollector(_ast).names() + std::move(_reservedNames)) { } diff --git a/libyul/optimiser/NameDispenser.h b/libyul/optimiser/NameDispenser.h index 9591dd135..db871f905 100644 --- a/libyul/optimiser/NameDispenser.h +++ b/libyul/optimiser/NameDispenser.h @@ -39,13 +39,17 @@ class NameDispenser { public: /// Initialize the name dispenser with all the names used in the given AST. - explicit NameDispenser(Dialect const& _dialect, Block const& _ast); + explicit NameDispenser(Dialect const& _dialect, Block const& _ast, std::set _reservedNames = {}); /// Initialize the name dispenser with the given used names. explicit NameDispenser(Dialect const& _dialect, std::set _usedNames); /// @returns a currently unused name that should be similar to _nameHint. YulString newName(YulString _nameHint); + /// Mark @a _name as used, i.e. the dispenser's newName function will not + /// return it. + void markUsed(YulString _name) { m_usedNames.insert(_name); } + private: bool illegalName(YulString _name); diff --git a/libyul/optimiser/NameDisplacer.cpp b/libyul/optimiser/NameDisplacer.cpp new file mode 100644 index 000000000..b143e3d98 --- /dev/null +++ b/libyul/optimiser/NameDisplacer.cpp @@ -0,0 +1,86 @@ +/* + 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 . +*/ +/** + * Optimiser component that renames identifiers to free up certain names. + */ + +#include + +#include + + +using namespace std; +using namespace dev; +using namespace yul; + +void NameDisplacer::operator()(Identifier& _identifier) +{ + checkAndReplace(_identifier.name); +} + +void NameDisplacer::operator()(VariableDeclaration& _varDecl) +{ + for (TypedName& var: _varDecl.variables) + checkAndReplaceNew(var.name); + + ASTModifier::operator()(_varDecl); +} + +void NameDisplacer::operator()(FunctionDefinition& _function) +{ + // Should have been done in the block already. + yulAssert(!m_namesToFree.count(_function.name), ""); + + for (auto& param: _function.parameters) + checkAndReplaceNew(param.name); + for (auto& retVar: _function.returnVariables) + checkAndReplaceNew(retVar.name); + + ASTModifier::operator()(_function); +} + +void NameDisplacer::operator()(FunctionCall& _funCall) +{ + checkAndReplace(_funCall.functionName.name); + ASTModifier::operator()(_funCall); +} + +void NameDisplacer::operator()(Block& _block) +{ + // First replace all the names of function definitions + // because of scoping. + for (auto& st: _block.statements) + if (st.type() == typeid(FunctionDefinition)) + checkAndReplaceNew(boost::get(st).name); + + ASTModifier::operator()(_block); +} + +void NameDisplacer::checkAndReplaceNew(YulString& _name) +{ + yulAssert(!m_translations.count(_name), ""); + if (m_namesToFree.count(_name)) + _name = (m_translations[_name] = m_nameDispenser.newName(_name)); +} + +void NameDisplacer::checkAndReplace(YulString& _name) const +{ + if (m_translations.count(_name)) + _name = m_translations.at(_name); + yulAssert(!m_namesToFree.count(_name), ""); +} + diff --git a/libyul/optimiser/NameDisplacer.h b/libyul/optimiser/NameDisplacer.h new file mode 100644 index 000000000..ed025d9f6 --- /dev/null +++ b/libyul/optimiser/NameDisplacer.h @@ -0,0 +1,70 @@ +/* + 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 . +*/ +/** + * Optimiser component that renames identifiers to free up certain names. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace yul +{ +struct Dialect; + +/** + * Optimiser component that renames identifiers to free up certain names. + * + * Prerequisites: Disambiguator + */ +class NameDisplacer: public ASTModifier +{ +public: + explicit NameDisplacer( + NameDispenser& _dispenser, + std::set const& _namesToFree + ): + m_nameDispenser(_dispenser), + m_namesToFree(_namesToFree) + { + for (YulString n: _namesToFree) + m_nameDispenser.markUsed(n); + } + + using ASTModifier::operator(); + void operator()(Identifier& _identifier) override; + void operator()(VariableDeclaration& _varDecl) override; + void operator()(FunctionDefinition& _function) override; + void operator()(FunctionCall& _funCall) override; + void operator()(Block& _block) override; + +protected: + /// Check if the newly introduced identifier @a _name has to be replaced. + void checkAndReplaceNew(YulString& _name); + /// Replace the identifier @a _name if it is in the translation map. + void checkAndReplace(YulString& _name) const; + + NameDispenser& m_nameDispenser; + std::set const& m_namesToFree; + std::map m_translations; +}; + +} diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 05b4b1b40..fd5c1499a 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -87,7 +87,7 @@ void OptimiserSuite::run( // None of the above can make stack problems worse. - NameDispenser dispenser{_dialect, ast}; + NameDispenser dispenser{_dialect, ast, reservedIdentifiers}; size_t codeSize = 0; for (size_t rounds = 0; rounds < 12; ++rounds) diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 496f0837c..66979cb66 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,15 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line soltestAssert(m_dialect, "Dialect not set."); if (m_optimizerStep == "disambiguator") disambiguate(); + else if (m_optimizerStep == "nameDisplacer") + { + disambiguate(); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + NameDisplacer{ + nameDispenser, + {"illegal1"_yulstring, "illegal2"_yulstring, "illegal3"_yulstring, "illegal4"_yulstring, "illegal5"_yulstring} + }(*m_ast); + } else if (m_optimizerStep == "blockFlattener") { disambiguate(); diff --git a/test/libyul/yulOptimizerTests/nameDisplacer/funtion_call.yul b/test/libyul/yulOptimizerTests/nameDisplacer/funtion_call.yul new file mode 100644 index 000000000..9c547d17a --- /dev/null +++ b/test/libyul/yulOptimizerTests/nameDisplacer/funtion_call.yul @@ -0,0 +1,25 @@ +{ + let x := illegal4(1, 2) + function illegal4(illegal1, illegal2) -> illegal3 { illegal3 := add(illegal1, illegal2) } + { + let y := illegal5(3, 4) + function illegal5(illegal1, illegal2) -> illegal3 { illegal3 := add(illegal1, illegal2) } + } +} +// ==== +// step: nameDisplacer +// ---- +// { +// let x := illegal4_1(1, 2) +// function illegal4_1(illegal1_2, illegal2_3) -> illegal3_4 +// { +// illegal3_4 := add(illegal1_2, illegal2_3) +// } +// { +// let y := illegal5_5(3, 4) +// function illegal5_5(illegal1_1, illegal2_2) -> illegal3_3 +// { +// illegal3_3 := add(illegal1_1, illegal2_2) +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/nameDisplacer/variables.yul b/test/libyul/yulOptimizerTests/nameDisplacer/variables.yul new file mode 100644 index 000000000..dae343217 --- /dev/null +++ b/test/libyul/yulOptimizerTests/nameDisplacer/variables.yul @@ -0,0 +1,11 @@ +{ { let illegal1 := 1 } { let illegal2 := 2 let illegal3, illegal4 } } +// ==== +// step: nameDisplacer +// ---- +// { +// { let illegal1_1 := 1 } +// { +// let illegal2_2 := 2 +// let illegal3_3, illegal4_4 +// } +// } diff --git a/test/libyul/yulOptimizerTests/nameDisplacer/variables_inside_functions.yul b/test/libyul/yulOptimizerTests/nameDisplacer/variables_inside_functions.yul new file mode 100644 index 000000000..73228739a --- /dev/null +++ b/test/libyul/yulOptimizerTests/nameDisplacer/variables_inside_functions.yul @@ -0,0 +1,16 @@ +{ + function f(illegal1, illegal2) -> illegal3 { + let illegal4 := illegal1 + illegal3 := add(illegal1, illegal2) + } +} +// ==== +// step: nameDisplacer +// ---- +// { +// function f(illegal1_1, illegal2_2) -> illegal3_3 +// { +// let illegal4_4 := illegal1_1 +// illegal3_3 := add(illegal1_1, illegal2_2) +// } +// } From b72977f0868ef374fb708c16434ccd9e646e6e47 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Mon, 17 Jun 2019 12:01:12 +0100 Subject: [PATCH 079/115] Review conventions section in assembly doc Changes from review Changes from review --- docs/assembly.rst | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 2f5734f33..5015a18bb 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -596,21 +596,21 @@ of their block is reached. Conventions in Solidity ----------------------- -In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits, -e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just -treat them as 256-bit numbers and the higher-order bits are only cleaned at the -point where it is necessary, i.e. just shortly before they are written to memory -or before comparisons are performed. This means that if you access such a variable -from within inline assembly, you might have to manually clean the higher order bits +In contrast to EVM assembly, Solidity has types which are narrower than 256 bits, +e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that types can be shorter than 256 +bits, and the higher-order bits are cleaned when necessary, +i.e., shortly before they are written to memory or before comparisons are performed. +This means that if you access such a variable +from within inline assembly, you might have to manually clean the higher-order bits first. -Solidity manages memory in a very simple way: There is a "free memory pointer" -at position ``0x40`` in memory. If you want to allocate memory, just use the memory -starting from where this pointer points at and update it accordingly. +Solidity manages memory in the following way. There is a "free memory pointer" +at position ``0x40`` in memory. If you want to allocate memory, use the memory +starting from where this pointer points at and update it. There is no guarantee that the memory has not been used before and thus you cannot assume that its contents are zero bytes. There is no built-in mechanism to release or free allocated memory. -Here is an assembly snippet that can be used for allocating memory:: +Here is an assembly snippet you can use for allocating memory that follows the process outlined above:: function allocate(length) -> pos { pos := mload(0x40) @@ -618,13 +618,13 @@ Here is an assembly snippet that can be used for allocating memory:: } The first 64 bytes of memory can be used as "scratch space" for short-term -allocation. The 32 bytes after the free memory pointer (i.e. starting at ``0x60``) -is meant to be zero permanently and is used as the initial value for +allocation. The 32 bytes after the free memory pointer (i.e., starting at ``0x60``) +are meant to be zero permanently and is used as the initial value for empty dynamic memory arrays. This means that the allocatable memory starts at ``0x80``, which is the initial value of the free memory pointer. -Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is +Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this is even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory arrays are pointers to memory arrays. The length of a dynamic array is stored at the first slot of the array and followed by the array elements. @@ -632,7 +632,7 @@ first slot of the array and followed by the array elements. .. warning:: Statically-sized memory arrays do not have a length field, but it might be added later to allow better convertibility between statically- and dynamically-sized arrays, so - please do not rely on that. + do not rely on this. Standalone Assembly From c5b50039d28e77146ba4115e246594ba38267aa7 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 6 Jun 2019 14:00:30 +0200 Subject: [PATCH 080/115] [Sol->Yul] Report error after Ir code this way less scrolling is required --- libsolidity/codegen/ir/IRGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index acf74805a..17b44fcd8 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -54,7 +54,7 @@ pair IRGenerator::run(ContractDefinition const& _contract) string errorMessage; for (auto const& error: asmStack.errors()) errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error); - solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir); + solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n"); } asmStack.optimize(); From 910cb8d32986c751932993a0dddd80687a3b741e Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 6 Jun 2019 14:07:40 +0200 Subject: [PATCH 081/115] [Sol->Yul] Implement .length for storage arrays --- libsolidity/codegen/YulUtilFunctions.cpp | 114 ++++++++++++++++++ libsolidity/codegen/YulUtilFunctions.h | 16 +++ .../codegen/ir/IRGeneratorForStatements.cpp | 30 ++++- libsolidity/codegen/ir/IRLValue.cpp | 28 +++++ libsolidity/codegen/ir/IRLValue.h | 19 +++ .../viaYul/array_storage_length_access.sol | 19 +++ 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 7724dd57e..4c8b20636 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -517,6 +517,120 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) }); } +std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); + solUnimplementedAssert(_type.baseType()->isValueType(), "..."); + solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "..."); + solUnimplementedAssert(_type.baseType()->storageSize() == 1, ""); + + string functionName = "resize_array_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array, newLen) { + if gt(newLen, ) { + invalid() + } + + let oldLen := (array) + + // Store new length + sstore(array, newLen) + + // Size was reduced, clear end of array + if lt(newLen, oldLen) { + let oldSlotCount := (oldLen) + let newSlotCount := (newLen) + let arrayDataStart := (array) + let deleteStart := add(arrayDataStart, newSlotCount) + let deleteEnd := add(arrayDataStart, oldSlotCount) + (deleteStart, deleteEnd) + } + })") + ("functionName", functionName) + ("fetchLength", arrayLengthFunction(_type)) + ("convertToSize", arrayConvertLengthToSize(_type)) + ("dataPosition", arrayDataAreaFunction(_type)) + ("clearStorageRange", clearStorageRangeFunction(*_type.baseType())) + ("maxArrayLength", (u256(1) << 64).str()) + .render(); + }); +} + +string YulUtilFunctions::clearStorageRangeFunction(Type const& _type) +{ + string functionName = "clear_storage_range_" + _type.identifier(); + + solUnimplementedAssert(_type.isValueType(), "..."); + + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (start, end) { + for {} lt(start, end) { start := add(start, 1) } + { + sstore(start, 0) + } + } + )") + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) +{ + string functionName = "array_convert_length_to_size_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Type const& baseType = *_type.baseType(); + + switch (_type.location()) + { + case DataLocation::Storage: + { + unsigned const baseStorageBytes = baseType.storageBytes(); + solAssert(baseStorageBytes > 0, ""); + solAssert(32 / baseStorageBytes > 0, ""); + + return Whiskers(R"( + function (length) -> size { + size := length + + size := (, length) + + // Number of slots rounded up + size := div(add(length, sub(, 1)), ) + + })") + ("functionName", functionName) + ("multiSlot", baseType.storageSize() > 1) + ("itemsPerSlot", to_string(32 / baseStorageBytes)) + ("storageSize", baseType.storageSize().str()) + ("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits())) + .render(); + } + case DataLocation::CallData: // fallthrough + case DataLocation::Memory: + return Whiskers(R"( + function (length) -> size { + + size := length + + size := (length, ) + + })") + ("functionName", functionName) + ("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize()) + ("byteArray", _type.isByteArray()) + ("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits())) + .render(); + default: + solAssert(false, ""); + } + + }); +} string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) { solAssert(_type.dataStoredIn(DataLocation::Memory), ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 9b512f767..91b47a180 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -104,6 +104,22 @@ public: std::string overflowCheckedUIntSubFunction(); std::string arrayLengthFunction(ArrayType const& _type); + + /// @returns the name of a function that resizes a storage array + /// signature: (array, newLen) + std::string resizeDynamicArrayFunction(ArrayType const& _type); + + /// @returns the name of a function that will clear the storage area given + /// by the start and end (exclusive) parameters (slots). Only works for value types. + /// signature: (start, end) + std::string clearStorageRangeFunction(Type const& _type); + + /// Returns the name of a function that will convert a given length to the + /// size in memory (number of storage slots or calldata/memory bytes) it + /// will require. + /// signature: (length) -> size + std::string arrayConvertLengthToSize(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required /// to store an array in memory given its length (internally encoded, not ABI encoded). /// The function reverts for too large lengths. diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index e9adf36a2..80a09ccbd 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -703,7 +703,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) } case Type::Category::Array: { - solUnimplementedAssert(false, ""); + auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + + solAssert(member == "length", ""); + + if (!type.isDynamicallySized()) + defineExpression(_memberAccess) << type.length() << "\n"; + else + switch (type.location()) + { + case DataLocation::CallData: + solUnimplementedAssert(false, ""); + //m_context << Instruction::SWAP1 << Instruction::POP; + break; + case DataLocation::Storage: + setLValue(_memberAccess, make_unique( + m_context, + m_context.variable(_memberAccess.expression()), + *_memberAccess.annotation().type, + type + )); + + break; + case DataLocation::Memory: + solUnimplementedAssert(false, ""); + //m_context << Instruction::MLOAD; + break; + } + + break; } case Type::Category::FixedBytes: { diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index defabd081..010eecb75 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -137,3 +137,31 @@ string IRStorageItem::setToZero() const { solUnimplemented("Delete for storage location not yet implemented"); } + +IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType): + IRLValue(_context, &_type), m_arrayType(_arrayType), m_slot(move(_slot)) +{ + solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!"); +} + +string IRStorageArrayLength::retrieveValue() const +{ + return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")\n"; +} + +string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const +{ + solAssert(_type == *m_type, "Different type, but might not be an error."); + + return m_context.utils().resizeDynamicArrayFunction(m_arrayType) + + "(" + + m_slot + + ", " + + _value + + ")\n"; +} + +string IRStorageArrayLength::setToZero() const +{ + return storeValue("0", *TypeProvider::uint256()); +} diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index 3c9338ebd..f8ac5b6be 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -34,6 +34,7 @@ namespace solidity class VariableDeclaration; class IRGenerationContext; class Type; +class ArrayType; /** * Abstract class used to retrieve, delete and store data in LValues. @@ -107,6 +108,24 @@ private: boost::variant const m_offset; }; +/** + * Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special + * semantics since assignments to it might reduce its length and thus the array's members have to be + * deleted. + */ +class IRStorageArrayLength: public IRLValue +{ +public: + IRStorageArrayLength(IRGenerationContext& _context, std::string _slot, Type const& _type, ArrayType const& _arrayType); + + std::string retrieveValue() const override; + std::string storeValue(std::string const& _value, Type const& _type) const override; + std::string setToZero() const override; + +private: + ArrayType const& m_arrayType; + std::string const m_slot; +}; } } diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol b/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol new file mode 100644 index 000000000..b972d8ade --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol @@ -0,0 +1,19 @@ +contract C { + uint[] storageArray; + function set_get_length(uint256 len) public returns (uint256) + { + storageArray.length = len; + return storageArray.length; + } + +} +// ==== +// compileViaYul: true +// ---- +// set_get_length(uint256): 0 -> 0 +// set_get_length(uint256): 1 -> 1 +// set_get_length(uint256): 10 -> 10 +// set_get_length(uint256): 20 -> 20 +// set_get_length(uint256): 0 -> 0 +// set_get_length(uint256): 0xFF -> 0xFF +// set_get_length(uint256): 0xFFFF -> 0xFFFF From b03cb2bce663490ac4b53ce8330cca2287c9a160 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 13 Jun 2019 17:31:38 +0200 Subject: [PATCH 082/115] YulUtilFuncs: Add missing/adjust existing documentation --- libsolidity/codegen/YulUtilFunctions.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 91b47a180..ec828ce4f 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -78,9 +78,10 @@ public: std::string shiftRightFunction(size_t _numBits); std::string shiftRightFunctionDynamic(); - /// @returns the name of a function f(value, toInsert) -> newValue which replaces the + /// @returns the name of a function which replaces the /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// byte) by the _numBytes least significant bytes of `toInsert`. + /// signature: (value, toInsert) -> result std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); /// signature: (value, shiftBytes, toInsert) -> result @@ -88,10 +89,13 @@ public: /// @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. + /// signature: (value) -> result std::string roundUpFunction(); + /// signature: (x, y) -> sum std::string overflowCheckedUIntAddFunction(size_t _bits); + /// signature: (x, y) -> product std::string overflowCheckedUIntMulFunction(size_t _bits); /// @returns name of function to perform division on integers. @@ -101,8 +105,12 @@ public: /// @returns computes the difference between two values. /// Assumes the input to be in range for the type. + /// signature: (x, y) -> diff std::string overflowCheckedUIntSubFunction(); + /// @returns the name of a function that fetches the length of the given + /// array + /// signature: (array) -> length std::string arrayLengthFunction(ArrayType const& _type); /// @returns the name of a function that resizes a storage array From b704abdd75c3b6a031626d23e2bd22b8cdc8b2b5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Jun 2019 18:17:50 +0200 Subject: [PATCH 083/115] Some eWasm boilerplate code. --- libyul/backends/wasm/EWasmToText.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libyul/backends/wasm/EWasmToText.cpp b/libyul/backends/wasm/EWasmToText.cpp index a0d552d11..4e2bf5c2b 100644 --- a/libyul/backends/wasm/EWasmToText.cpp +++ b/libyul/backends/wasm/EWasmToText.cpp @@ -34,9 +34,18 @@ string EWasmToText::run( vector const& _functions ) { - string ret = "(module\n\n"; + string ret = "(module\n"; + // TODO Add all the interface functions: + // ret += " (import \"ethereum\" \"getBalance\" (func $getBalance (param i32 i32)))\n"; + + // allocate one 64k page of memory and make it available to the Ethereum client + ret += " (memory $memory (export \"memory\") 1)\n"; + // export the main function + ret += " (export \"main\" (func $main))\n"; + for (auto const& g: _globals) ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n"; + ret += "\n"; for (auto const& f: _functions) ret += transform(f) + "\n"; return move(ret) + ")\n"; From fc6f47e453dc7cbd655d7e33ce0368d65eb018d4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Jun 2019 18:46:16 +0200 Subject: [PATCH 084/115] Restrict whiskers parameters to regular characters. --- libdevcore/Whiskers.cpp | 20 ++++++++++++++++++-- libdevcore/Whiskers.h | 5 ++++- test/libdevcore/Whiskers.cpp | 18 ++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp index 8a20f2cd7..c48c8d6f1 100644 --- a/libdevcore/Whiskers.cpp +++ b/libdevcore/Whiskers.cpp @@ -37,6 +37,7 @@ Whiskers::Whiskers(string _template): Whiskers& Whiskers::operator()(string _parameter, string _value) { + checkParameterValid(_parameter); checkParameterUnknown(_parameter); m_parameters[move(_parameter)] = move(_value); return *this; @@ -44,6 +45,7 @@ Whiskers& Whiskers::operator()(string _parameter, string _value) Whiskers& Whiskers::operator()(string _parameter, bool _value) { + checkParameterValid(_parameter); checkParameterUnknown(_parameter); m_conditions[move(_parameter)] = _value; return *this; @@ -54,7 +56,11 @@ Whiskers& Whiskers::operator()( vector> _values ) { + checkParameterValid(_listParameter); checkParameterUnknown(_listParameter); + for (auto const& element: _values) + for (auto const& val: element) + checkParameterValid(val.first); m_listParameters[move(_listParameter)] = move(_values); return *this; } @@ -64,7 +70,17 @@ string Whiskers::render() const return replace(m_template, m_parameters, m_conditions, m_listParameters); } -void Whiskers::checkParameterUnknown(string const& _parameter) +void Whiskers::checkParameterValid(string const& _parameter) const +{ + static boost::regex validParam("^" + paramRegex() + "$"); + assertThrow( + boost::regex_match(_parameter, validParam), + WhiskersError, + "Parameter" + _parameter + " contains invalid characters." + ); +} + +void Whiskers::checkParameterUnknown(string const& _parameter) const { assertThrow( !m_parameters.count(_parameter), @@ -91,7 +107,7 @@ string Whiskers::replace( ) { using namespace boost; - static regex listOrTag("<([^#/?!>]+)>|<#([^>]+)>(.*?)|<\\?([^>]+)>(.*?)((.*?))?"); + static regex listOrTag("<(" + paramRegex() + ")>|<#(" + paramRegex() + ")>(.*?)|<\\?(" + paramRegex() + ")>(.*?)((.*?))?"); return regex_replace(_template, listOrTag, [&](match_results _match) -> string { string tagName(_match[1]); diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h index 7944358a0..ebe17b51c 100644 --- a/libdevcore/Whiskers.h +++ b/libdevcore/Whiskers.h @@ -85,7 +85,8 @@ public: std::string render() const; private: - void checkParameterUnknown(std::string const& _parameter); + void checkParameterValid(std::string const& _parameter) const; + void checkParameterUnknown(std::string const& _parameter) const; static std::string replace( std::string const& _template, @@ -94,6 +95,8 @@ private: StringListMap const& _listParameters = StringListMap() ); + static std::string paramRegex() { return "[a-zA-Z0-9_$-]+"; } + /// Joins the two maps throwing an exception if two keys are equal. static StringMap joinMaps(StringMap const& _a, StringMap const& _b); diff --git a/test/libdevcore/Whiskers.cpp b/test/libdevcore/Whiskers.cpp index 8a785774e..924f2efd0 100644 --- a/test/libdevcore/Whiskers.cpp +++ b/test/libdevcore/Whiskers.cpp @@ -116,11 +116,11 @@ BOOST_AUTO_TEST_CASE(conditional_plus_list) BOOST_AUTO_TEST_CASE(complicated_replacement) { - string templ = "a x \n >."; + string templ = "a x \n >."; string result = Whiskers(templ) ("b", "BE") ("complicated", "COPL") - ("nesPL \n NEST>."); } @@ -180,6 +180,20 @@ BOOST_AUTO_TEST_CASE(parameter_collision) BOOST_CHECK_THROW(m.render(), WhiskersError); } +BOOST_AUTO_TEST_CASE(invalid_param) +{ + string templ = "a "; + Whiskers m(templ); + BOOST_CHECK_THROW(m("b ", "X"), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(invalid_param_rendered) +{ + string templ = "a "; + Whiskers m(templ); + BOOST_CHECK_EQUAL(m.render(), templ); +} + BOOST_AUTO_TEST_SUITE_END() } From 51ba7f5f171d3faa593d6fa64fda2087d04b4d2b Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Wed, 19 Jun 2019 19:25:05 +0200 Subject: [PATCH 085/115] Add CI job for optimization proofs --- .circleci/config.yml | 19 +++++++++++++++++++ scripts/run_proofs.sh | 29 +++++++++++++++++++++++++++++ test/formal/rule.py | 14 ++++++++++---- 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100755 scripts/run_proofs.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 0cc8bc261..da2de1c34 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,6 +33,9 @@ defaults: command: | export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" scripts/regressions.py -o test_results + - run_proofs: &run_proofs + name: Correctness proofs for optimization rules + command: scripts/run_proofs.sh - solc_artifact: &solc_artifact path: build/solc/solc destination: solc @@ -304,6 +307,21 @@ jobs: name: Test buglist command: ./test/buglistTests.js + proofs: + docker: + - image: buildpack-deps:bionic + environment: + TERM: xterm + steps: + - checkout + - run: + name: Z3 python deps + command: | + apt-get -qq update + apt-get -qy install python-pip + pip install --user z3-solver + - run: *run_proofs + test_x86_linux: docker: - image: buildpack-deps:bionic @@ -490,6 +508,7 @@ workflows: - test_check_spelling: *build_on_tags - test_check_style: *build_on_tags - test_buglist: *build_on_tags + - proofs: *build_on_tags - build_emscripten: *build_on_tags - test_emscripten_solcjs: <<: *build_on_tags diff --git a/scripts/run_proofs.sh b/scripts/run_proofs.sh new file mode 100755 index 000000000..0a9d122d3 --- /dev/null +++ b/scripts/run_proofs.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -e + +REPO_ROOT="$(dirname "$0")"/.. + +git fetch origin +error=0 +for new_proof in $(git diff develop --name-only $REPO_ROOT/test/formal/) +do + set +e + echo "Proving $new_proof..." + output=$(python "$REPO_ROOT/$new_proof") + result=$? + set -e + + if [[ "$result" != 0 ]] + then + echo "Proof $(basename "$new_proof" ".py") failed: $output." + error=1 + fi +done + +if [[ "error" -eq 0 ]] +then + echo "All proofs succeeded." +fi + +exit $error diff --git a/test/formal/rule.py b/test/formal/rule.py index e434fcb3f..9327f7e5a 100644 --- a/test/formal/rule.py +++ b/test/formal/rule.py @@ -1,3 +1,5 @@ +import sys + from z3 import * class Rule: @@ -21,9 +23,9 @@ class Rule: result = self.solver.check() if result == unknown: - raise BaseException('Unable to satisfy requirements.') + self.error('Unable to satisfy requirements.') elif result == unsat: - raise BaseException('Requirements are unsatisfiable.') + self.error('Requirements are unsatisfiable.') self.solver.push() self.solver.add(self.constraints) @@ -31,8 +33,12 @@ class Rule: result = self.solver.check() if result == unknown: - raise BaseException('Unable to prove rule.') + self.error('Unable to prove rule.') elif result == sat: m = self.solver.model() - raise BaseException('Rule is incorrect.\nModel: ' + str(m)) + self.error('Rule is incorrect.\nModel: ' + str(m)) self.solver.pop() + + def error(self, msg): + print(msg) + sys.exit(1) From ecab46c707f6b297c646c9d88cf854a856da10d9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 18 Jun 2019 18:14:49 +0200 Subject: [PATCH 086/115] Finish word size transform. --- libyul/backends/wasm/WordSizeTransform.cpp | 26 ++++++++++++++++--- libyul/backends/wasm/WordSizeTransform.h | 9 ++++--- test/libyul/YulOptimizerTest.cpp | 2 +- .../wordSizeTransform/or_bool_renamed.yul | 25 ++++++++++++++++++ 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 test/libyul/yulOptimizerTests/wordSizeTransform/or_bool_renamed.yul diff --git a/libyul/backends/wasm/WordSizeTransform.cpp b/libyul/backends/wasm/WordSizeTransform.cpp index 4388eebd1..08d55e296 100644 --- a/libyul/backends/wasm/WordSizeTransform.cpp +++ b/libyul/backends/wasm/WordSizeTransform.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -41,6 +43,10 @@ void WordSizeTransform::operator()(FunctionalInstruction& _ins) void WordSizeTransform::operator()(FunctionCall& _fc) { + if (BuiltinFunction const* fun = m_inputDialect.builtin(_fc.functionName.name)) + if (fun->literalArguments) + return; + rewriteFunctionCallArguments(_fc.arguments); } @@ -48,7 +54,7 @@ void WordSizeTransform::operator()(If& _if) { _if.condition = make_unique(FunctionCall{ locationOf(*_if.condition), - Identifier{locationOf(*_if.condition), "or_bool"_yulstring}, // TODO make sure this is not used + Identifier{locationOf(*_if.condition), "or_bool"_yulstring}, expandValueToVector(*_if.condition) }); (*this)(_if.body); @@ -59,6 +65,18 @@ void WordSizeTransform::operator()(Switch&) yulAssert(false, "Switch statement not implemented."); } +void WordSizeTransform::operator()(ForLoop& _for) +{ + (*this)(_for.pre); + _for.condition = make_unique(FunctionCall{ + locationOf(*_for.condition), + Identifier{locationOf(*_for.condition), "or_bool"_yulstring}, + expandValueToVector(*_for.condition) + }); + (*this)(_for.post); + (*this)(_for.body); +} + void WordSizeTransform::operator()(Block& _block) { iterateReplacing( @@ -142,9 +160,11 @@ void WordSizeTransform::operator()(Block& _block) ); } -void WordSizeTransform::run(Block& _ast, NameDispenser& _nameDispenser) +void WordSizeTransform::run(Dialect const& _inputDialect, Block& _ast, NameDispenser& _nameDispenser) { - WordSizeTransform{_nameDispenser}(_ast); + // Free the name `or_bool`. + NameDisplacer{_nameDispenser, {"or_bool"_yulstring}}(_ast); + WordSizeTransform{_inputDialect, _nameDispenser}(_ast); } void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList) diff --git a/libyul/backends/wasm/WordSizeTransform.h b/libyul/backends/wasm/WordSizeTransform.h index 8c71b9817..42bf58940 100644 --- a/libyul/backends/wasm/WordSizeTransform.h +++ b/libyul/backends/wasm/WordSizeTransform.h @@ -51,7 +51,7 @@ namespace yul * take four times the parameters and each of type u64. * In addition, it uses a single other builtin function called `or_bool` that * takes four u64 parameters and is supposed to return the logical disjunction - * of them es a u64 value. + * of them as a u64 value. If this name is already used somewhere, it is renamed. * * Prerequisite: Disambiguator, ExpressionSplitter */ @@ -63,12 +63,14 @@ public: void operator()(FunctionCall&) override; void operator()(If&) override; void operator()(Switch&) override; + void operator()(ForLoop&) override; void operator()(Block& _block) override; - static void run(Block& _ast, NameDispenser& _nameDispenser); + static void run(Dialect const& _inputDialect, Block& _ast, NameDispenser& _nameDispenser); private: - explicit WordSizeTransform(NameDispenser& _nameDispenser): + explicit WordSizeTransform(Dialect const& _inputDialect, NameDispenser& _nameDispenser): + m_inputDialect(_inputDialect), m_nameDispenser(_nameDispenser) { } @@ -80,6 +82,7 @@ private: std::array, 4> expandValue(Expression const& _e); std::vector expandValueToVector(Expression const& _e); + Dialect const& m_inputDialect; NameDispenser& m_nameDispenser; /// maps original u256 variable's name to corresponding u64 variables' names std::map> m_variableMapping; diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 07ab58dd6..b5f397d17 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -297,7 +297,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line disambiguate(); NameDispenser nameDispenser{*m_dialect, *m_ast}; ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - WordSizeTransform::run(*m_ast, nameDispenser); + WordSizeTransform::run(*m_dialect, *m_ast, nameDispenser); } else if (m_optimizerStep == "fullSuite") { diff --git a/test/libyul/yulOptimizerTests/wordSizeTransform/or_bool_renamed.yul b/test/libyul/yulOptimizerTests/wordSizeTransform/or_bool_renamed.yul new file mode 100644 index 000000000..016dd9bb8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/wordSizeTransform/or_bool_renamed.yul @@ -0,0 +1,25 @@ +{ + let or_bool := 2 + if or_bool { sstore(0, 1) } +} +// ==== +// step: wordSizeTransform +// ---- +// { +// let or_bool_3_0 := 0 +// let or_bool_3_1 := 0 +// let or_bool_3_2 := 0 +// let or_bool_3_3 := 2 +// if or_bool(or_bool_3_0, or_bool_3_1, or_bool_3_2, or_bool_3_3) +// { +// let _1_0 := 0 +// let _1_1 := 0 +// let _1_2 := 0 +// let _1_3 := 1 +// let _2_0 := 0 +// let _2_1 := 0 +// let _2_2 := 0 +// let _2_3 := 0 +// sstore(_2_0, _2_1, _2_2, _2_3, _1_0, _1_1, _1_2, _1_3) +// } +// } From 88988af561b7796cda44fa64b21f91960a69ba8b Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 11:24:19 +0200 Subject: [PATCH 087/115] Implement signed addition for sol->yul code generation. --- libsolidity/codegen/YulUtilFunctions.cpp | 31 +++++++++------- libsolidity/codegen/YulUtilFunctions.h | 2 +- .../codegen/ir/IRGeneratorForStatements.cpp | 29 ++++++++++----- ...d_overflow.yul => detect_add_overflow.sol} | 0 .../viaYul/detect_add_overflow_signed.sol | 37 +++++++++++++++++++ 5 files changed, 75 insertions(+), 24 deletions(-) rename test/libsolidity/semanticTests/viaYul/{detect_add_overflow.yul => detect_add_overflow.sol} (100%) create mode 100644 test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 4c8b20636..a791cae35 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -379,27 +379,32 @@ string YulUtilFunctions::roundUpFunction() }); } -string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits) +string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type) { - solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); - string functionName = "checked_add_uint_" + to_string(_bits); + string functionName = "checked_add_" + _type.identifier(); + // TODO: Consider to add a special case for unsigned 256-bit integers + // and use the following instead: + // sum := add(x, y) if lt(sum, x) { revert(0, 0) } return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (x, y) -> sum { - - let mask := - sum := add(and(x, mask), and(y, mask)) - if and(sum, not(mask)) { revert(0, 0) } - - sum := add(x, y) - if lt(sum, x) { revert(0, 0) } - + + // overflow, if x >= 0 and y > (maxValue - x) + if and(iszero(slt(x, 0)), sgt(y, sub(, x))) { revert(0, 0) } + // underflow, if x < 0 and y < (minValue - x) + if and(slt(x, 0), slt(y, sub(, x))) { revert(0, 0) } + + // overflow, if x > (maxValue - y) + if gt(x, sub(, y)) { revert(0, 0) } + + sum := add(x, y) } )") - ("shortType", _bits < 256) ("functionName", functionName) - ("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index ec828ce4f..fcc168626 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -93,7 +93,7 @@ public: std::string roundUpFunction(); /// signature: (x, y) -> sum - std::string overflowCheckedUIntAddFunction(size_t _bits); + std::string overflowCheckedIntAddFunction(IntegerType const& _type); /// signature: (x, y) -> product std::string overflowCheckedUIntMulFunction(size_t _bits); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 80a09ccbd..1c64c2fdc 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1126,18 +1126,27 @@ string IRGeneratorForStatements::binaryOperation( if (IntegerType const* type = dynamic_cast(&_type)) { string fun; - // TODO: Only division is implemented for signed integers for now. - if (!type->isSigned()) + // TODO: Implement all operations for signed and unsigned types. + switch (_operator) { - if (_operator == Token::Add) - fun = m_utils.overflowCheckedUIntAddFunction(type->numBits()); - else if (_operator == Token::Sub) - fun = m_utils.overflowCheckedUIntSubFunction(); - else if (_operator == Token::Mul) - fun = m_utils.overflowCheckedUIntMulFunction(type->numBits()); + case Token::Add: + fun = m_utils.overflowCheckedIntAddFunction(*type); + break; + case Token::Sub: + if (!type->isSigned()) + fun = m_utils.overflowCheckedUIntSubFunction(); + break; + case Token::Mul: + if (!type->isSigned()) + fun = m_utils.overflowCheckedUIntMulFunction(type->numBits()); + break; + case Token::Div: + fun = m_utils.overflowCheckedIntDivFunction(*type); + break; + default: + break; } - if (_operator == Token::Div) - fun = m_utils.overflowCheckedIntDivFunction(*type); + solUnimplementedAssert(!fun.empty(), ""); return fun + "(" + _left + ", " + _right + ")\n"; } diff --git a/test/libsolidity/semanticTests/viaYul/detect_add_overflow.yul b/test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol similarity index 100% rename from test/libsolidity/semanticTests/viaYul/detect_add_overflow.yul rename to test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol diff --git a/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol new file mode 100644 index 000000000..6e44c44ba --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol @@ -0,0 +1,37 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a + b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a + b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(int256,int256): 5, 6 -> 11 +// f(int256,int256): -2, 1 -> -1 +// f(int256,int256): -2, 2 -> 0 +// f(int256,int256): 2, -2 -> 0 +// f(int256,int256): -5, -6 -> -11 +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0, 0x0F -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 0x0F, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 1 -> FAILURE +// f(int256,int256): 1, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000001, -1 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): -1, 0x8000000000000000000000000000000000000000000000000000000000000001 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000000, -1 -> FAILURE +// f(int256,int256): -1, 0x8000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// g(int8,int8): 5, 6 -> 11 +// g(int8,int8): -2, 1 -> -1 +// g(int8,int8): -2, 2 -> 0 +// g(int8,int8): 2, -2 -> 0 +// g(int8,int8): -5, -6 -> -11 +// g(int8,int8): 126, 1 -> 127 +// g(int8,int8): 1, 126 -> 127 +// g(int8,int8): 127, 1 -> FAILURE +// g(int8,int8): 1, 127 -> FAILURE +// g(int8,int8): -127, -1 -> -128 +// g(int8,int8): -1, -127 -> -128 +// g(int8,int8): -127, -2 -> FAILURE +// g(int8,int8): -2, -127 -> FAILURE From 5f6af8b374db026eca74f65f1b942df2a7c95740 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 13:07:06 +0200 Subject: [PATCH 088/115] Implement signed subtraction for sol->yul code generation. --- libsolidity/codegen/YulUtilFunctions.cpp | 16 +++++-- libsolidity/codegen/YulUtilFunctions.h | 2 +- .../codegen/ir/IRGeneratorForStatements.cpp | 3 +- .../viaYul/detect_sub_overflow.sol | 17 ++++++++ .../viaYul/detect_sub_overflow_signed.sol | 42 +++++++++++++++++++ 5 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol create mode 100644 test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index a791cae35..f85a3eac4 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -464,18 +464,28 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) }); } -string YulUtilFunctions::overflowCheckedUIntSubFunction() +string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type) { - string functionName = "checked_sub_uint"; + string functionName = "checked_sub_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { return Whiskers(R"( function (x, y) -> diff { - if lt(x, y) { revert(0, 0) } + + // underflow, if y >= 0 and x < (minValue + y) + if and(iszero(slt(y, 0)), slt(x, add(, y))) { revert(0, 0) } + // overflow, if y < 0 and x > (maxValue + y) + if and(slt(y, 0), sgt(x, add(, y))) { revert(0, 0) } + + if lt(x, y) { revert(0, 0) } + diff := sub(x, y) } )") ("functionName", functionName) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index fcc168626..1fd4e02e1 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -106,7 +106,7 @@ public: /// @returns computes the difference between two values. /// Assumes the input to be in range for the type. /// signature: (x, y) -> diff - std::string overflowCheckedUIntSubFunction(); + std::string overflowCheckedIntSubFunction(IntegerType const& _type); /// @returns the name of a function that fetches the length of the given /// array diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 1c64c2fdc..6919004b6 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1133,8 +1133,7 @@ string IRGeneratorForStatements::binaryOperation( fun = m_utils.overflowCheckedIntAddFunction(*type); break; case Token::Sub: - if (!type->isSigned()) - fun = m_utils.overflowCheckedUIntSubFunction(); + fun = m_utils.overflowCheckedIntSubFunction(*type); break; case Token::Mul: if (!type->isSigned()) diff --git a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol new file mode 100644 index 000000000..5342052ff --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol @@ -0,0 +1,17 @@ +contract C { + function f(uint a, uint b) public pure returns (uint x) { + x = a - b; + } + function g(uint8 a, uint8 b) public pure returns (uint8 x) { + x = a - b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(uint256,uint256): 6, 5 -> 1 +// f(uint256,uint256): 6, 6 -> 0 +// f(uint256,uint256): 5, 6 -> FAILURE +// g(uint8,uint8): 6, 5 -> 1 +// g(uint8,uint8): 6, 6 -> 0 +// g(uint8,uint8): 5, 6 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol new file mode 100644 index 000000000..1a87ef9ec --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol @@ -0,0 +1,42 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a - b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a - b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(int256,int256): 5, 6 -> -1 +// f(int256,int256): -2, 1 -> -3 +// f(int256,int256): -2, 2 -> -4 +// f(int256,int256): 2, -2 -> 4 +// f(int256,int256): 2, 2 -> 0 +// f(int256,int256): -5, -6 -> 1 +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0, -15 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0, -16 -> FAILURE +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, -1 -> FAILURE +// f(int256,int256): 15, 0x8000000000000000000000000000000000000000000000000000000000000010 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 16, 0x8000000000000000000000000000000000000000000000000000000000000010 -> FAILURE +// f(int256,int256): 1, 0x8000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// f(int256,int256): -1, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): -2, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000001, 1 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000001, 2 -> FAILURE +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000000, 1 -> FAILURE +// g(int8,int8): 5, 6 -> -1 +// g(int8,int8): -2, 1 -> -3 +// g(int8,int8): -2, 2 -> -4 +// g(int8,int8): 2, -2 -> 4 +// g(int8,int8): 2, 2 -> 0 +// g(int8,int8): -5, -6 -> 1 +// g(int8,int8): 126, -1 -> 127 +// g(int8,int8): 1, -126 -> 127 +// g(int8,int8): 127, -1 -> FAILURE +// g(int8,int8): 1, -127 -> FAILURE +// g(int8,int8): -127, 1 -> -128 +// g(int8,int8): -1, 127 -> -128 +// g(int8,int8): -127, 2 -> FAILURE +// g(int8,int8): -2, 127 -> FAILURE From e4c884ae1379e1256700733e4f077721fe924b0d Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 13:19:38 +0200 Subject: [PATCH 089/115] Unify style of checked integer division for sol->yul code generation. --- libsolidity/codegen/YulUtilFunctions.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index f85a3eac4..737edd696 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -439,8 +439,6 @@ string YulUtilFunctions::overflowCheckedUIntMulFunction(size_t _bits) string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) { - unsigned bits = _type.numBits(); - solAssert(0 < bits && bits <= 256 && bits % 8 == 0, ""); string functionName = "checked_div_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return @@ -448,7 +446,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) function (x, y) -> r { if iszero(y) { revert(0, 0) } - // x / -1 == x + // overflow for minVal / -1 if and( eq(x, ), eq(y, sub(0, 1)) @@ -457,10 +455,10 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) r := sdiv(x, y) } )") - ("functionName", functionName) - ("signed", _type.isSigned()) - ("minVal", (0 - (u256(1) << (bits - 1))).str()) - .render(); + ("functionName", functionName) + ("signed", _type.isSigned()) + ("minVal", toCompactHexWithPrefix(u256(_type.minValue()))) + .render(); }); } From a5b9f634efad2087786a4867586327dc3be40918 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 14:06:13 +0200 Subject: [PATCH 090/115] Implement signed multiplication for sol->yul code generation. --- libsolidity/codegen/YulUtilFunctions.cpp | 44 +++++++------- libsolidity/codegen/YulUtilFunctions.h | 2 +- .../codegen/ir/IRGeneratorForStatements.cpp | 3 +- ...l_overflow.yul => detect_mul_overflow.sol} | 0 .../viaYul/detect_mul_overflow_signed.sol | 58 +++++++++++++++++++ 5 files changed, 84 insertions(+), 23 deletions(-) rename test/libsolidity/semanticTests/viaYul/{detect_mul_overflow.yul => detect_mul_overflow.sol} (100%) create mode 100644 test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 737edd696..9c93441ef 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -409,31 +409,35 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type) }); } -string YulUtilFunctions::overflowCheckedUIntMulFunction(size_t _bits) +string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) { - solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); - string functionName = "checked_mul_uint_" + to_string(_bits); + string functionName = "checked_mul_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return - // - The current overflow check *before* the multiplication could - // be replaced by the following check *after* the multiplication: - // if and(iszero(iszero(x)), iszero(eq(div(product, x), y))) { revert(0, 0) } - // - The case the x equals 0 could be treated separately and directly return zero. + // Multiplication by zero could be treated separately and directly return zero. Whiskers(R"( function (x, y) -> product { - if and(iszero(iszero(x)), lt(div(, x), y)) { revert(0, 0) } - - product := mulmod(x, y, ) - - product := mul(x, y) - + + // overflow, if x > 0, y > 0 and x > (maxValue / y) + if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(, y))) { revert(0, 0) } + // underflow, if x > 0, y < 0 and y < (minValue / x) + if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(, x))) { revert(0, 0) } + // underflow, if x < 0, y > 0 and x < (minValue / y) + if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(, y))) { revert(0, 0) } + // overflow, if x < 0, y < 0 and x < (maxValue / y) + if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(, y))) { revert(0, 0) } + + // overflow, if x != 0 and y > (maxValue / x) + if and(iszero(iszero(x)), gt(y, div(, x))) { revert(0, 0) } + + product := mul(x, y) } )") - ("shortType", _bits < 256) - ("functionName", functionName) - ("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits)) - ("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) - .render(); + ("functionName", functionName) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) + .render(); }); } @@ -620,7 +624,7 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) ("multiSlot", baseType.storageSize() > 1) ("itemsPerSlot", to_string(32 / baseStorageBytes)) ("storageSize", baseType.storageSize().str()) - ("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits())) + ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) .render(); } case DataLocation::CallData: // fallthrough @@ -636,7 +640,7 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) ("functionName", functionName) ("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize()) ("byteArray", _type.isByteArray()) - ("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits())) + ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) .render(); default: solAssert(false, ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 1fd4e02e1..ccfa29cc7 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -96,7 +96,7 @@ public: std::string overflowCheckedIntAddFunction(IntegerType const& _type); /// signature: (x, y) -> product - std::string overflowCheckedUIntMulFunction(size_t _bits); + std::string overflowCheckedIntMulFunction(IntegerType const& _type); /// @returns name of function to perform division on integers. /// Checks for division by zero and the special case of diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 6919004b6..de1fe3ac4 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1136,8 +1136,7 @@ string IRGeneratorForStatements::binaryOperation( fun = m_utils.overflowCheckedIntSubFunction(*type); break; case Token::Mul: - if (!type->isSigned()) - fun = m_utils.overflowCheckedUIntMulFunction(type->numBits()); + fun = m_utils.overflowCheckedIntMulFunction(*type); break; case Token::Div: fun = m_utils.overflowCheckedIntDivFunction(*type); diff --git a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.yul b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol similarity index 100% rename from test/libsolidity/semanticTests/viaYul/detect_mul_overflow.yul rename to test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol diff --git a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol new file mode 100644 index 000000000..930a24ed7 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol @@ -0,0 +1,58 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a * b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a * b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(int256,int256): 5, 6 -> 30 +// f(int256,int256): -1, 1 -> -1 +// f(int256,int256): -1, 2 -> -2 +// # positive, positive # +// f(int256,int256): 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, 2 -> FAILURE +// f(int256,int256): 2, 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// # positive, negative # +// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, -2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000001, -2 -> FAILURE +// f(int256,int256): 2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 2, 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE +// # negative, positive # +// f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000001 -> FAILURE +// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, 2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> FAILURE +// # negative, negative # +// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000001, -2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, -2 -> FAILURE +// f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000001 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// # small type # +// g(int8,int8): 5, 6 -> 30 +// g(int8,int8): -1, 1 -> -1 +// g(int8,int8): -1, 2 -> -2 +// # positive, positive # +// g(int8,int8): 63, 2 -> 126 +// g(int8,int8): 64, 2 -> FAILURE +// g(int8,int8): 2, 63 -> 126 +// g(int8,int8): 2, 64 -> FAILURE +// # positive, negative # +// g(int8,int8): 64, -2 -> -128 +// g(int8,int8): 65, -2 -> FAILURE +// g(int8,int8): 2, -64 -> -128 +// g(int8,int8): 2, -65 -> FAILURE +// # negative, positive # +// g(int8,int8): -2, 64 -> -128 +// g(int8,int8): -2, 65 -> FAILURE +// g(int8,int8): -64, 2 -> -128 +// g(int8,int8): -65, 2 -> FAILURE +// # negative, negative # +// g(int8,int8): -63, -2 -> 126 +// g(int8,int8): -64, -2 -> FAILURE +// g(int8,int8): -2, -63 -> 126 +// g(int8,int8): -2, -64 -> FAILURE From fcd3410f263162d939a072e6dac130aef62b8b8b Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 12 Jun 2019 16:27:26 +0200 Subject: [PATCH 091/115] Implement checked mod for sol->yul code generation. --- libsolidity/codegen/YulUtilFunctions.cpp | 17 +++++++++ libsolidity/codegen/YulUtilFunctions.h | 4 +++ .../codegen/ir/IRGeneratorForStatements.cpp | 3 ++ .../semanticTests/viaYul/detect_mod_zero.sol | 25 +++++++++++++ .../viaYul/detect_mod_zero_signed.sol | 35 +++++++++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol create mode 100644 test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 9c93441ef..998a4ec1d 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -466,6 +466,23 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) }); } +string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type) +{ + string functionName = "checked_mod_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (x, y) -> r { + if iszero(y) { revert(0, 0) } + r := smod(x, y) + } + )") + ("functionName", functionName) + ("signed", _type.isSigned()) + .render(); + }); +} + string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type) { string functionName = "checked_sub_" + _type.identifier(); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index ccfa29cc7..f424d7d54 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -103,6 +103,10 @@ public: /// signed division of the smallest number by -1. std::string overflowCheckedIntDivFunction(IntegerType const& _type); + /// @returns name of function to perform modulo on integers. + /// Reverts for modulo by zero. + std::string checkedIntModFunction(IntegerType const& _type); + /// @returns computes the difference between two values. /// Assumes the input to be in range for the type. /// signature: (x, y) -> diff diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index de1fe3ac4..541c610a7 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1141,6 +1141,9 @@ string IRGeneratorForStatements::binaryOperation( case Token::Div: fun = m_utils.overflowCheckedIntDivFunction(*type); break; + case Token::Mod: + fun = m_utils.checkedIntModFunction(*type); + break; default: break; } diff --git a/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol b/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol new file mode 100644 index 000000000..d8defa17a --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol @@ -0,0 +1,25 @@ +contract C { + function f(uint a, uint b) public pure returns (uint x) { + x = a % b; + } + function g(uint8 a, uint8 b) public pure returns (uint8 x) { + x = a % b; + } +} +// ==== +// compileViaYul: only +// ---- +// f(uint256,uint256): 10, 3 -> 1 +// f(uint256,uint256): 10, 2 -> 0 +// f(uint256,uint256): 11, 2 -> 1 +// f(uint256,uint256): 2, 2 -> 0 +// f(uint256,uint256): 1, 0 -> FAILURE +// f(uint256,uint256): 0, 0 -> FAILURE +// f(uint256,uint256): 0, 1 -> 0 +// g(uint8,uint8): 10, 3 -> 1 +// g(uint8,uint8): 10, 2 -> 0 +// g(uint8,uint8): 11, 2 -> 1 +// g(uint8,uint8): 2, 2 -> 0 +// g(uint8,uint8): 1, 0 -> FAILURE +// g(uint8,uint8): 0, 0 -> FAILURE +// g(uint8,uint8): 0, 1 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol new file mode 100644 index 000000000..9164bf06c --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol @@ -0,0 +1,35 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a % b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a % b; + } +} +// ==== +// compileViaYul: only +// ---- +// f(int256,int256): 10, 3 -> 1 +// f(int256,int256): 10, 2 -> 0 +// f(int256,int256): 11, 2 -> 1 +// f(int256,int256): -10, 3 -> -1 +// f(int256,int256): 10, -3 -> 1 +// f(int256,int256): -10, -3 -> -1 +// f(int256,int256): 2, 2 -> 0 +// f(int256,int256): 1, 0 -> FAILURE +// f(int256,int256): -1, 0 -> FAILURE +// f(int256,int256): 0, 0 -> FAILURE +// f(int256,int256): 0, 1 -> 0 +// f(int256,int256): 0, -1 -> 0 +// g(int8,int8): 10, 3 -> 1 +// g(int8,int8): 10, 2 -> 0 +// g(int8,int8): 11, 2 -> 1 +// g(int8,int8): -10, 3 -> -1 +// g(int8,int8): 10, -3 -> 1 +// g(int8,int8): -10, -3 -> -1 +// g(int8,int8): 2, 2 -> 0 +// g(int8,int8): 1, 0 -> FAILURE +// g(int8,int8): -1, 0 -> FAILURE +// g(int8,int8): 0, 0 -> FAILURE +// g(int8,int8): 0, 1 -> 0 +// g(int8,int8): 0, -1 -> 0 From 346c512cd7a4b8355b7aa2b9f82c721c8d68ea21 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 20 Jun 2019 14:50:39 +0200 Subject: [PATCH 092/115] [Sol->Yul] Implement _slot/_offset suffix for storage variables --- .../codegen/ir/IRGeneratorForStatements.cpp | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 80a09ccbd..db5b9813f 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -56,6 +56,35 @@ struct CopyTranslate: public yul::ASTCopier using ASTCopier::operator(); + yul::Expression operator()(yul::Identifier const& _identifier) override + { + if (m_references.count(&_identifier)) + { + auto const& reference = m_references.at(&_identifier); + auto const varDecl = dynamic_cast(reference.declaration); + solUnimplementedAssert(varDecl, ""); + + if (reference.isOffset || reference.isSlot) + { + solAssert(reference.isOffset != reference.isSlot, ""); + + pair slot_offset = m_context.storageLocationOfVariable(*varDecl); + + string const value = reference.isSlot ? + slot_offset.first.str() : + to_string(slot_offset.second); + + return yul::Literal{ + _identifier.location, + yul::LiteralKind::Number, + yul::YulString{value}, + yul::YulString{"uint256"} + }; + } + } + return ASTCopier::operator()(_identifier); + } + yul::YulString translateIdentifier(yul::YulString _name) override { // Strictly, the dialect used by inline assembly (m_dialect) could be different @@ -76,9 +105,10 @@ struct CopyTranslate: public yul::ASTCopier auto const& reference = m_references.at(&_identifier); auto const varDecl = dynamic_cast(reference.declaration); solUnimplementedAssert(varDecl, ""); - solUnimplementedAssert( + + solAssert( reference.isOffset == false && reference.isSlot == false, - "" + "Should not be called for offset/slot" ); return yul::Identifier{ From c71fb76bb217f56b3a8a707998463ac8a6b18987 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 14 Jun 2019 17:47:54 +0200 Subject: [PATCH 093/115] Proofs for the overflow and underflow conditions in checked arithmetic for Sol->Yul code generation. --- test/formal/checked_int_add.py | 39 +++++++++++++++++++++++++++ test/formal/checked_int_div.py | 35 +++++++++++++++++++++++++ test/formal/checked_int_mul_16.py | 42 ++++++++++++++++++++++++++++++ test/formal/checked_int_sub.py | 39 +++++++++++++++++++++++++++ test/formal/checked_uint_add.py | 35 +++++++++++++++++++++++++ test/formal/checked_uint_mul_16.py | 36 +++++++++++++++++++++++++ test/formal/checked_uint_sub.py | 35 +++++++++++++++++++++++++ test/formal/opcodes.py | 3 +++ test/formal/util.py | 27 +++++++++++++++++++ 9 files changed, 291 insertions(+) create mode 100644 test/formal/checked_int_add.py create mode 100644 test/formal/checked_int_div.py create mode 100644 test/formal/checked_int_mul_16.py create mode 100644 test/formal/checked_int_sub.py create mode 100644 test/formal/checked_uint_add.py create mode 100644 test/formal/checked_uint_mul_16.py create mode 100644 test/formal/checked_uint_sub.py create mode 100644 test/formal/util.py diff --git a/test/formal/checked_int_add.py b/test/formal/checked_int_add.py new file mode 100644 index 000000000..6e74944e2 --- /dev/null +++ b/test/formal/checked_int_add.py @@ -0,0 +1,39 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer addition. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow and underflow conditions + actual_overflow = Not(BVAddNoOverflow(X_short, Y_short, True)) + actual_underflow = Not(BVAddNoUnderflow(X_short, Y_short)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVSignedMax(type_bits, n_bits) + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntAddFunction + overflow_check = AND(ISZERO(SLT(X, 0)), SGT(Y, SUB(maxValue, X))) + underflow_check = AND(SLT(X, 0), SLT(Y, SUB(minValue, X))) + + rule.check(actual_overflow, overflow_check != 0) + rule.check(actual_underflow, underflow_check != 0) + + type_bits *= 2 diff --git a/test/formal/checked_int_div.py b/test/formal/checked_int_div.py new file mode 100644 index 000000000..2a36a9488 --- /dev/null +++ b/test/formal/checked_int_div.py @@ -0,0 +1,35 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer division. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow conditions + actual_overflow = Not(BVSDivNoOverflow(X_short, Y_short)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntDivFunction + overflow_check = AND(EQ(X, minValue), EQ(Y, SUB(0, 1))) + + rule.check(actual_overflow, overflow_check != 0) + + type_bits *= 2 diff --git a/test/formal/checked_int_mul_16.py b/test/formal/checked_int_mul_16.py new file mode 100644 index 000000000..cc8743b1d --- /dev/null +++ b/test/formal/checked_int_mul_16.py @@ -0,0 +1,42 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer multiplication. +""" + +# Approximation with 16-bit base types. +n_bits = 16 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow and underflow conditions + actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, True)) + actual_underflow = Not(BVMulNoUnderflow(X_short, Y_short)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVSignedMax(type_bits, n_bits) + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntMulFunction + overflow_check_1 = AND(AND(SGT(X, 0), SGT(Y, 0)), GT(X, DIV(maxValue, Y))) + underflow_check_1 = AND(AND(SGT(X, 0), SLT(Y, 0)), SLT(Y, SDIV(minValue, X))) + underflow_check_2 = AND(AND(SLT(X, 0), SGT(Y, 0)), SLT(X, SDIV(minValue, Y))) + overflow_check_2 = AND(AND(SLT(X, 0), SLT(Y, 0)), SLT(X, SDIV(maxValue, Y))) + + rule.check(actual_overflow, Or(overflow_check_1 != 0, overflow_check_2 != 0)) + rule.check(actual_underflow, Or(underflow_check_1 != 0, underflow_check_2 != 0)) + + type_bits *= 2 diff --git a/test/formal/checked_int_sub.py b/test/formal/checked_int_sub.py new file mode 100644 index 000000000..72a0a2109 --- /dev/null +++ b/test/formal/checked_int_sub.py @@ -0,0 +1,39 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer subtraction. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow and underflow conditions + actual_overflow = Not(BVSubNoOverflow(X_short, Y_short)) + actual_underflow = Not(BVSubNoUnderflow(X_short, Y_short, True)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVSignedMax(type_bits, n_bits) + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntSubFunction + underflow_check = AND(ISZERO(SLT(Y, 0)), SLT(X, ADD(minValue, Y))) + overflow_check = AND(SLT(Y, 0), SGT(X, ADD(maxValue, Y))) + + rule.check(actual_underflow, underflow_check != 0) + rule.check(actual_overflow, overflow_check != 0) + + type_bits *= 2 diff --git a/test/formal/checked_uint_add.py b/test/formal/checked_uint_add.py new file mode 100644 index 000000000..596fa04f0 --- /dev/null +++ b/test/formal/checked_uint_add.py @@ -0,0 +1,35 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked unsigned integer addition. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow condition + actual_overflow = Not(BVAddNoOverflow(X_short, Y_short, False)) + + # cast to full n_bits values + X = BVUnsignedUpCast(X_short, n_bits) + Y = BVUnsignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVUnsignedMax(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntAddFunction + overflow_check = GT(X, SUB(maxValue, Y)) + + rule.check(overflow_check != 0, actual_overflow) + + type_bits *= 2 diff --git a/test/formal/checked_uint_mul_16.py b/test/formal/checked_uint_mul_16.py new file mode 100644 index 000000000..6e8901799 --- /dev/null +++ b/test/formal/checked_uint_mul_16.py @@ -0,0 +1,36 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked unsigned integer multiplication. +""" + +# Approximation with 16-bit base types. +n_bits = 16 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow condition + actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, False)) + + # cast to full n_bits values + X = BVUnsignedUpCast(X_short, n_bits) + Y = BVUnsignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVUnsignedMax(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntMulFunction + overflow_check = AND(ISZERO(ISZERO(X)), GT(Y, DIV(maxValue, X))) + + rule.check(overflow_check != 0, actual_overflow) + + type_bits *= 2 diff --git a/test/formal/checked_uint_sub.py b/test/formal/checked_uint_sub.py new file mode 100644 index 000000000..b0f25b582 --- /dev/null +++ b/test/formal/checked_uint_sub.py @@ -0,0 +1,35 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked unsigned integer subtraction. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow condition + actual_overflow = Not(BVSubNoUnderflow(X_short, Y_short, False)) + + # cast to full n_bits values + X = BVUnsignedUpCast(X_short, n_bits) + Y = BVUnsignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVUnsignedMax(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntSubFunction + overflow_check = LT(X, Y) + + rule.check(overflow_check != 0, actual_overflow) + + type_bits *= 2 diff --git a/test/formal/opcodes.py b/test/formal/opcodes.py index ca6282f70..cdd60ddae 100644 --- a/test/formal/opcodes.py +++ b/test/formal/opcodes.py @@ -33,6 +33,9 @@ def SLT(x, y): def SGT(x, y): return If(x > y, BitVecVal(1, x.size()), BitVecVal(0, x.size())) +def EQ(x, y): + return If(x == y, BitVecVal(1, x.size()), BitVecVal(0, x.size())) + def ISZERO(x): return If(x == 0, BitVecVal(1, x.size()), BitVecVal(0, x.size())) diff --git a/test/formal/util.py b/test/formal/util.py new file mode 100644 index 000000000..ec98f9c81 --- /dev/null +++ b/test/formal/util.py @@ -0,0 +1,27 @@ +from z3 import * + +def BVUnsignedUpCast(x, n_bits): + assert(x.size() <= n_bits) + if x.size() < n_bits: + return Concat(BitVecVal(0, n_bits - x.size()), x) + else: + return x + +def BVUnsignedMax(type_bits, n_bits): + assert(type_bits <= n_bits) + return BitVecVal((1 << type_bits) - 1, n_bits) + +def BVSignedUpCast(x, n_bits): + assert(x.size() <= n_bits) + if x.size() < n_bits: + return Concat(If(x < 0, BitVecVal(-1, n_bits - x.size()), BitVecVal(0, n_bits - x.size())), x) + else: + return x + +def BVSignedMax(type_bits, n_bits): + assert(type_bits <= n_bits) + return BitVecVal((1 << (type_bits - 1)) - 1, n_bits) + +def BVSignedMin(type_bits, n_bits): + assert(type_bits <= n_bits) + return BitVecVal(-(1 << (type_bits - 1)), n_bits) From c3ccce9745f6ed064ec9d694cf6ebce273000eb9 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Thu, 20 Jun 2019 13:49:24 +0200 Subject: [PATCH 094/115] Fix run_proofs script to actually run proofs --- scripts/run_proofs.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/run_proofs.sh b/scripts/run_proofs.sh index 0a9d122d3..3a89d6b70 100755 --- a/scripts/run_proofs.sh +++ b/scripts/run_proofs.sh @@ -2,15 +2,17 @@ set -e +( REPO_ROOT="$(dirname "$0")"/.. +cd "$REPO_ROOT" git fetch origin error=0 -for new_proof in $(git diff develop --name-only $REPO_ROOT/test/formal/) +for new_proof in $(git diff origin/develop --name-only test/formal/) do set +e echo "Proving $new_proof..." - output=$(python "$REPO_ROOT/$new_proof") + output=$(python "$new_proof") result=$? set -e @@ -27,3 +29,4 @@ then fi exit $error +) From 1dd63f416edd0af9cea3edb68fb80d4dcb6c5813 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 13 Jun 2019 17:22:24 +0200 Subject: [PATCH 095/115] [Sol->Yul] Implement index access for storage arrays --- libsolidity/codegen/YulUtilFunctions.cpp | 30 +++++++++++ libsolidity/codegen/YulUtilFunctions.h | 6 +++ .../codegen/ir/IRGeneratorForStatements.cpp | 40 ++++++++++++++- libsolidity/codegen/ir/IRLValue.cpp | 3 +- .../viaYul/array_storage_index_access.sol | 26 ++++++++++ .../array_storage_index_boundery_test.sol | 21 ++++++++ .../array_storage_index_zeroed_test.sol | 51 +++++++++++++++++++ 7 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 test/libsolidity/semanticTests/viaYul/array_storage_index_access.sol create mode 100644 test/libsolidity/semanticTests/viaYul/array_storage_index_boundery_test.sol create mode 100644 test/libsolidity/semanticTests/viaYul/array_storage_index_zeroed_test.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 4c8b20636..643de4c7c 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -690,6 +690,36 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) }); } +string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type) +{ + solUnimplementedAssert(_type.baseType()->storageBytes() > 16, ""); + + string functionName = "storage_array_index_access_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array, index) -> slot, offset { + if iszero(lt(index, (array))) { + invalid() + } + + let data := (array) + + + + slot := add(data, mul(index, )) + offset := 0 + + } + )") + ("functionName", functionName) + ("arrayLen", arrayLengthFunction(_type)) + ("dataAreaFunc", arrayDataAreaFunction(_type)) + ("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16) + ("storageSize", _type.baseType()->storageSize().str()) + .render(); + }); +} + string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index ec828ce4f..fd3bef809 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -136,6 +136,12 @@ public: /// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer /// for the data position of an array which is stored in that slot / memory area / calldata area. std::string arrayDataAreaFunction(ArrayType const& _type); + + /// @returns the name of a function that returns the slot and offset for the + /// given array and index + /// signature: (array, index) -> slot, offset + std::string storageArrayIndexAccessFunction(ArrayType const& _type); + /// @returns the name of a function that advances an array data pointer to the next element. /// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot. std::string nextArrayElementFunction(ArrayType const& _type); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index db5b9813f..5ddb9f29b 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -820,7 +820,45 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) )); } else if (baseType.category() == Type::Category::Array) - solUnimplementedAssert(false, ""); + { + ArrayType const& arrayType = dynamic_cast(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + switch (arrayType.location()) + { + case DataLocation::Storage: + { + string slot = m_context.newYulVariable(); + string offset = m_context.newYulVariable(); + + m_code << Whiskers(R"( + let , := (, ) + )") + ("slot", slot) + ("offset", offset) + ("indexFunc", m_utils.storageArrayIndexAccessFunction(arrayType)) + ("array", m_context.variable(_indexAccess.baseExpression())) + ("index", m_context.variable(*_indexAccess.indexExpression())) + .render(); + + setLValue(_indexAccess, make_unique( + m_context, + slot, + offset, + *_indexAccess.annotation().type + )); + + break; + } + case DataLocation::Memory: + solUnimplementedAssert(false, ""); + break; + case DataLocation::CallData: + solUnimplementedAssert(false, ""); + break; + } + + } else if (baseType.category() == Type::Category::FixedBytes) solUnimplementedAssert(false, ""); else if (baseType.category() == Type::Category::TypeType) diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index 010eecb75..f1e5cbc7c 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -135,7 +135,8 @@ string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) string IRStorageItem::setToZero() const { - solUnimplemented("Delete for storage location not yet implemented"); + solUnimplementedAssert(m_type->isValueType(), ""); + return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type); } IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType): diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_index_access.sol b/test/libsolidity/semanticTests/viaYul/array_storage_index_access.sol new file mode 100644 index 000000000..ca79ea742 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_index_access.sol @@ -0,0 +1,26 @@ +contract C { + uint[] storageArray; + function test_indicies(uint256 len) public + { + storageArray.length = len; + + for (uint i = 0; i < len; i++) + storageArray[i] = i + 1; + + for (uint i = 0; i < len; i++) + require(storageArray[i] == i + 1); + } +} +// ==== +// compileViaYul: true +// ---- +// test_indicies(uint256): 1 -> +// test_indicies(uint256): 129 -> +// test_indicies(uint256): 5 -> +// test_indicies(uint256): 10 -> +// test_indicies(uint256): 15 -> +// test_indicies(uint256): 0xFF -> +// test_indicies(uint256): 1000 -> +// test_indicies(uint256): 129 -> +// test_indicies(uint256): 128 -> +// test_indicies(uint256): 1 -> diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_index_boundery_test.sol b/test/libsolidity/semanticTests/viaYul/array_storage_index_boundery_test.sol new file mode 100644 index 000000000..8c5ee6845 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_index_boundery_test.sol @@ -0,0 +1,21 @@ +contract C { + uint[] storageArray; + function test_boundery_check(uint256 len, uint256 access) public returns +(uint256) + { + storageArray.length = len; + return storageArray[access]; + } +} +// ==== +// compileViaYul: true +// ---- +// test_boundery_check(uint256, uint256): 10, 11 -> FAILURE +// test_boundery_check(uint256, uint256): 10, 9 -> 0 +// test_boundery_check(uint256, uint256): 1, 9 -> FAILURE +// test_boundery_check(uint256, uint256): 1, 1 -> FAILURE +// test_boundery_check(uint256, uint256): 10, 10 -> FAILURE +// test_boundery_check(uint256, uint256): 256, 256 -> FAILURE +// test_boundery_check(uint256, uint256): 256, 255 -> 0 +// test_boundery_check(uint256, uint256): 256, 0xFFFF -> FAILURE +// test_boundery_check(uint256, uint256): 256, 2 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_index_zeroed_test.sol b/test/libsolidity/semanticTests/viaYul/array_storage_index_zeroed_test.sol new file mode 100644 index 000000000..344c0add5 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_index_zeroed_test.sol @@ -0,0 +1,51 @@ +contract C { + uint[] storageArray; + function test_zeroed_indicies(uint256 len) public + { + storageArray.length = len; + + for (uint i = 0; i < len; i++) + storageArray[i] = i + 1; + + if (len > 3) + { + storageArray.length = 3; + + for (uint i = 3; i < len; i++) + { + assembly { + mstore(0, storageArray_slot) + let pos := add(keccak256(0, 0x20), i) + + if iszero(eq(sload(pos), 0)) { + revert(0, 0) + } + } + } + + } + + storageArray.length = 0; + storageArray.length = len; + + for (uint i = 0; i < len; i++) + { + require(storageArray[i] == 0); + + uint256 val = storageArray[i]; + uint256 check; + + assembly { check := iszero(val) } + + require(check == 1); + } + } +} +// ==== +// compileViaYul: true +// ---- +// test_zeroed_indicies(uint256): 1 -> +// test_zeroed_indicies(uint256): 5 -> +// test_zeroed_indicies(uint256): 10 -> +// test_zeroed_indicies(uint256): 15 -> +// test_zeroed_indicies(uint256): 0xFF -> From 1f9d11c6444108a10789d7ec3633bca8baf23417 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 21 May 2019 15:52:15 +0200 Subject: [PATCH 096/115] Knowledge about storage. --- libdevcore/InvertibleMap.h | 42 ++++++ libyul/CMakeLists.txt | 4 + libyul/Dialect.h | 3 + libyul/backends/evm/EVMDialect.cpp | 9 +- libyul/backends/wasm/WasmDialect.cpp | 1 + libyul/optimiser/DataFlowAnalyzer.cpp | 122 +++++++++++++++++- libyul/optimiser/DataFlowAnalyzer.h | 40 +++++- libyul/optimiser/KnowledgeBase.cpp | 31 +++++ libyul/optimiser/KnowledgeBase.h | 53 ++++++++ libyul/optimiser/SLoadResolver.cpp | 48 +++++++ libyul/optimiser/SLoadResolver.h | 49 +++++++ libyul/optimiser/Semantics.cpp | 5 + libyul/optimiser/Semantics.h | 5 + test/libyul/YulOptimizerTest.cpp | 16 +++ .../yulOptimizerTests/fullSuite/storage.yul | 15 +++ .../sloadResolver/reassign.yul | 16 +++ .../reassign_value_expression.yul | 32 +++++ .../sloadResolver/second_store.yul | 19 +++ .../sloadResolver/second_store_same_value.yul | 19 +++ .../sloadResolver/simple.yul | 14 ++ 20 files changed, 537 insertions(+), 6 deletions(-) create mode 100644 libyul/optimiser/KnowledgeBase.cpp create mode 100644 libyul/optimiser/KnowledgeBase.h create mode 100644 libyul/optimiser/SLoadResolver.cpp create mode 100644 libyul/optimiser/SLoadResolver.h create mode 100644 test/libyul/yulOptimizerTests/fullSuite/storage.yul create mode 100644 test/libyul/yulOptimizerTests/sloadResolver/reassign.yul create mode 100644 test/libyul/yulOptimizerTests/sloadResolver/reassign_value_expression.yul create mode 100644 test/libyul/yulOptimizerTests/sloadResolver/second_store.yul create mode 100644 test/libyul/yulOptimizerTests/sloadResolver/second_store_same_value.yul create mode 100644 test/libyul/yulOptimizerTests/sloadResolver/simple.yul diff --git a/libdevcore/InvertibleMap.h b/libdevcore/InvertibleMap.h index 9c67119a1..4448d03d7 100644 --- a/libdevcore/InvertibleMap.h +++ b/libdevcore/InvertibleMap.h @@ -20,6 +20,48 @@ #include #include +/** + * Data structure that keeps track of values and keys of a mapping. + */ +template +struct InvertibleMap +{ + std::map values; + // references[x] == {y | values[y] == x} + std::map> references; + + void set(K _key, V _value) + { + if (values.count(_key)) + references[values[_key]].erase(_key); + values[_key] = _value; + references[_value].insert(_key); + } + + void eraseKey(K _key) + { + if (values.count(_key)) + references[values[_key]].erase(_key); + values.erase(_key); + } + + void eraseValue(V _value) + { + if (references.count(_value)) + { + for (V v: references[_value]) + values.erase(v); + references.erase(_value); + } + } + + void clear() + { + values.clear(); + references.clear(); + } +}; + template struct InvertibleRelation { diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index f85f4e7c4..e11f5dc5f 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -94,6 +94,8 @@ add_library(yul optimiser/FunctionHoister.h optimiser/InlinableExpressionFunctionFinder.cpp optimiser/InlinableExpressionFunctionFinder.h + optimiser/KnowledgeBase.cpp + optimiser/KnowledgeBase.h optimiser/MainFunction.cpp optimiser/MainFunction.h optimiser/Metrics.cpp @@ -109,6 +111,8 @@ add_library(yul optimiser/RedundantAssignEliminator.cpp optimiser/RedundantAssignEliminator.h optimiser/Rematerialiser.cpp + optimiser/SLoadResolver.cpp + optimiser/SLoadResolver.h optimiser/Rematerialiser.h optimiser/SSAReverser.cpp optimiser/SSAReverser.h diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 9dd41ca34..feffc5890 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -57,6 +57,9 @@ struct BuiltinFunction bool sideEffectFreeIfNoMSize = false; /// If true, this is the msize instruction. bool isMSize = false; + /// If false, storage of the current contract before and after the function is the same + /// under every circumstance. If the function does not return, this can be false. + bool invalidatesStorage = true; /// If true, can only accept literals as arguments and they cannot be moved to variables. bool literalArguments = false; }; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 323b7d7bd..ba5163689 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -54,6 +54,7 @@ pair createEVMFunction( f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction); f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction); f.isMSize = _instruction == dev::eth::Instruction::MSIZE; + f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction); f.literalArguments = false; f.instruction = _instruction; f.generateCode = [_instruction]( @@ -76,6 +77,7 @@ pair createFunction( bool _movable, bool _sideEffectFree, bool _sideEffectFreeIfNoMSize, + bool _invalidatesStorage, bool _literalArguments, std::function)> _generateCode ) @@ -90,6 +92,7 @@ pair createFunction( f.sideEffectFree = _sideEffectFree; f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize; f.isMSize = false; + f.invalidatesStorage = _invalidatesStorage; f.instruction = {}; f.generateCode = std::move(_generateCode); return {name, f}; @@ -110,7 +113,7 @@ map createBuiltins(langutil::EVMVersion _evmVe if (_objectAccess) { - builtins.emplace(createFunction("datasize", 1, 1, true, true, true, true, []( + builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -131,7 +134,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataSize(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, true, []( + builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -152,7 +155,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataOffset(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, []( + builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, false, []( FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&, diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index 6d4eef27d..acfad416f 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -83,5 +83,6 @@ void WasmDialect::addFunction(string _name, size_t _params, size_t _returns) f.sideEffectFree = false; f.sideEffectFreeIfNoMSize = false; f.isMSize = false; + f.invalidatesStorage = true; f.literalArguments = false; } diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index e1ce12f02..db20537c8 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -26,21 +26,48 @@ #include #include #include +#include #include #include +#include using namespace std; using namespace dev; using namespace yul; + +void DataFlowAnalyzer::operator()(ExpressionStatement& _statement) +{ + if (boost::optional> vars = isSimpleSStore(_statement)) + { + ASTModifier::operator()(_statement); + m_storage.set(vars->first, vars->second); + set keysToErase; + for (auto const& item: m_storage.values) + if (!( + m_knowledgeBase.knownToBeDifferent(vars->first, item.first) || + m_knowledgeBase.knownToBeEqual(vars->second, item.second) + )) + keysToErase.insert(item.first); + for (YulString const& key: keysToErase) + m_storage.eraseKey(key); + } + else + { + clearStorageKnowledgeIfInvalidated(_statement.expression); + ASTModifier::operator()(_statement); + } +} + void DataFlowAnalyzer::operator()(Assignment& _assignment) { set names; for (auto const& var: _assignment.variableNames) names.emplace(var.name); assertThrow(_assignment.value, OptimizerException, ""); + clearStorageKnowledgeIfInvalidated(*_assignment.value); visit(*_assignment.value); handleAssignment(names, _assignment.value.get()); } @@ -53,15 +80,23 @@ void DataFlowAnalyzer::operator()(VariableDeclaration& _varDecl) m_variableScopes.back().variables += names; if (_varDecl.value) + { + clearStorageKnowledgeIfInvalidated(*_varDecl.value); visit(*_varDecl.value); + } handleAssignment(names, _varDecl.value.get()); } void DataFlowAnalyzer::operator()(If& _if) { + clearStorageKnowledgeIfInvalidated(*_if.condition); + InvertibleMap storage = m_storage; + ASTModifier::operator()(_if); + joinStorageKnowledge(storage); + Assignments assignments; assignments(_if.body); clearValues(assignments.names()); @@ -69,17 +104,24 @@ void DataFlowAnalyzer::operator()(If& _if) void DataFlowAnalyzer::operator()(Switch& _switch) { + clearStorageKnowledgeIfInvalidated(*_switch.expression); visit(*_switch.expression); set assignedVariables; for (auto& _case: _switch.cases) { + InvertibleMap storage = m_storage; (*this)(_case.body); + joinStorageKnowledge(storage); + Assignments assignments; assignments(_case.body); assignedVariables += assignments.names(); // This is a little too destructive, we could retain the old values. clearValues(assignments.names()); + clearStorageKnowledgeIfInvalidated(_case.body); } + for (auto& _case: _switch.cases) + clearStorageKnowledgeIfInvalidated(_case.body); clearValues(assignedVariables); } @@ -89,8 +131,10 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) // but this could be difficult if it is subclassed. map value; InvertibleRelation references; + InvertibleMap storage; m_value.swap(value); swap(m_references, references); + swap(m_storage, storage); pushScope(true); for (auto const& parameter: _fun.parameters) @@ -105,6 +149,7 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) popScope(); m_value.swap(value); swap(m_references, references); + swap(m_storage, storage); } void DataFlowAnalyzer::operator()(ForLoop& _for) @@ -121,11 +166,20 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) assignments(_for.post); clearValues(assignments.names()); + // break/continue are tricky for storage and thus we almost always clear here. + clearStorageKnowledgeIfInvalidated(*_for.condition); + clearStorageKnowledgeIfInvalidated(_for.post); + clearStorageKnowledgeIfInvalidated(_for.body); + visit(*_for.condition); (*this)(_for.body); clearValues(assignmentsSinceCont.names()); + clearStorageKnowledgeIfInvalidated(_for.body); (*this)(_for.post); clearValues(assignments.names()); + clearStorageKnowledgeIfInvalidated(*_for.condition); + clearStorageKnowledgeIfInvalidated(_for.post); + clearStorageKnowledgeIfInvalidated(_for.body); } void DataFlowAnalyzer::operator()(Block& _block) @@ -159,7 +213,13 @@ void DataFlowAnalyzer::handleAssignment(set const& _variables, Expres auto const& referencedVariables = movableChecker.referencedVariables(); for (auto const& name: _variables) + { m_references.set(name, referencedVariables); + // assignment to slot denoted by "name" + m_storage.eraseKey(name); + // assignment to slot contents denoted by "name" + m_storage.eraseValue(name); + } } void DataFlowAnalyzer::pushScope(bool _functionScope) @@ -188,7 +248,18 @@ void DataFlowAnalyzer::clearValues(set _variables) // This cannot be easily tested since the substitutions will be done // one by one on the fly, and the last line will just be add(1, 1) - // Clear variables that reference variables to be cleared. + // First clear storage knowledge, because we do not have to clear + // storage knowledge of variables whose expression has changed, + // since the value is still unchanged. + for (auto const& name: _variables) + { + // clear slot denoted by "name" + m_storage.eraseKey(name); + // clear slot contents denoted by "name" + m_storage.eraseValue(name); + } + + // Also clear variables that reference variables to be cleared. for (auto const& name: _variables) for (auto const& ref: m_references.backward[name]) _variables.emplace(ref); @@ -200,6 +271,31 @@ void DataFlowAnalyzer::clearValues(set _variables) m_references.eraseKey(name); } +void DataFlowAnalyzer::clearStorageKnowledgeIfInvalidated(Block const& _block) +{ + if (SideEffectsCollector(m_dialect, _block).invalidatesStorage()) + m_storage.clear(); +} + +void DataFlowAnalyzer::clearStorageKnowledgeIfInvalidated(Expression const& _expr) +{ + if (SideEffectsCollector(m_dialect, _expr).invalidatesStorage()) + m_storage.clear(); +} + +void DataFlowAnalyzer::joinStorageKnowledge(InvertibleMap const& _other) +{ + set keysToErase; + for (auto const& item: m_storage.values) + { + auto it = _other.values.find(item.first); + if (it == _other.values.end() || it->second != item.second) + keysToErase.insert(item.first); + } + for (auto const& key: keysToErase) + m_storage.eraseKey(key); +} + bool DataFlowAnalyzer::inScope(YulString _variableName) const { for (auto const& scope: m_variableScopes | boost::adaptors::reversed) @@ -211,3 +307,27 @@ bool DataFlowAnalyzer::inScope(YulString _variableName) const } return false; } + +boost::optional> DataFlowAnalyzer::isSimpleSStore( + ExpressionStatement const& _statement +) const +{ + if (_statement.expression.type() == typeid(FunctionCall)) + { + FunctionCall const& funCall = boost::get(_statement.expression); + if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) + if (auto const* builtin = dialect->builtin(funCall.functionName.name)) + if (builtin->instruction == dev::eth::Instruction::SSTORE) + if ( + funCall.arguments.at(0).type() == typeid(Identifier) && + funCall.arguments.at(1).type() == typeid(Identifier) + ) + { + YulString key = boost::get(funCall.arguments.at(0)).name; + YulString value = boost::get(funCall.arguments.at(1)).name; + return make_pair(key, value); + } + } + return {}; +} + diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 1b92d24ae..cd2a30e45 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -23,6 +23,7 @@ #pragma once #include +#include #include #include @@ -42,14 +43,34 @@ struct Dialect; * * A special zero constant expression is used for the default value of variables. * + * The class also tracks contents in storage and memory. Both keys and values + * are names of variables. Whenever such a variable is re-assigned, the knowledge + * is cleared. + * + * For elementary statements, we check if it is an SSTORE(x, y) / MSTORE(x, y) + * If yes, visit the statement. Then record that fact and clear all storage slots t + * where we cannot prove x != t or y == m_storage[t] using the current values of the variables x and t. + * Otherwise, determine if the statement invalidates storage/memory. If yes, clear all knowledge + * about storage/memory before visiting the statement. Then visit the statement. + * + * For forward-joining control flow, storage/memory information from the branches is combined. + * If the keys or values are different or non-existent in one branch, the key is deleted. + * This works also for memory (where addresses overlap) because one branch is always an + * older version of the other and thus overlapping contents would have been deleted already + * at the point of assignment. + * * Prerequisite: Disambiguator, ForLoopInitRewriter. */ class DataFlowAnalyzer: public ASTModifier { public: - explicit DataFlowAnalyzer(Dialect const& _dialect): m_dialect(_dialect) {} + explicit DataFlowAnalyzer(Dialect const& _dialect): + m_dialect(_dialect), + m_knowledgeBase(_dialect, m_value) + {} using ASTModifier::operator(); + void operator()(ExpressionStatement& _statement) override; void operator()(Assignment& _assignment) override; void operator()(VariableDeclaration& _varDecl) override; void operator()(If& _if) override; @@ -72,15 +93,31 @@ protected: /// for example at points where control flow is merged. void clearValues(std::set _names); + /// Clears knowledge about storage if storage may be modified inside the block. + void clearStorageKnowledgeIfInvalidated(Block const& _block); + + /// Clears knowledge about storage if storage may be modified inside the expression. + void clearStorageKnowledgeIfInvalidated(Expression const& _expression); + + void joinStorageKnowledge(InvertibleMap const& _other); + /// Returns true iff the variable is in scope. bool inScope(YulString _variableName) const; + boost::optional> isSimpleSStore(ExpressionStatement const& _statement) const; + + Dialect const& m_dialect; + /// Current values of variables, always movable. std::map m_value; /// m_references.forward[a].contains(b) <=> the current expression assigned to a references b /// m_references.backward[b].contains(a) <=> the current expression assigned to a references b InvertibleRelation m_references; + InvertibleMap m_storage; + + KnowledgeBase m_knowledgeBase; + struct Scope { explicit Scope(bool _isFunction): isFunction(_isFunction) {} @@ -92,7 +129,6 @@ protected: Expression const m_zero{Literal{{}, LiteralKind::Number, YulString{"0"}, {}}}; /// List of scopes. std::vector m_variableScopes; - Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/KnowledgeBase.cpp b/libyul/optimiser/KnowledgeBase.cpp new file mode 100644 index 000000000..e0efdb17b --- /dev/null +++ b/libyul/optimiser/KnowledgeBase.cpp @@ -0,0 +1,31 @@ +/* + 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 . +*/ +/** + * Class that can answer questions about values of variables and their relations. + */ + +#include + +using namespace yul; + +bool KnowledgeBase::knownToBeDifferent(YulString, YulString) const +{ + // TODO try to use the simplification rules together with the + // current values to turn `sub(_a, _b)` into a nonzero constant. + // If that fails, try `eq(_a, _b)`. + return false; +} diff --git a/libyul/optimiser/KnowledgeBase.h b/libyul/optimiser/KnowledgeBase.h new file mode 100644 index 000000000..369cee532 --- /dev/null +++ b/libyul/optimiser/KnowledgeBase.h @@ -0,0 +1,53 @@ +/* + 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 . +*/ +/** + * Class that can answer questions about values of variables and their relations. + */ + +#pragma once + +#include +#include +#include + +namespace yul +{ + +class Dialect; + +/** + * Class that can answer questions about values of variables and their relations. + * + * The reference to the map of values provided at construction is assumed to be updating. + */ +class KnowledgeBase +{ +public: + KnowledgeBase(Dialect const& _dialect, std::map const& _variableValues): + m_dialect(_dialect), + m_variableValues(_variableValues) + {} + + bool knownToBeDifferent(YulString _a, YulString _b) const; + bool knownToBeEqual(YulString _a, YulString _b) const { return _a == _b; } + +private: + Dialect const& m_dialect; + std::map const& m_variableValues; +}; + +} diff --git a/libyul/optimiser/SLoadResolver.cpp b/libyul/optimiser/SLoadResolver.cpp new file mode 100644 index 000000000..2dd243fb7 --- /dev/null +++ b/libyul/optimiser/SLoadResolver.cpp @@ -0,0 +1,48 @@ +/* + 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 . +*/ +/** + * Optimisation stage that replaces expressions of type ``sload(x)`` by the value + * currently stored in storage, if known. + */ + +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace yul; + +void SLoadResolver::visit(Expression& _e) +{ + if (_e.type() == typeid(FunctionCall)) + { + FunctionCall const& funCall = boost::get(_e); + if (auto const* builtin = dynamic_cast(m_dialect).builtin(funCall.functionName.name)) + if (builtin->instruction == dev::eth::Instruction::SLOAD) + if (funCall.arguments.at(0).type() == typeid(Identifier)) + { + YulString key = boost::get(funCall.arguments.at(0)).name; + if (m_storage.values.count(key)) + { + _e = Identifier{locationOf(_e), m_storage.values[key]}; + return; + } + } + } +} diff --git a/libyul/optimiser/SLoadResolver.h b/libyul/optimiser/SLoadResolver.h new file mode 100644 index 000000000..4d60f9389 --- /dev/null +++ b/libyul/optimiser/SLoadResolver.h @@ -0,0 +1,49 @@ +/* + 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 . +*/ +/** + * Optimisation stage that replaces expressions of type ``sload(x)`` by the value + * currently stored in storage, if known. + */ + +#pragma once + +#include + +namespace yul +{ + +struct EVMDialect; + +/** + * Optimisation stage that replaces expressions of type ``sload(x)`` by the value + * currently stored in storage, if known. + * + * Works best if the code is in SSA form. + * + * Prerequisite: Disambiguator, ForLoopInitRewriter. + */ +class SLoadResolver: public DataFlowAnalyzer +{ +public: + SLoadResolver(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {} + +protected: + using ASTModifier::visit; + void visit(Expression& _e) override; +}; + +} diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index f9e9e8338..93122af44 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -63,6 +63,8 @@ void SideEffectsCollector::operator()(FunctionalInstruction const& _instr) m_sideEffectFreeIfNoMSize = false; if (_instr.instruction == eth::Instruction::MSIZE) m_containsMSize = true; + if (eth::SemanticInformation::invalidatesStorage(_instr.instruction)) + m_invalidatesStorage = true; } void SideEffectsCollector::operator()(FunctionCall const& _functionCall) @@ -79,12 +81,15 @@ void SideEffectsCollector::operator()(FunctionCall const& _functionCall) m_sideEffectFreeIfNoMSize = false; if (f->isMSize) m_containsMSize = true; + if (f->invalidatesStorage) + m_invalidatesStorage = true; } else { m_movable = false; m_sideEffectFree = false; m_sideEffectFreeIfNoMSize = false; + m_invalidatesStorage = true; } } diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 6c36ca0d4..f388bef2c 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -54,6 +54,7 @@ public: } bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; } bool containsMSize() const { return m_containsMSize; } + bool invalidatesStorage() const { return m_invalidatesStorage; } private: Dialect const& m_dialect; @@ -69,6 +70,9 @@ private: /// Note that this is a purely syntactic property meaning that even if this is false, /// the code can still contain calls to functions that contain the msize instruction. bool m_containsMSize = false; + /// If false, storage is guaranteed to be unchanged by the coded under all + /// circumstances. + bool m_invalidatesStorage = false; }; /** @@ -88,6 +92,7 @@ public: void visit(Statement const&) override; using ASTWalker::visit; + std::set const& referencedVariables() const { return m_variableReferences; } private: diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index b5f397d17..25022b501 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -252,6 +253,21 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line SSATransform::run(*m_ast, nameDispenser); RedundantAssignEliminator::run(*m_dialect, *m_ast); } + else if (m_optimizerStep == "sloadResolver") + { + disambiguate(); + ForLoopInitRewriter{}(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); + CommonSubexpressionEliminator{*m_dialect}(*m_ast); + ExpressionSimplifier::run(*m_dialect, *m_ast); + + SLoadResolver{*m_dialect}(*m_ast); + + UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); + ExpressionJoiner::run(*m_ast); + ExpressionJoiner::run(*m_ast); + } else if (m_optimizerStep == "controlFlowSimplifier") { disambiguate(); diff --git a/test/libyul/yulOptimizerTests/fullSuite/storage.yul b/test/libyul/yulOptimizerTests/fullSuite/storage.yul new file mode 100644 index 000000000..9867345fa --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/storage.yul @@ -0,0 +1,15 @@ +{ + sstore(4, 5) + sstore(4, 3) + sstore(8, sload(4)) +} +// ==== +// step: fullSuite +// ---- +// { +// { +// sstore(4, 5) +// sstore(4, 3) +// sstore(8, sload(4)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/reassign.yul b/test/libyul/yulOptimizerTests/sloadResolver/reassign.yul new file mode 100644 index 000000000..51db34f53 --- /dev/null +++ b/test/libyul/yulOptimizerTests/sloadResolver/reassign.yul @@ -0,0 +1,16 @@ +{ + let a := calldataload(0) + sstore(a, 6) + a := calldataload(2) + mstore(0, sload(a)) +} +// ==== +// step: sloadResolver +// ---- +// { +// let _1 := 0 +// let a := calldataload(_1) +// sstore(a, 6) +// a := calldataload(2) +// mstore(_1, sload(a)) +// } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/reassign_value_expression.yul b/test/libyul/yulOptimizerTests/sloadResolver/reassign_value_expression.yul new file mode 100644 index 000000000..1f05da777 --- /dev/null +++ b/test/libyul/yulOptimizerTests/sloadResolver/reassign_value_expression.yul @@ -0,0 +1,32 @@ +{ + let x := calldataload(1) + let a := add(x, 10) + sstore(a, 7) + // This clears the expression assigned to ``a`` but + // should not clear storage knowledge + x := 9 + mstore(sload(a), 11) + // This, on the other hand, actually clears knowledge + a := 33 + mstore(sload(a), 11) + // Try again with different expression to avoid + // clearing because we cannot know if it is different + a := 39 + mstore(sload(a), 11) +} +// ==== +// step: sloadResolver +// ---- +// { +// let x := calldataload(1) +// let a := add(x, 10) +// let _3 := 7 +// sstore(a, _3) +// x := 9 +// let _4 := 11 +// mstore(_3, _4) +// a := 33 +// mstore(sload(a), _4) +// a := 39 +// mstore(sload(a), _4) +// } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/second_store.yul b/test/libyul/yulOptimizerTests/sloadResolver/second_store.yul new file mode 100644 index 000000000..b9823e29b --- /dev/null +++ b/test/libyul/yulOptimizerTests/sloadResolver/second_store.yul @@ -0,0 +1,19 @@ +{ + let x := calldataload(1) + sstore(x, 7) + sstore(calldataload(0), 6) + // We cannot replace this because we do not know + // if the two slots are different. + mstore(0, sload(x)) +} +// ==== +// step: sloadResolver +// ---- +// { +// let x := calldataload(1) +// sstore(x, 7) +// let _3 := 6 +// let _4 := 0 +// sstore(calldataload(_4), _3) +// mstore(_4, sload(x)) +// } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/second_store_same_value.yul b/test/libyul/yulOptimizerTests/sloadResolver/second_store_same_value.yul new file mode 100644 index 000000000..ef59ec8d0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/sloadResolver/second_store_same_value.yul @@ -0,0 +1,19 @@ +{ + let x := calldataload(1) + sstore(x, 7) + sstore(calldataload(0), 7) + // We can replace this because both values that were + // written are 7. + mstore(0, sload(x)) +} +// ==== +// step: sloadResolver +// ---- +// { +// let x := calldataload(1) +// let _2 := 7 +// sstore(x, _2) +// let _4 := 0 +// sstore(calldataload(_4), _2) +// mstore(_4, _2) +// } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/simple.yul b/test/libyul/yulOptimizerTests/sloadResolver/simple.yul new file mode 100644 index 000000000..3bd07d488 --- /dev/null +++ b/test/libyul/yulOptimizerTests/sloadResolver/simple.yul @@ -0,0 +1,14 @@ +{ + sstore(calldataload(0), calldataload(10)) + let t := sload(calldataload(10)) + let q := sload(calldataload(0)) + mstore(t, q) +} +// ==== +// step: sloadResolver +// ---- +// { +// let _2 := calldataload(10) +// sstore(calldataload(0), _2) +// mstore(sload(_2), _2) +// } From 85726004013a07b6771abc2797bf3a9ef1c22b33 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 21 May 2019 18:38:05 +0200 Subject: [PATCH 097/115] Implement knowledge base. --- libyul/optimiser/KnowledgeBase.cpp | 63 ++++++++++++++++++- libyul/optimiser/KnowledgeBase.h | 8 ++- .../sloadResolver/second_store_with_delta.yul | 24 +++++++ 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 test/libyul/yulOptimizerTests/sloadResolver/second_store_with_delta.yul diff --git a/libyul/optimiser/KnowledgeBase.cpp b/libyul/optimiser/KnowledgeBase.cpp index e0efdb17b..2c13ac05b 100644 --- a/libyul/optimiser/KnowledgeBase.cpp +++ b/libyul/optimiser/KnowledgeBase.cpp @@ -20,12 +20,69 @@ #include -using namespace yul; +#include +#include +#include +#include -bool KnowledgeBase::knownToBeDifferent(YulString, YulString) const +#include + +using namespace yul; +using namespace dev; + +bool KnowledgeBase::knownToBeDifferent(YulString _a, YulString _b) { - // TODO try to use the simplification rules together with the + // Try to use the simplification rules together with the // current values to turn `sub(_a, _b)` into a nonzero constant. // If that fails, try `eq(_a, _b)`. + + Expression expr1 = simplify(FunctionCall{{}, {{}, "sub"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); + if (expr1.type() == typeid(Literal)) + return valueOfLiteral(boost::get(expr1)) != 0; + + Expression expr2 = simplify(FunctionCall{{}, {{}, "eq"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); + if (expr2.type() == typeid(Literal)) + return valueOfLiteral(boost::get(expr2)) == 0; + return false; } + +bool KnowledgeBase::knownToBeDifferentByAtLeast32(YulString _a, YulString _b) +{ + // Try to use the simplification rules together with the + // current values to turn `sub(_a, _b)` into a constant whose absolute value is at least 32. + + Expression expr1 = simplify(FunctionCall{{}, {{}, "sub"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); + if (expr1.type() == typeid(Literal)) + { + u256 val = valueOfLiteral(boost::get(expr1)); + return val >= 32 && val <= u256(0) - 32; + } + + return false; +} + +Expression KnowledgeBase::simplify(Expression _expression) +{ + bool startedRecursion = (m_recursionCounter == 0); + dev::ScopeGuard{[&] { if (startedRecursion) m_recursionCounter = 0; }}; + + if (startedRecursion) + m_recursionCounter = 100; + else if (m_recursionCounter == 1) + return _expression; + else + --m_recursionCounter; + + if (_expression.type() == typeid(FunctionCall)) + for (Expression& arg: boost::get(_expression).arguments) + arg = simplify(arg); + else if (_expression.type() == typeid(FunctionalInstruction)) + for (Expression& arg: boost::get(_expression).arguments) + arg = simplify(arg); + + if (auto match = SimplificationRules::findFirstMatch(_expression, m_dialect, m_variableValues)) + return simplify(match->action().toExpression(locationOf(_expression))); + + return _expression; +} diff --git a/libyul/optimiser/KnowledgeBase.h b/libyul/optimiser/KnowledgeBase.h index 369cee532..35e2d40ee 100644 --- a/libyul/optimiser/KnowledgeBase.h +++ b/libyul/optimiser/KnowledgeBase.h @@ -27,7 +27,7 @@ namespace yul { -class Dialect; +struct Dialect; /** * Class that can answer questions about values of variables and their relations. @@ -42,12 +42,16 @@ public: m_variableValues(_variableValues) {} - bool knownToBeDifferent(YulString _a, YulString _b) const; + bool knownToBeDifferent(YulString _a, YulString _b); + bool knownToBeDifferentByAtLeast32(YulString _a, YulString _b); bool knownToBeEqual(YulString _a, YulString _b) const { return _a == _b; } private: + Expression simplify(Expression _expression); + Dialect const& m_dialect; std::map const& m_variableValues; + size_t m_recursionCounter = 0; }; } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/second_store_with_delta.yul b/test/libyul/yulOptimizerTests/sloadResolver/second_store_with_delta.yul new file mode 100644 index 000000000..5d50185ae --- /dev/null +++ b/test/libyul/yulOptimizerTests/sloadResolver/second_store_with_delta.yul @@ -0,0 +1,24 @@ +{ + let x := calldataload(1) + let a := add(x, 10) + let b := add(x, 20) + sstore(a, 7) + // does not invalidate the first store, because the + // difference is a constant, even if the absolute + // values are unknown + sstore(b, 8) + mstore(sload(a), sload(b)) +} +// ==== +// step: sloadResolver +// ---- +// { +// let x := calldataload(1) +// let a := add(x, 10) +// let b := add(x, 20) +// let _4 := 7 +// sstore(a, _4) +// let _5 := 8 +// sstore(b, _5) +// mstore(_4, _5) +// } From 2b979cba38ce094fe833bc6b3526c76e86ea7172 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 28 May 2019 00:14:01 +0200 Subject: [PATCH 098/115] Also optimize memory. --- libyul/CMakeLists.txt | 4 +- libyul/Dialect.h | 3 + libyul/backends/evm/EVMDialect.cpp | 9 +- libyul/backends/wasm/WasmDialect.cpp | 1 + libyul/optimiser/DataFlowAnalyzer.cpp | 110 +++++++++++++----- libyul/optimiser/DataFlowAnalyzer.h | 30 ++++- .../{SLoadResolver.cpp => LoadResolver.cpp} | 38 ++++-- .../{SLoadResolver.h => LoadResolver.h} | 16 ++- libyul/optimiser/Semantics.cpp | 5 + libyul/optimiser/Semantics.h | 3 +- test/libyul/YulOptimizerTest.cpp | 6 +- ...y_with_different_kinds_of_invalidation.yul | 38 ++++++ .../loadResolver/memory_with_msize.yul | 17 +++ .../loadResolver/merge_known_write.yul | 22 ++++ .../merge_known_write_with_distance.yul | 20 ++++ .../loadResolver/merge_unknown_write.yul | 22 ++++ .../reassign.yul | 2 +- .../reassign_value_expression.yul | 2 +- .../loadResolver/second_mstore_with_delta.yul | 24 ++++ .../second_store.yul | 2 +- .../second_store_same_value.yul | 2 +- .../second_store_with_delta.yul | 2 +- .../simple.yul | 2 +- .../loadResolver/simple_memory.yul | 14 +++ 24 files changed, 331 insertions(+), 63 deletions(-) rename libyul/optimiser/{SLoadResolver.cpp => LoadResolver.cpp} (56%) rename libyul/optimiser/{SLoadResolver.h => LoadResolver.h} (76%) create mode 100644 test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/memory_with_msize.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/merge_known_write.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/merge_known_write_with_distance.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/merge_unknown_write.yul rename test/libyul/yulOptimizerTests/{sloadResolver => loadResolver}/reassign.yul (91%) rename test/libyul/yulOptimizerTests/{sloadResolver => loadResolver}/reassign_value_expression.yul (96%) create mode 100644 test/libyul/yulOptimizerTests/loadResolver/second_mstore_with_delta.yul rename test/libyul/yulOptimizerTests/{sloadResolver => loadResolver}/second_store.yul (94%) rename test/libyul/yulOptimizerTests/{sloadResolver => loadResolver}/second_store_same_value.yul (93%) rename test/libyul/yulOptimizerTests/{sloadResolver => loadResolver}/second_store_with_delta.yul (95%) rename test/libyul/yulOptimizerTests/{sloadResolver => loadResolver}/simple.yul (91%) create mode 100644 test/libyul/yulOptimizerTests/loadResolver/simple_memory.yul diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index e11f5dc5f..b0c226f45 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -96,6 +96,8 @@ add_library(yul optimiser/InlinableExpressionFunctionFinder.h optimiser/KnowledgeBase.cpp optimiser/KnowledgeBase.h + optimiser/LoadResolver.cpp + optimiser/LoadResolver.h optimiser/MainFunction.cpp optimiser/MainFunction.h optimiser/Metrics.cpp @@ -111,8 +113,6 @@ add_library(yul optimiser/RedundantAssignEliminator.cpp optimiser/RedundantAssignEliminator.h optimiser/Rematerialiser.cpp - optimiser/SLoadResolver.cpp - optimiser/SLoadResolver.h optimiser/Rematerialiser.h optimiser/SSAReverser.cpp optimiser/SSAReverser.h diff --git a/libyul/Dialect.h b/libyul/Dialect.h index feffc5890..bf1a352e1 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -60,6 +60,9 @@ struct BuiltinFunction /// If false, storage of the current contract before and after the function is the same /// under every circumstance. If the function does not return, this can be false. bool invalidatesStorage = true; + /// If false, memory before and after the function is the same under every circumstance. + /// If the function does not return, this can be false. + bool invalidatesMemory = true; /// If true, can only accept literals as arguments and they cannot be moved to variables. bool literalArguments = false; }; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index ba5163689..b4a7cbd49 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -55,6 +55,7 @@ pair createEVMFunction( f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction); f.isMSize = _instruction == dev::eth::Instruction::MSIZE; f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction); + f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction); f.literalArguments = false; f.instruction = _instruction; f.generateCode = [_instruction]( @@ -78,6 +79,7 @@ pair createFunction( bool _sideEffectFree, bool _sideEffectFreeIfNoMSize, bool _invalidatesStorage, + bool _invalidatesMemory, bool _literalArguments, std::function)> _generateCode ) @@ -93,6 +95,7 @@ pair createFunction( f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize; f.isMSize = false; f.invalidatesStorage = _invalidatesStorage; + f.invalidatesMemory = _invalidatesMemory; f.instruction = {}; f.generateCode = std::move(_generateCode); return {name, f}; @@ -113,7 +116,7 @@ map createBuiltins(langutil::EVMVersion _evmVe if (_objectAccess) { - builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, true, []( + builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, false, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -134,7 +137,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataSize(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, true, []( + builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, false, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -155,7 +158,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataOffset(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, false, []( + builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, []( FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&, diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index acfad416f..5b76a4af9 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -84,5 +84,6 @@ void WasmDialect::addFunction(string _name, size_t _params, size_t _returns) f.sideEffectFreeIfNoMSize = false; f.isMSize = false; f.invalidatesStorage = true; + f.invalidatesMemory = true; f.literalArguments = false; } diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index db20537c8..a927a7eca 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -40,7 +40,7 @@ using namespace yul; void DataFlowAnalyzer::operator()(ExpressionStatement& _statement) { - if (boost::optional> vars = isSimpleSStore(_statement)) + if (auto vars = isSimpleStore(dev::eth::Instruction::SSTORE, _statement)) { ASTModifier::operator()(_statement); m_storage.set(vars->first, vars->second); @@ -54,9 +54,22 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement) for (YulString const& key: keysToErase) m_storage.eraseKey(key); } + else if (auto vars = isSimpleStore(dev::eth::Instruction::MSTORE, _statement)) + { + ASTModifier::operator()(_statement); + set keysToErase; + for (auto const& item: m_memory.values) + if (!m_knowledgeBase.knownToBeDifferentByAtLeast32(vars->first, item.first)) + keysToErase.insert(item.first); + // TODO is it fine to do that here? + // can we also move the storage above? + m_memory.set(vars->first, vars->second); + for (YulString const& key: keysToErase) + m_memory.eraseKey(key); + } else { - clearStorageKnowledgeIfInvalidated(_statement.expression); + clearKnowledgeIfInvalidated(_statement.expression); ASTModifier::operator()(_statement); } } @@ -67,7 +80,7 @@ void DataFlowAnalyzer::operator()(Assignment& _assignment) for (auto const& var: _assignment.variableNames) names.emplace(var.name); assertThrow(_assignment.value, OptimizerException, ""); - clearStorageKnowledgeIfInvalidated(*_assignment.value); + clearKnowledgeIfInvalidated(*_assignment.value); visit(*_assignment.value); handleAssignment(names, _assignment.value.get()); } @@ -81,7 +94,7 @@ void DataFlowAnalyzer::operator()(VariableDeclaration& _varDecl) if (_varDecl.value) { - clearStorageKnowledgeIfInvalidated(*_varDecl.value); + clearKnowledgeIfInvalidated(*_varDecl.value); visit(*_varDecl.value); } @@ -90,12 +103,13 @@ void DataFlowAnalyzer::operator()(VariableDeclaration& _varDecl) void DataFlowAnalyzer::operator()(If& _if) { - clearStorageKnowledgeIfInvalidated(*_if.condition); + clearKnowledgeIfInvalidated(*_if.condition); InvertibleMap storage = m_storage; + InvertibleMap memory = m_memory; ASTModifier::operator()(_if); - joinStorageKnowledge(storage); + joinKnowledge(storage, memory); Assignments assignments; assignments(_if.body); @@ -104,24 +118,25 @@ void DataFlowAnalyzer::operator()(If& _if) void DataFlowAnalyzer::operator()(Switch& _switch) { - clearStorageKnowledgeIfInvalidated(*_switch.expression); + clearKnowledgeIfInvalidated(*_switch.expression); visit(*_switch.expression); set assignedVariables; for (auto& _case: _switch.cases) { InvertibleMap storage = m_storage; + InvertibleMap memory = m_memory; (*this)(_case.body); - joinStorageKnowledge(storage); + joinKnowledge(storage, memory); Assignments assignments; assignments(_case.body); assignedVariables += assignments.names(); // This is a little too destructive, we could retain the old values. clearValues(assignments.names()); - clearStorageKnowledgeIfInvalidated(_case.body); + clearKnowledgeIfInvalidated(_case.body); } for (auto& _case: _switch.cases) - clearStorageKnowledgeIfInvalidated(_case.body); + clearKnowledgeIfInvalidated(_case.body); clearValues(assignedVariables); } @@ -132,9 +147,11 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) map value; InvertibleRelation references; InvertibleMap storage; + InvertibleMap memory; m_value.swap(value); swap(m_references, references); swap(m_storage, storage); + swap(m_memory, memory); pushScope(true); for (auto const& parameter: _fun.parameters) @@ -150,6 +167,7 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) m_value.swap(value); swap(m_references, references); swap(m_storage, storage); + swap(m_memory, memory); } void DataFlowAnalyzer::operator()(ForLoop& _for) @@ -167,19 +185,19 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) clearValues(assignments.names()); // break/continue are tricky for storage and thus we almost always clear here. - clearStorageKnowledgeIfInvalidated(*_for.condition); - clearStorageKnowledgeIfInvalidated(_for.post); - clearStorageKnowledgeIfInvalidated(_for.body); + clearKnowledgeIfInvalidated(*_for.condition); + clearKnowledgeIfInvalidated(_for.post); + clearKnowledgeIfInvalidated(_for.body); visit(*_for.condition); (*this)(_for.body); clearValues(assignmentsSinceCont.names()); - clearStorageKnowledgeIfInvalidated(_for.body); + clearKnowledgeIfInvalidated(_for.body); (*this)(_for.post); clearValues(assignments.names()); - clearStorageKnowledgeIfInvalidated(*_for.condition); - clearStorageKnowledgeIfInvalidated(_for.post); - clearStorageKnowledgeIfInvalidated(_for.body); + clearKnowledgeIfInvalidated(*_for.condition); + clearKnowledgeIfInvalidated(_for.post); + clearKnowledgeIfInvalidated(_for.body); } void DataFlowAnalyzer::operator()(Block& _block) @@ -219,6 +237,10 @@ void DataFlowAnalyzer::handleAssignment(set const& _variables, Expres m_storage.eraseKey(name); // assignment to slot contents denoted by "name" m_storage.eraseValue(name); + // assignment to slot denoted by "name" + m_memory.eraseKey(name); + // assignment to slot contents denoted by "name" + m_memory.eraseValue(name); } } @@ -257,6 +279,10 @@ void DataFlowAnalyzer::clearValues(set _variables) m_storage.eraseKey(name); // clear slot contents denoted by "name" m_storage.eraseValue(name); + // assignment to slot denoted by "name" + m_memory.eraseKey(name); + // assignment to slot contents denoted by "name" + m_memory.eraseValue(name); } // Also clear variables that reference variables to be cleared. @@ -271,29 +297,51 @@ void DataFlowAnalyzer::clearValues(set _variables) m_references.eraseKey(name); } -void DataFlowAnalyzer::clearStorageKnowledgeIfInvalidated(Block const& _block) +void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block) { - if (SideEffectsCollector(m_dialect, _block).invalidatesStorage()) + SideEffectsCollector sideEffects(m_dialect, _block); + if (sideEffects.invalidatesStorage()) m_storage.clear(); + if (sideEffects.invalidatesMemory()) + m_memory.clear(); } -void DataFlowAnalyzer::clearStorageKnowledgeIfInvalidated(Expression const& _expr) +void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Expression const& _expr) { - if (SideEffectsCollector(m_dialect, _expr).invalidatesStorage()) + SideEffectsCollector sideEffects(m_dialect, _expr); + if (sideEffects.invalidatesStorage()) m_storage.clear(); + if (sideEffects.invalidatesMemory()) + m_memory.clear(); } -void DataFlowAnalyzer::joinStorageKnowledge(InvertibleMap const& _other) +void DataFlowAnalyzer::joinKnowledge( + InvertibleMap const& _olderStorage, + InvertibleMap const& _olderMemory +) { + joinKnowledgeHelper(m_storage, _olderStorage); + joinKnowledgeHelper(m_memory, _olderMemory); +} + +void DataFlowAnalyzer::joinKnowledgeHelper( + InvertibleMap& _this, + InvertibleMap const& _older +) +{ + // We clear if the key does not exist in the older map or if the value is different. + // This also works for memory because _older is an "older version" + // of m_memory and thus any overlapping write would have cleared the keys + // that are not known to be different inside m_memory already. set keysToErase; - for (auto const& item: m_storage.values) + for (auto const& item: _this.values) { - auto it = _other.values.find(item.first); - if (it == _other.values.end() || it->second != item.second) + auto it = _older.values.find(item.first); + if (it == _older.values.end() || it->second != item.second) keysToErase.insert(item.first); } for (auto const& key: keysToErase) - m_storage.eraseKey(key); + _this.eraseKey(key); } bool DataFlowAnalyzer::inScope(YulString _variableName) const @@ -308,16 +356,22 @@ bool DataFlowAnalyzer::inScope(YulString _variableName) const return false; } -boost::optional> DataFlowAnalyzer::isSimpleSStore( +boost::optional> DataFlowAnalyzer::isSimpleStore( + dev::eth::Instruction _store, ExpressionStatement const& _statement ) const { + yulAssert( + _store == dev::eth::Instruction::MSTORE || + _store == dev::eth::Instruction::SSTORE, + "" + ); if (_statement.expression.type() == typeid(FunctionCall)) { FunctionCall const& funCall = boost::get(_statement.expression); if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) if (auto const* builtin = dialect->builtin(funCall.functionName.name)) - if (builtin->instruction == dev::eth::Instruction::SSTORE) + if (builtin->instruction == _store) if ( funCall.arguments.at(0).type() == typeid(Identifier) && funCall.arguments.at(1).type() == typeid(Identifier) diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index cd2a30e45..5df45dcb8 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -27,6 +27,9 @@ #include #include +// TODO avoid +#include + #include #include @@ -93,18 +96,32 @@ protected: /// for example at points where control flow is merged. void clearValues(std::set _names); - /// Clears knowledge about storage if storage may be modified inside the block. - void clearStorageKnowledgeIfInvalidated(Block const& _block); + /// Clears knowledge about storage or memory if they may be modified inside the block. + void clearKnowledgeIfInvalidated(Block const& _block); - /// Clears knowledge about storage if storage may be modified inside the expression. - void clearStorageKnowledgeIfInvalidated(Expression const& _expression); + /// Clears knowledge about storage or memory if they may be modified inside the expression. + void clearKnowledgeIfInvalidated(Expression const& _expression); - void joinStorageKnowledge(InvertibleMap const& _other); + /// Joins knowledge about storage and memory with an older point in the control-flow. + /// This only works if the current state is a direct successor of the older point, + /// i.e. `_otherStorage` and `_otherMemory` cannot have additional changes. + void joinKnowledge( + InvertibleMap const& _olderStorage, + InvertibleMap const& _olderMemory + ); + + static void joinKnowledgeHelper( + InvertibleMap& _thisData, + InvertibleMap const& _olderData + ); /// Returns true iff the variable is in scope. bool inScope(YulString _variableName) const; - boost::optional> isSimpleSStore(ExpressionStatement const& _statement) const; + boost::optional> isSimpleStore( + dev::eth::Instruction _store, + ExpressionStatement const& _statement + ) const; Dialect const& m_dialect; @@ -115,6 +132,7 @@ protected: InvertibleRelation m_references; InvertibleMap m_storage; + InvertibleMap m_memory; KnowledgeBase m_knowledgeBase; diff --git a/libyul/optimiser/SLoadResolver.cpp b/libyul/optimiser/LoadResolver.cpp similarity index 56% rename from libyul/optimiser/SLoadResolver.cpp rename to libyul/optimiser/LoadResolver.cpp index 2dd243fb7..b33963e94 100644 --- a/libyul/optimiser/SLoadResolver.cpp +++ b/libyul/optimiser/LoadResolver.cpp @@ -19,30 +19,48 @@ * currently stored in storage, if known. */ -#include +#include #include +#include #include using namespace std; using namespace dev; using namespace yul; -void SLoadResolver::visit(Expression& _e) +void LoadResolver::run(Dialect const& _dialect, Block& _ast) +{ + bool containsMSize = SideEffectsCollector(_dialect, _ast).containsMSize(); + LoadResolver{_dialect, !containsMSize}(_ast); +} + +void LoadResolver::visit(Expression& _e) { if (_e.type() == typeid(FunctionCall)) { FunctionCall const& funCall = boost::get(_e); if (auto const* builtin = dynamic_cast(m_dialect).builtin(funCall.functionName.name)) - if (builtin->instruction == dev::eth::Instruction::SLOAD) - if (funCall.arguments.at(0).type() == typeid(Identifier)) + if (!builtin->parameters.empty() && funCall.arguments.at(0).type() == typeid(Identifier)) + { + YulString key = boost::get(funCall.arguments.at(0)).name; + if ( + builtin->instruction == dev::eth::Instruction::SLOAD && + m_storage.values.count(key) + ) { - YulString key = boost::get(funCall.arguments.at(0)).name; - if (m_storage.values.count(key)) - { - _e = Identifier{locationOf(_e), m_storage.values[key]}; - return; - } + _e = Identifier{locationOf(_e), m_storage.values[key]}; + return; } + else if ( + m_optimizeMLoad && + builtin->instruction == dev::eth::Instruction::MLOAD && + m_memory.values.count(key) + ) + { + _e = Identifier{locationOf(_e), m_memory.values[key]}; + return; + } + } } } diff --git a/libyul/optimiser/SLoadResolver.h b/libyul/optimiser/LoadResolver.h similarity index 76% rename from libyul/optimiser/SLoadResolver.h rename to libyul/optimiser/LoadResolver.h index 4d60f9389..3db124594 100644 --- a/libyul/optimiser/SLoadResolver.h +++ b/libyul/optimiser/LoadResolver.h @@ -29,21 +29,29 @@ namespace yul struct EVMDialect; /** - * Optimisation stage that replaces expressions of type ``sload(x)`` by the value - * currently stored in storage, if known. + * Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value + * currently stored in storage resp. memory, if known. * * Works best if the code is in SSA form. * * Prerequisite: Disambiguator, ForLoopInitRewriter. */ -class SLoadResolver: public DataFlowAnalyzer +class LoadResolver: public DataFlowAnalyzer { public: - SLoadResolver(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {} + static void run(Dialect const& _dialect, Block& _ast); + +private: + LoadResolver(Dialect const& _dialect, bool _optimizeMLoad): + DataFlowAnalyzer(_dialect), + m_optimizeMLoad(_optimizeMLoad) + {} protected: using ASTModifier::visit; void visit(Expression& _e) override; + + bool m_optimizeMLoad = false; }; } diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 93122af44..4426c63a8 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -65,6 +65,8 @@ void SideEffectsCollector::operator()(FunctionalInstruction const& _instr) m_containsMSize = true; if (eth::SemanticInformation::invalidatesStorage(_instr.instruction)) m_invalidatesStorage = true; + if (eth::SemanticInformation::invalidatesMemory(_instr.instruction)) + m_invalidatesMemory = true; } void SideEffectsCollector::operator()(FunctionCall const& _functionCall) @@ -83,6 +85,8 @@ void SideEffectsCollector::operator()(FunctionCall const& _functionCall) m_containsMSize = true; if (f->invalidatesStorage) m_invalidatesStorage = true; + if (f->invalidatesMemory) + m_invalidatesMemory = true; } else { @@ -90,6 +94,7 @@ void SideEffectsCollector::operator()(FunctionCall const& _functionCall) m_sideEffectFree = false; m_sideEffectFreeIfNoMSize = false; m_invalidatesStorage = true; + m_invalidatesMemory = true; } } diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index f388bef2c..4dcec2e5e 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -55,6 +55,7 @@ public: bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; } bool containsMSize() const { return m_containsMSize; } bool invalidatesStorage() const { return m_invalidatesStorage; } + bool invalidatesMemory() const { return m_invalidatesMemory; } private: Dialect const& m_dialect; @@ -73,6 +74,7 @@ private: /// If false, storage is guaranteed to be unchanged by the coded under all /// circumstances. bool m_invalidatesStorage = false; + bool m_invalidatesMemory = false; }; /** @@ -92,7 +94,6 @@ public: void visit(Statement const&) override; using ASTWalker::visit; - std::set const& referencedVariables() const { return m_variableReferences; } private: diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 25022b501..2fd9a5ef4 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,6 @@ #include #include #include -#include #include #include #include @@ -253,7 +253,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line SSATransform::run(*m_ast, nameDispenser); RedundantAssignEliminator::run(*m_dialect, *m_ast); } - else if (m_optimizerStep == "sloadResolver") + else if (m_optimizerStep == "loadResolver") { disambiguate(); ForLoopInitRewriter{}(*m_ast); @@ -262,7 +262,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line CommonSubexpressionEliminator{*m_dialect}(*m_ast); ExpressionSimplifier::run(*m_dialect, *m_ast); - SLoadResolver{*m_dialect}(*m_ast); + LoadResolver::run(*m_dialect, *m_ast); UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); ExpressionJoiner::run(*m_ast); diff --git a/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul b/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul new file mode 100644 index 000000000..0bd777cf8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul @@ -0,0 +1,38 @@ +{ + mstore(2, 9) + sstore(0, mload(2)) + pop(call(0, 0, 0, 0, 0, 0, 0)) + sstore(0, mload(2)) + + mstore(2, 10) + mstore8(calldataload(0), 4) + sstore(0, mload(2)) + + mstore(2, 10) + g() + sstore(0, mload(2)) + + function g() {} +} +// ==== +// step: loadResolver +// ---- +// { +// let _1 := 9 +// let _2 := 2 +// mstore(_2, _1) +// let _4 := _1 +// let _5 := 0 +// sstore(_5, _4) +// pop(call(_5, _5, _5, _5, _5, _5, _5)) +// sstore(_5, mload(_2)) +// let _17 := 10 +// mstore(_2, _17) +// mstore8(calldataload(_5), 4) +// sstore(_5, mload(_2)) +// mstore(_2, _17) +// g() +// sstore(_5, mload(_2)) +// function g() +// { } +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/memory_with_msize.yul b/test/libyul/yulOptimizerTests/loadResolver/memory_with_msize.yul new file mode 100644 index 000000000..17a8761d9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/memory_with_msize.yul @@ -0,0 +1,17 @@ +{ + // No mload removal because of msize + mstore(calldataload(0), msize()) + let t := mload(calldataload(10)) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _1 := msize() +// let _3 := calldataload(0) +// mstore(_3, _1) +// let t := mload(calldataload(10)) +// sstore(t, mload(_3)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_known_write.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write.yul new file mode 100644 index 000000000..7f5dfb2f0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write.yul @@ -0,0 +1,22 @@ +{ + mstore(calldataload(0), calldataload(10)) + if calldataload(1) { + mstore(calldataload(0), 1) + } + let t := mload(0) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// let _3 := 0 +// let _4 := calldataload(_3) +// mstore(_4, _2) +// let _5 := 1 +// if calldataload(_5) { mstore(_4, _5) } +// let t := mload(_3) +// sstore(t, mload(_4)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_known_write_with_distance.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write_with_distance.yul new file mode 100644 index 000000000..f7cfdd7c3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write_with_distance.yul @@ -0,0 +1,20 @@ +{ + mstore(calldataload(0), calldataload(10)) + if calldataload(1) { + mstore(add(calldataload(0), 0x20), 1) + } + let t := mload(add(calldataload(0), 0x20)) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// let _4 := calldataload(0) +// mstore(_4, _2) +// let _5 := 1 +// if calldataload(_5) { mstore(add(_4, 0x20), _5) } +// sstore(mload(add(_4, 0x20)), _2) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_unknown_write.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_unknown_write.yul new file mode 100644 index 000000000..4fc21bbf5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_unknown_write.yul @@ -0,0 +1,22 @@ +{ + mstore(calldataload(0), calldataload(10)) + if calldataload(1) { + mstore(0, 1) + } + let t := mload(0) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// let _3 := 0 +// let _4 := calldataload(_3) +// mstore(_4, _2) +// let _5 := 1 +// if calldataload(_5) { mstore(_3, _5) } +// let t := mload(_3) +// sstore(t, mload(_4)) +// } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/reassign.yul b/test/libyul/yulOptimizerTests/loadResolver/reassign.yul similarity index 91% rename from test/libyul/yulOptimizerTests/sloadResolver/reassign.yul rename to test/libyul/yulOptimizerTests/loadResolver/reassign.yul index 51db34f53..fe7fccfe6 100644 --- a/test/libyul/yulOptimizerTests/sloadResolver/reassign.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/reassign.yul @@ -5,7 +5,7 @@ mstore(0, sload(a)) } // ==== -// step: sloadResolver +// step: loadResolver // ---- // { // let _1 := 0 diff --git a/test/libyul/yulOptimizerTests/sloadResolver/reassign_value_expression.yul b/test/libyul/yulOptimizerTests/loadResolver/reassign_value_expression.yul similarity index 96% rename from test/libyul/yulOptimizerTests/sloadResolver/reassign_value_expression.yul rename to test/libyul/yulOptimizerTests/loadResolver/reassign_value_expression.yul index 1f05da777..d2d0999fa 100644 --- a/test/libyul/yulOptimizerTests/sloadResolver/reassign_value_expression.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/reassign_value_expression.yul @@ -15,7 +15,7 @@ mstore(sload(a), 11) } // ==== -// step: sloadResolver +// step: loadResolver // ---- // { // let x := calldataload(1) diff --git a/test/libyul/yulOptimizerTests/loadResolver/second_mstore_with_delta.yul b/test/libyul/yulOptimizerTests/loadResolver/second_mstore_with_delta.yul new file mode 100644 index 000000000..ae23c1b8b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/second_mstore_with_delta.yul @@ -0,0 +1,24 @@ +{ + let x := calldataload(1) + let a := add(x, 10) + let b := add(x, 42) + mstore(a, 7) + // does not invalidate the first store, because the + // difference is larger than 32, even if the absolute + // values are unknown + mstore(b, 8) + sstore(mload(a), mload(b)) +} +// ==== +// step: loadResolver +// ---- +// { +// let x := calldataload(1) +// let a := add(x, 10) +// let b := add(x, 42) +// let _4 := 7 +// mstore(a, _4) +// let _5 := 8 +// mstore(b, _5) +// sstore(_4, _5) +// } diff --git a/test/libyul/yulOptimizerTests/sloadResolver/second_store.yul b/test/libyul/yulOptimizerTests/loadResolver/second_store.yul similarity index 94% rename from test/libyul/yulOptimizerTests/sloadResolver/second_store.yul rename to test/libyul/yulOptimizerTests/loadResolver/second_store.yul index b9823e29b..67aa196f4 100644 --- a/test/libyul/yulOptimizerTests/sloadResolver/second_store.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/second_store.yul @@ -7,7 +7,7 @@ mstore(0, sload(x)) } // ==== -// step: sloadResolver +// step: loadResolver // ---- // { // let x := calldataload(1) diff --git a/test/libyul/yulOptimizerTests/sloadResolver/second_store_same_value.yul b/test/libyul/yulOptimizerTests/loadResolver/second_store_same_value.yul similarity index 93% rename from test/libyul/yulOptimizerTests/sloadResolver/second_store_same_value.yul rename to test/libyul/yulOptimizerTests/loadResolver/second_store_same_value.yul index ef59ec8d0..b90b177b3 100644 --- a/test/libyul/yulOptimizerTests/sloadResolver/second_store_same_value.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/second_store_same_value.yul @@ -7,7 +7,7 @@ mstore(0, sload(x)) } // ==== -// step: sloadResolver +// step: loadResolver // ---- // { // let x := calldataload(1) diff --git a/test/libyul/yulOptimizerTests/sloadResolver/second_store_with_delta.yul b/test/libyul/yulOptimizerTests/loadResolver/second_store_with_delta.yul similarity index 95% rename from test/libyul/yulOptimizerTests/sloadResolver/second_store_with_delta.yul rename to test/libyul/yulOptimizerTests/loadResolver/second_store_with_delta.yul index 5d50185ae..a4154ce63 100644 --- a/test/libyul/yulOptimizerTests/sloadResolver/second_store_with_delta.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/second_store_with_delta.yul @@ -10,7 +10,7 @@ mstore(sload(a), sload(b)) } // ==== -// step: sloadResolver +// step: loadResolver // ---- // { // let x := calldataload(1) diff --git a/test/libyul/yulOptimizerTests/sloadResolver/simple.yul b/test/libyul/yulOptimizerTests/loadResolver/simple.yul similarity index 91% rename from test/libyul/yulOptimizerTests/sloadResolver/simple.yul rename to test/libyul/yulOptimizerTests/loadResolver/simple.yul index 3bd07d488..a32a54241 100644 --- a/test/libyul/yulOptimizerTests/sloadResolver/simple.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/simple.yul @@ -5,7 +5,7 @@ mstore(t, q) } // ==== -// step: sloadResolver +// step: loadResolver // ---- // { // let _2 := calldataload(10) diff --git a/test/libyul/yulOptimizerTests/loadResolver/simple_memory.yul b/test/libyul/yulOptimizerTests/loadResolver/simple_memory.yul new file mode 100644 index 000000000..a4c06ebdc --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/simple_memory.yul @@ -0,0 +1,14 @@ +{ + mstore(calldataload(0), calldataload(10)) + let t := mload(calldataload(10)) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// mstore(calldataload(0), _2) +// sstore(mload(_2), _2) +// } From b58170f890e8ad867a7565cc310e1175287c16bf Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Fri, 26 Apr 2019 11:57:49 +0200 Subject: [PATCH 099/115] Refactor & Speedups on CircleCI; split out out codecov into dedicated tests. --- .circleci/README.md | 21 + .circleci/config.yml | 916 +++++++++++++------------ .circleci/docker/Dockerfile.archlinux | 28 + .circleci/docker/Dockerfile.ubuntu1904 | 124 ++++ .circleci/soltest.sh | 63 ++ docs/contributing.rst | 2 +- scripts/common.sh | 96 +++ scripts/tests.sh | 102 +-- test/cmdlineTests.sh | 15 +- test/externalTests.sh | 1 + test/externalTests/colony.sh | 1 + test/externalTests/common.sh | 11 +- test/externalTests/gnosis.sh | 1 + test/externalTests/zeppelin.sh | 1 + 14 files changed, 847 insertions(+), 535 deletions(-) create mode 100644 .circleci/README.md create mode 100644 .circleci/docker/Dockerfile.archlinux create mode 100644 .circleci/docker/Dockerfile.ubuntu1904 create mode 100755 .circleci/soltest.sh create mode 100644 scripts/common.sh diff --git a/.circleci/README.md b/.circleci/README.md new file mode 100644 index 000000000..8c411c7b1 --- /dev/null +++ b/.circleci/README.md @@ -0,0 +1,21 @@ +## CircleCI integration + +### Docker images + +The docker images are build locally on the developer machine: + +```!sh +cd .circleci/docker/ + +docker build -t ethereum/solc-buildpack-deps:ubuntu1904 -f Dockerfile.ubuntu1904 . +docker push solidity/solc-buildpack-deps:ubuntu1904 + +docker build -t ethereum/solc-buildpack-deps:archlinux -f Dockerfile.archlinux . +docker push solidity/solc-buildpack-deps:archlinux +``` + +which you can find on Dockerhub after the push at: + + https://hub.docker.com/r/ethereum/solidity-buildpack-deps + +where the image tag reflects the target OS to build Solidity and run its test on. diff --git a/.circleci/config.yml b/.circleci/config.yml index da2de1c34..d88cb0269 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,51 +1,72 @@ +# vim:ts=2:sw=2:et +# -------------------------------------------------------------------------- +# Prefixes used in order to keep CircleCI workflow overview more readable: +# - b: build +# - t: test +# - ubu: ubuntu +# - ems: Emscripten +version: 2 + defaults: - # The default for tags is to not run, so we have to explicitly match a filter. - - build_on_tags: &build_on_tags - filters: - tags: - only: /.*/ + + # -------------------------------------------------------------------------- + # Build Templates + - setup_prerelease_commit_hash: &setup_prerelease_commit_hash name: Store commit hash and prerelease command: | if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi echo -n "$CIRCLE_SHA1" > commit_hash.txt + - run_build: &run_build name: Build command: | + set -ex + if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi + echo -n "$CIRCLE_SHA1" > commit_hash.txt mkdir -p build cd build [ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON" - cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS + cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS -G "Unix Makefiles" make -j4 + - run_build_ossfuzz: &run_build_ossfuzz name: Build_ossfuzz command: | mkdir -p build cd build - /src/LPM/external.protobuf/bin/protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz - cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS + protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz + cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS make ossfuzz ossfuzz_proto -j4 - - run_tests: &run_tests - name: Tests - command: scripts/tests.sh --junit_report test_results - - run_regressions: &run_regressions - name: Regression tests - command: | - export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" - scripts/regressions.py -o test_results + - run_proofs: &run_proofs name: Correctness proofs for optimization rules command: scripts/run_proofs.sh - - solc_artifact: &solc_artifact + + # -------------------------------------------------------------------------- + # Artifacts Templates + + # the whole build directory + - artifacts_build_dir: &artifacts_build_dir + root: build + paths: + - "*" + + # compiled solc executable target + - artifacts_solc: &artifacts_solc path: build/solc/solc destination: solc - - all_artifacts: &all_artifacts + + # compiled executable targets + - artifacts_executables: &artifacts_executables root: build paths: - solc/solc - test/soltest - test/tools/solfuzzer - - ossfuzz_artifacts: &ossfuzz_artifacts + + # compiled OSSFUZZ targets + - artifacts_executables_ossfuzz: &artifacts_executables_ossfuzz root: build paths: - test/tools/ossfuzz/const_opt_ossfuzz @@ -57,9 +78,280 @@ defaults: - test/tools/ossfuzz/yul_proto_diff_ossfuzz - test/tools/ossfuzz/yul_proto_ossfuzz -version: 2 + # test result output directory + - artifacts_test_results: &artifacts_test_results + path: test_results/ + destination: test_results/ + + # -------------------------------------------------------------------------- + # Tests Templates + + # store_test_results helper + - store_test_results: &store_test_results + path: test_results/ + + - run_soltest: &run_soltest + name: soltest + command: ./.circleci/soltest.sh + + - run_cmdline_tests: &run_cmdline_tests + name: command line tests + command: ./test/cmdlineTests.sh + + - test_steps: &test_steps + - checkout + - attach_workspace: + at: build + - run: *run_soltest + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + - test_ubuntu1904: &test_ubuntu1904 + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: *test_steps + + - test_asan: &test_asan + <<: *test_ubuntu1904 + steps: + - checkout + - attach_workspace: + at: build + - run: + <<: *run_soltest + no_output_timeout: 30m + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + # -------------------------------------------------------------------------- + # Workflow Templates + + - workflow_trigger_on_tags: &workflow_trigger_on_tags + filters: + tags: + only: /.*/ + + - workflow_ubuntu1904: &workflow_ubuntu1904 + <<: *workflow_trigger_on_tags + requires: + - b_ubu + + - workflow_ubuntu1904_codecov: &workflow_ubuntu1904_codecov + <<: *workflow_trigger_on_tags + requires: + - b_ubu_codecov + + - workflow_osx: &workflow_osx + <<: *workflow_trigger_on_tags + requires: + - b_osx + + - workflow_ubuntu1904_asan: &workflow_ubuntu1904_asan + <<: *workflow_trigger_on_tags + requires: + - b_ubu_asan + + - workflow_emscripten: &workflow_emscripten + <<: *workflow_trigger_on_tags + requires: + - b_ems + + - workflow_ubuntu1904_ossfuzz: &workflow_ubuntu1904_ossfuzz + <<: *workflow_trigger_on_tags + requires: + - b_ubu_ossfuzz + +# ----------------------------------------------------------------------------------------------- jobs: - build_emscripten: + + chk_spelling: + docker: + - image: circleci/python:3.6 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build + - run: + name: Install dependencies + command: | + pip install --user codespell + - run: + name: Check spelling + command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt + + chk_coding_style: + docker: + - image: buildpack-deps:disco + steps: + - checkout + - run: + name: Check for C++ coding style + command: ./scripts/check_style.sh + + chk_buglist: + docker: + - image: circleci/node + environment: + TERM: xterm + steps: + - checkout + - run: + name: JS deps + command: | + npm install download + npm install JSONPath + npm install mktemp + - run: + name: Test buglist + command: ./test/buglistTests.js + + chk_proofs: + docker: + - image: buildpack-deps:disco + environment: + TERM: xterm + steps: + - checkout + - run: + name: Z3 python deps + command: | + apt-get -qq update + apt-get -qy install python-pip + pip install --user z3-solver + - run: *run_proofs + + b_ubu: &build_ubuntu1904 + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + b_ubu_codecov: + <<: *build_ubuntu1904 + environment: + COVERAGE: ON + CMAKE_BUILD_TYPE: Debug + steps: + - checkout + - run: *run_build + - persist_to_workspace: *artifacts_build_dir + + t_ubu_codecov: + <<: *test_ubuntu1904 + environment: + EVM: constantinople + OPTIMIZE: 1 + steps: + - checkout + - attach_workspace: + at: build + - run: + name: "soltest: Syntax Tests" + command: build/test/soltest -t 'syntaxTest*' -- --no-ipc --testpath test + - run: + name: "Code Coverage: Syntax Tests" + command: codecov --flags syntax --gcov-root build + - run: *run_soltest + - run: + name: "Coverage: All" + command: codecov --flags all --gcov-root build + - store_artifacts: *artifacts_test_results + + # Builds in C++17 mode and uses debug build in order to speed up. + # Do *NOT* store any artifacts or workspace as we don't run tests on this build. + b_ubu_cxx17: + <<: *build_ubuntu1904 + environment: + CMAKE_BUILD_TYPE: Debug + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake -DUSE_CVC4=OFF + steps: + - checkout + - run: *run_build + + b_ubu_ossfuzz: + <<: *build_ubuntu1904 + environment: + TERM: xterm + CC: /usr/bin/clang-8 + CXX: /usr/bin/clang++-8 + CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake + steps: + - checkout + - run: *setup_prerelease_commit_hash + - run: *run_build_ossfuzz + - persist_to_workspace: *artifacts_executables_ossfuzz + + t_ubu_ossfuzz: &t_ubu_ossfuzz + <<: *test_ubuntu1904 + steps: + - checkout + - attach_workspace: + at: build + - run: + name: Regression tests + command: | + mkdir -p test_results + export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" + scripts/regressions.py -o test_results + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + b_archlinux: + docker: + - image: ethereum/solidity-buildpack-deps:archlinux + environment: + TERM: xterm + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + b_osx: + macos: + xcode: "10.0.0" + environment: + TERM: xterm + CMAKE_BUILD_TYPE: Debug + CMAKE_OPTIONS: -DLLL=ON + steps: + - checkout + - run: + name: Install build dependencies + command: | + brew unlink python + brew install z3 + brew install boost + brew install cmake + brew install wget + ./scripts/install_obsolete_jsoncpp_1_7_4.sh + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + t_osx_cli: + macos: + xcode: "10.0.0" + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build + - run: + name: Install dependencies + command: | + brew unlink python + brew install z3 + - run: *run_cmdline_tests + - store_artifacts: *artifacts_test_results + + b_ems: docker: - image: trzeci/emscripten:sdk-tag-1.38.22-64bit environment: @@ -94,7 +386,123 @@ jobs: - soljson.js - version.txt - test_emscripten_solcjs: + # x64 ASAN build, for testing for memory related bugs + b_ubu_asan: &b_ubu_asan + <<: *build_ubuntu1904 + environment: + CMAKE_OPTIONS: -DSANITIZE=address + CMAKE_BUILD_TYPE: Release + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + b_docs: + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - run: *setup_prerelease_commit_hash + - run: + name: Build documentation + command: ./scripts/docs.sh + - store_artifacts: + path: docs/_build/html/ + destination: docs-html + + t_ubu_cli: &t_ubu_cli + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build + - run: *run_cmdline_tests + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + t_ubu_asan_cli: + <<: *t_ubu_cli + environment: + TERM: xterm + ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2 + steps: + - checkout + - attach_workspace: + at: build + - run: + <<: *run_cmdline_tests + no_output_timeout: 30m + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + t_ubu_asan_constantinople: + <<: *test_asan + environment: + EVM: constantinople + OPTIMIZE: 0 + SOLTEST_IPC: 0 + ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2 + + t_ubu_homestead: + <<: *test_ubuntu1904 + environment: + EVM: homestead + OPTIMIZE: 0 + + t_ubu_homestead_opt: + <<: *test_ubuntu1904 + environment: + EVM: homestead + OPTIMIZE: 1 + + t_ubu_byzantium: + <<: *test_ubuntu1904 + environment: + EVM: byzantium + OPTIMIZE: 0 + + t_ubu_byzantium_opt: + <<: *test_ubuntu1904 + environment: + EVM: byzantium + OPTIMIZE: 1 + + t_ubu_constantinople: + <<: *test_ubuntu1904 + environment: + EVM: constantinople + OPTIMIZE: 0 + + t_ubu_constantinople_opt: + <<: *test_ubuntu1904 + environment: + EVM: constantinople + OPTIMIZE: 1 + + t_ubu_constantinople_opt_abiv2: + <<: *test_ubuntu1904 + environment: + EVM: constantinople + OPTIMIZE: 1 + ABI_ENCODER_V2: 1 + + t_ubu_petersburg: + <<: *test_ubuntu1904 + environment: + EVM: petersburg + OPTIMIZE: 0 + + t_ubu_petersburg_opt: + <<: *test_ubuntu1904 + environment: + EVM: petersburg + OPTIMIZE: 1 + + t_ems_solcjs: docker: - image: circleci/node:10 environment: @@ -104,16 +512,13 @@ jobs: - attach_workspace: at: /tmp/workspace - run: - name: Install external tests deps + name: Test solcjs command: | node --version npm --version - - run: - name: Test solcjs - command: | test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) - test_emscripten_external_gnosis: + t_ems_external_gnosis: docker: - image: circleci/node:10 environment: @@ -127,7 +532,7 @@ jobs: command: | test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js - test_emscripten_external_zeppelin: + t_ems_external_zeppelin: docker: - image: circleci/node:10 environment: @@ -141,7 +546,7 @@ jobs: command: | test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js - test_emscripten_external_colony: + t_ems_external_colony: docker: - image: circleci/node:10 environment: @@ -159,386 +564,51 @@ jobs: command: | test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js - build_x86_linux: - docker: - - image: buildpack-deps:bionic - environment: - TERM: xterm - COVERAGE: "ON" - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - "*" - - build_x86_linux_cxx17: - docker: - - image: buildpack-deps:disco - environment: - TERM: xterm - CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - - run: *setup_prerelease_commit_hash - - run: *run_build - - build_x86_archlinux: - docker: - - image: archlinux/base - environment: - TERM: xterm - steps: - - run: - name: Install build dependencies - command: | - pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar - - checkout - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - solc/solc - - test/soltest - - test/tools/solfuzzer - - build_x86_clang7_asan: - docker: - - image: buildpack-deps:cosmic - environment: - TERM: xterm - CC: /usr/bin/clang-7 - CXX: /usr/bin/clang++-7 - CMAKE_OPTIONS: -DSANITIZE=address -DCMAKE_BUILD_TYPE=Debug - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - solc/solc - - test/soltest - - test/tools/solfuzzer - - build_x86_mac: - macos: - xcode: "10.0.0" - environment: - TERM: xterm - CMAKE_OPTIONS: -DLLL=ON - steps: - - checkout - - run: - name: Install build dependencies - command: | - brew unlink python - brew install z3 - brew install boost - brew install cmake - brew install wget - ./scripts/install_obsolete_jsoncpp_1_7_4.sh - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: *all_artifacts - - test_check_spelling: - docker: - - image: circleci/python:3.6 - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - pip install --user codespell - - run: - name: Check spelling - command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt - - test_check_style: - docker: - - image: buildpack-deps:bionic - steps: - - checkout - - run: - name: Check for trailing whitespace - command: ./scripts/check_style.sh - - test_buglist: - docker: - - image: circleci/node - environment: - TERM: xterm - steps: - - checkout - - run: - name: JS deps - command: | - npm install download - npm install JSONPath - npm install mktemp - - run: - name: Test buglist - command: ./test/buglistTests.js - - proofs: - docker: - - image: buildpack-deps:bionic - environment: - TERM: xterm - steps: - - checkout - - run: - name: Z3 python deps - command: | - apt-get -qq update - apt-get -qy install python-pip - pip install --user z3-solver - - run: *run_proofs - - test_x86_linux: - docker: - - image: buildpack-deps:bionic - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - apt-get -qq update - apt-get -qy install libcvc4-dev libleveldb1v5 python-pip - pip install codecov - - run: mkdir -p test_results - - run: - name: Test type checker - command: build/test/soltest -t 'syntaxTest*' -- --no-ipc --testpath test - - run: - name: Coverage of type checker - command: codecov --flags syntax --gcov-root build - - run: *run_tests - - run: - name: Coverage of all - command: codecov --flags all --gcov-root build - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - test_x86_clang7_asan: - docker: - - image: buildpack-deps:cosmic - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - apt-get -qq update - apt-get -qy install llvm-7-dev libcvc4-dev libleveldb1v5 python-pip - # This is needed to resolve the symbols. Since we're using clang7 in the build, we must use the appropriate symbolizer. - update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1 - - run: mkdir -p test_results - - run: - name: Run soltest with ASAN - command: | - ulimit -a - # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). - ulimit -s 16384 - export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" - build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test - - run: - name: Run commandline tests with ASAN - command: | - ulimit -a - # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). - ulimit -s 16384 - export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" - test/cmdlineTests.sh - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - test_x86_archlinux: - docker: - - image: archlinux/base - environment: - TERM: xterm - steps: - - run: - name: Install dependencies - command: | - pacman --noconfirm -Syu --noprogressbar --needed boost z3 cvc4 git openssh tar - - checkout - - attach_workspace: - at: build - - run: mkdir -p test_results - - run: build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - test_x86_mac: - macos: - xcode: "10.0.0" - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - brew unlink python - brew install z3 - - run: mkdir -p test_results - - run: *run_tests - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - docs: - docker: - - image: buildpack-deps:bionic - environment: - DEBIAN_FRONTEND: noninteractive - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install python-sphinx python-pip - - run: *setup_prerelease_commit_hash - - run: - name: Build documentation - command: ./scripts/docs.sh - - store_artifacts: - path: docs/_build/html/ - destination: docs-html - - build_x86_linux_ossfuzz: - docker: - - image: buildpack-deps:disco - environment: - TERM: xterm - CC: /usr/bin/clang-8 - CXX: /usr/bin/clang++-8 - CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install wget clang-8 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\* - ./scripts/install_lpm.sh - ./scripts/install_libfuzzer.sh - # Install evmone and dependencies (intx and ethash) - ./scripts/install_evmone.sh - - run: *setup_prerelease_commit_hash - - run: *run_build_ossfuzz - - persist_to_workspace: *ossfuzz_artifacts - - test_x86_ossfuzz_regression: - docker: - - image: buildpack-deps:disco - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - apt-get -qq update - apt-get -qy install libcvc4-dev llvm-8-dev - ./scripts/download_ossfuzz_corpus.sh - update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1 - - run: mkdir -p test_results - - run: *run_regressions - - store_artifacts: - path: test_results/ - destination: test_results/ - workflows: version: 2 - build_all: - jobs: - - test_check_spelling: *build_on_tags - - test_check_style: *build_on_tags - - test_buglist: *build_on_tags - - proofs: *build_on_tags - - build_emscripten: *build_on_tags - - test_emscripten_solcjs: - <<: *build_on_tags - requires: - - build_emscripten - - build_x86_linux: *build_on_tags - - build_x86_linux_cxx17: *build_on_tags - - build_x86_clang7_asan: *build_on_tags - - build_x86_mac: *build_on_tags - - test_x86_linux: - <<: *build_on_tags - requires: - - build_x86_linux - - test_x86_clang7_asan: - <<: *build_on_tags - requires: - - build_x86_clang7_asan - - test_x86_mac: - <<: *build_on_tags - requires: - - build_x86_mac - - docs: *build_on_tags - - build_x86_archlinux: *build_on_tags - - test_x86_archlinux: - <<: *build_on_tags - requires: - - build_x86_archlinux - - build_x86_linux_ossfuzz: *build_on_tags - test_nightly: + main: + jobs: + # basic checks + - chk_spelling: *workflow_trigger_on_tags + - chk_coding_style: *workflow_trigger_on_tags + - chk_buglist: *workflow_trigger_on_tags + - chk_proofs: *workflow_trigger_on_tags + + # build-only + - b_docs: *workflow_trigger_on_tags + - b_archlinux: *workflow_trigger_on_tags + - b_ubu_cxx17: *workflow_trigger_on_tags + - b_ubu_ossfuzz: *workflow_trigger_on_tags + + # OS/X build and tests + - b_osx: *workflow_trigger_on_tags + - t_osx_cli: *workflow_osx + + # Ubuntu 18.10 build and tests + - b_ubu: *workflow_trigger_on_tags + - t_ubu_cli: *workflow_ubuntu1904 + - t_ubu_homestead: *workflow_ubuntu1904 + - t_ubu_homestead_opt: *workflow_ubuntu1904 + - t_ubu_byzantium: *workflow_ubuntu1904 + - t_ubu_byzantium_opt: *workflow_ubuntu1904 + - t_ubu_constantinople: *workflow_ubuntu1904 + - t_ubu_constantinople_opt: *workflow_ubuntu1904 + - t_ubu_constantinople_opt_abiv2: *workflow_ubuntu1904 + - t_ubu_petersburg: *workflow_ubuntu1904 + - t_ubu_petersburg_opt: *workflow_ubuntu1904 + + # ASan build and tests + - b_ubu_asan: *workflow_trigger_on_tags + - t_ubu_asan_constantinople: *workflow_ubuntu1904_asan + - t_ubu_asan_cli: *workflow_ubuntu1904_asan + + # Emscripten build and selected tests + - b_ems: *workflow_trigger_on_tags + - t_ems_solcjs: *workflow_emscripten + + nightly: + triggers: - schedule: cron: "0 0 * * *" @@ -546,23 +616,19 @@ workflows: branches: only: - develop - jobs: - - build_emscripten: *build_on_tags - - test_emscripten_external_zeppelin: - <<: *build_on_tags - requires: - - build_emscripten - - test_emscripten_external_gnosis: - <<: *build_on_tags - requires: - - build_emscripten - - test_emscripten_external_colony: - <<: *build_on_tags - requires: - - build_emscripten - - build_x86_linux_ossfuzz: *build_on_tags - - test_x86_ossfuzz_regression: - <<: *build_on_tags - requires: - - build_x86_linux_ossfuzz + + jobs: + # Emscripten builds and external tests + - b_ems: *workflow_trigger_on_tags + - t_ems_external_zeppelin: *workflow_emscripten + - t_ems_external_gnosis: *workflow_emscripten + - t_ems_external_colony: *workflow_emscripten + + # OSSFUZZ builds and (regression) tests + - b_ubu_ossfuzz: *workflow_trigger_on_tags + - t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz + + # Code Coverage enabled build and tests + - b_ubu_codecov: *workflow_trigger_on_tags + - t_ubu_codecov: *workflow_ubuntu1904_codecov diff --git a/.circleci/docker/Dockerfile.archlinux b/.circleci/docker/Dockerfile.archlinux new file mode 100644 index 000000000..cd956c9cd --- /dev/null +++ b/.circleci/docker/Dockerfile.archlinux @@ -0,0 +1,28 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Arch Linux +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# 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 +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM archlinux/base + +RUN pacman --noconfirm -Syu --noprogressbar --needed \ + base-devel boost cmake z3 cvc4 git openssh tar + diff --git a/.circleci/docker/Dockerfile.ubuntu1904 b/.circleci/docker/Dockerfile.ubuntu1904 new file mode 100644 index 000000000..bad21f664 --- /dev/null +++ b/.circleci/docker/Dockerfile.ubuntu1904 @@ -0,0 +1,124 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 19.04 (Disco Dingo) +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# 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 +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM buildpack-deps:disco + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential \ + software-properties-common \ + cmake ninja-build clang++-8 \ + libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \ + libboost-program-options-dev \ + libjsoncpp-dev \ + llvm-8-dev libcvc4-dev libleveldb1d \ + ; \ + apt-get install -qy python-pip python-sphinx; \ + update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \ + pip install codecov; \ + rm -rf /var/lib/apt/lists/* + +# Aleth for end-to-end tests +ARG ALETH_VERSION="1.6.0" +ARG ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6" +ARG ALETH_URL="https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz" +RUN set -ex; \ + wget -q -O /tmp/aleth.tar.gz "${ALETH_URL}"; \ + test "$(shasum /tmp/aleth.tar.gz)" = "$ALETH_HASH /tmp/aleth.tar.gz"; \ + tar -xf /tmp/aleth.tar.gz -C /usr + +# Z3 +RUN set -ex; \ + git clone --depth=1 --branch="Z3-4.8.5" https://github.com/Z3Prover/z3.git /usr/src/z3; \ + mkdir /usr/src/z3/build; \ + cd /usr/src/z3/build; \ + cmake -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="/usr" -G "Ninja" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/z3 + +# OSSFUZZ: LPM package (do not remove build dirs as solidity compiles/links against that dir) +RUN set -ex; \ + mkdir /src; \ + cd /src; \ + git clone https://github.com/google/libprotobuf-mutator.git; \ + cd libprotobuf-mutator; \ + git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + mkdir ../LPM; \ + cd ../LPM; \ + cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release; \ + ninja; \ + cp -vpr external.protobuf/bin/* /usr/bin/; \ + cp -vpr external.protobuf/include/* /usr/include/; \ + cp -vpr external.protobuf/lib/* /usr/lib/; \ + ninja install/strip + +# OSSFUZZ: libfuzzer +RUN set -ex; \ + cd /var/tmp; \ + svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer libfuzzer; \ + mkdir -p build-libfuzzer; \ + cd build-libfuzzer; \ + clang++-8 -O1 -stdlib=libstdc++ -std=c++11 -O2 -fPIC -c ../libfuzzer/*.cpp -I../libfuzzer; \ + ar r /usr/lib/libFuzzingEngine.a *.o; \ + rm -rf /var/lib/libfuzzer + +# ETHASH +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \ + cd ethash; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/ethash + +# INTX +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \ + cd intx; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/intx; + +# EVMONE +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.1.0" --recurse-submodules https://github.com/chfast/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh new file mode 100755 index 000000000..242d53f7c --- /dev/null +++ b/.circleci/soltest.sh @@ -0,0 +1,63 @@ +#! /bin/bash +#------------------------------------------------------------------------------ +# Bash script to execute the Solidity tests by CircleCI. +# +# The documentation for solidity is hosted at: +# +# https://solidity.readthedocs.org +# +# ------------------------------------------------------------------------------ +# Configuration Environment Variables: +# +# EVM=version_string Specifies EVM version to compile for (such as homestead, etc) +# OPTIMIZE=1 Enables backend optimizer +# ABI_ENCODER_V2=1 Enables ABI encoder version 2 +# +# ------------------------------------------------------------------------------ +# 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 +# +# (c) 2016-2019 solidity contributors. +# ------------------------------------------------------------------------------ +set -e + +OPTIMIZE=${OPTIMIZE:-"0"} +EVM=${EVM:-"invalid"} +WORKDIR=${CIRCLE_WORKING_DIRECTORY:-.} +SOLTEST_IPC=${SOLTEST_IPC:-1} +REPODIR="$(realpath $(dirname $0)/..)" +ALETH_PATH="/usr/bin/aleth" + +source "${REPODIR}/scripts/common.sh" +# Test result output directory (CircleCI is reading test results from here) +mkdir -p test_results + +ALETH_PID=$(run_aleth) + +function cleanup() { + safe_kill $ALETH_PID $ALETH_PATH +} +trap cleanup INT TERM + +# in case we run with ASAN enabled, we must increase stck size. +ulimit -s 16384 + +BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/$EVM.xml" +SOLTEST_ARGS="--evm-version=$EVM --ipcpath "${WORKDIR}/geth.ipc" $flags" +test "${SOLTEST_IPC}" = "1" || SOLTEST_ARGS="$SOLTEST_ARGS --no-ipc" +test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize" +test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2 --optimize-yul" + +${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS} diff --git a/docs/contributing.rst b/docs/contributing.rst index 0e0a58905..fd488d56d 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -130,7 +130,7 @@ The CI runs additional tests (including ``solc-js`` and testing third party Soli Some versions of ``aleth`` can not be used for testing. We suggest using the same version that the Solidity continuous integration tests use. - Currently the CI uses version ``1.5.0-alpha.7`` of ``aleth``. + Currently the CI uses version ``1.6.0`` of ``aleth``. Writing and running syntax tests -------------------------------- diff --git a/scripts/common.sh b/scripts/common.sh new file mode 100644 index 000000000..f1e5cf134 --- /dev/null +++ b/scripts/common.sh @@ -0,0 +1,96 @@ +# ------------------------------------------------------------------------------ +# vim:ts=4:et +# 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 +# +# (c) 2016-2019 solidity contributors. +# ------------------------------------------------------------------------------ + +if [ "$CIRCLECI" ] +then + export TERM="${TERM:-xterm}" + function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } + function printError() { echo "$(tput setaf 1)$1$(tput setaf 7)"; } + function printLog() { echo "$(tput setaf 3)$1$(tput setaf 7)"; } +else + function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } + function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } + function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; } +fi + +safe_kill() +{ + local PID=${1} + local NAME=${2:-${1}} + local n=1 + + # only proceed if $PID does exist + kill -0 $PID 2>/dev/null || return + + echo "Sending SIGTERM to ${NAME} (${PID}) ..." + kill $PID + + # wait until process terminated gracefully + while kill -0 $PID 2>/dev/null && [[ $n -le 4 ]]; do + echo "Waiting ($n) ..." + sleep 1 + n=$[n + 1] + done + + # process still alive? then hard-kill + if kill -0 $PID 2>/dev/null; then + echo "Sending SIGKILL to ${NAME} (${PID}) ..." + kill -9 $PID + fi +} + +# Ensures aleth executable and exposes the following information: +# +# - env var ALETH_PATH: to point to the aleth executable. +# - directory /tmp/test if needed. No cleanup is done on this directory +function download_aleth() +{ + if which aleth &>/dev/null; then + ALETH_PATH=`which aleth` + elif [[ "$OSTYPE" == "darwin"* ]]; then + ALETH_PATH="$(realpath $(dirname "$0")/..)/aleth" + elif [ "$CIRCLECI" ] || [ -z $CI ]; then + ALETH_PATH="aleth" + else + ALETH_PATH="/tmp/test/bin/aleth" + mkdir -p /tmp/test + # Any time the hash is updated here, the "Running the compiler tests" section in contributing.rst should also be updated. + local ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6" + local ALETH_VERSION="1.6.0" + wget -q -O /tmp/test/aleth.tar.gz https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz + test "$(shasum /tmp/test/aleth.tar.gz)" = "$ALETH_HASH /tmp/test/aleth.tar.gz" + tar -xf /tmp/test/aleth.tar.gz -C /tmp/test + sync + chmod +x $ALETH_PATH + sync # Otherwise we might get a "text file busy" error + fi +} + +# Executes aleth in the background and echos its PID. +function run_aleth() +{ + $ALETH_PATH --db memorydb --test -d "${WORKDIR}" &> /dev/null & + echo $! + + # Wait until the IPC endpoint is available. + while [ ! -S "${WORKDIR}/geth.ipc" ] ; do sleep 1; done + sleep 2 +} + diff --git a/scripts/tests.sh b/scripts/tests.sh index d0882aff4..0350de5a0 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -28,7 +28,9 @@ set -e -REPO_ROOT="$(dirname "$0")"/.. +REPO_ROOT="$(dirname "$0")/.." + +source "${REPO_ROOT}/scripts/common.sh" WORKDIR=`mktemp -d` # Will be printed in case of a test failure @@ -40,38 +42,8 @@ CMDLINE_PID= if [[ "$OSTYPE" == "darwin"* ]] then SMT_FLAGS="--no-smt" - if [ "$CIRCLECI" ] - then - IPC_ENABLED=false - IPC_FLAGS="--no-ipc" - fi fi -safe_kill() { - local PID=${1} - local NAME=${2:-${1}} - local n=1 - - # only proceed if $PID does exist - kill -0 $PID 2>/dev/null || return - - echo "Sending SIGTERM to ${NAME} (${PID}) ..." - kill $PID - - # wait until process terminated gracefully - while kill -0 $PID 2>/dev/null && [[ $n -le 4 ]]; do - echo "Waiting ($n) ..." - sleep 1 - n=$[n + 1] - done - - # process still alive? then hard-kill - if kill -0 $PID 2>/dev/null; then - echo "Sending SIGKILL to ${NAME} (${PID}) ..." - kill -9 $PID - fi -} - cleanup() { # ensure failing commands don't cause termination during cleanup (especially within safe_kill) set +e @@ -103,15 +75,6 @@ else log_directory="" fi -if [ "$CIRCLECI" ] -then - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } - function printError() { echo "$(tput setaf 1)$1$(tput setaf 7)"; } -else - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } - function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } -fi - printTask "Running commandline tests..." # Only run in parallel if this is run on CI infrastructure if [[ -n "$CI" ]] @@ -126,41 +89,6 @@ else fi fi -function download_aleth() -{ - if [[ "$OSTYPE" == "darwin"* ]]; then - ALETH_PATH="$REPO_ROOT/aleth" - elif [ -z $CI ]; then - ALETH_PATH="aleth" - else - mkdir -p /tmp/test - # Any time the hash is updated here, the "Running the compiler tests" section in contributing.rst should also be updated. - ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6" - ALETH_VERSION=1.6.0 - wget -q -O /tmp/test/aleth.tar.gz https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz - test "$(shasum /tmp/test/aleth.tar.gz)" = "$ALETH_HASH /tmp/test/aleth.tar.gz" - tar -xf /tmp/test/aleth.tar.gz -C /tmp/test - ALETH_PATH="/tmp/test/bin/aleth" - sync - chmod +x $ALETH_PATH - sync # Otherwise we might get a "text file busy" error - fi - -} - -# $1: data directory -# echos the PID -function run_aleth() -{ - # Use this to have aleth log output - #$REPO_ROOT/scripts/aleth_with_log.sh $ALETH_PATH $ALETH_TMP_OUT --log-verbosity 3 --db memorydb --test -d "${WORKDIR}" &> /dev/null & - $ALETH_PATH --db memorydb --test -d "${WORKDIR}" &> /dev/null & - echo $! - # Wait until the IPC endpoint is available. - while [ ! -S "${WORKDIR}/geth.ipc" ] ; do sleep 1; done - sleep 2 -} - function check_aleth() { printTask "Running IPC tests with $ALETH_PATH..." if ! hash $ALETH_PATH 2>/dev/null; then @@ -176,17 +104,11 @@ then ALETH_PID=$(run_aleth) fi -progress="--show-progress" -if [ "$CIRCLECI" ] -then - progress="" -fi - EVM_VERSIONS="homestead byzantium" -if [ "$CIRCLECI" ] || [ -z "$CI" ] +if [ -z "$CI" ] then -EVM_VERSIONS+=" constantinople petersburg" + EVM_VERSIONS+=" constantinople petersburg" fi # And then run the Solidity unit-tests in the matrix combination of optimizer / no optimizer @@ -212,16 +134,16 @@ do log="" if [ -n "$log_directory" ] then - if [ -n "$optimize" ] - then - log=--logger=JUNIT,error,$log_directory/opt_$vm.xml $testargs - else - log=--logger=JUNIT,error,$log_directory/noopt_$vm.xml $testargs_no_opt - fi + if [ -n "$optimize" ] + then + log=--logger=JUNIT,error,$log_directory/opt_$vm.xml $testargs + else + log=--logger=JUNIT,error,$log_directory/noopt_$vm.xml $testargs_no_opt + fi fi set +e - "$REPO_ROOT"/build/test/soltest $progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $IPC_FLAGS $force_abiv2_flag --ipcpath "${WORKDIR}/geth.ipc" + "$REPO_ROOT"/build/test/soltest --show-progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $IPC_FLAGS $force_abiv2_flag --ipcpath "${WORKDIR}/geth.ipc" if test "0" -ne "$?"; then if [ -n "$log_directory" ] diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 89290b357..d0e01278d 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -31,6 +31,7 @@ set -e ## GLOBAL VARIABLES REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) +source "${REPO_ROOT}/scripts/common.sh" SOLC="$REPO_ROOT/build/solc/solc" INTERACTIVE=true if ! tty -s || [ "$CI" ] @@ -40,17 +41,13 @@ fi FULLARGS="--optimize --ignore-missing --combined-json abi,asm,ast,bin,bin-runtime,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc" -## FUNCTIONS - -if [ "$CIRCLECI" ] -then - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } - function printError() { echo "$(tput setaf 1)$1$(tput setaf 7)"; } -else - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } - function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } +# extend stack size in case we run via ASAN +if [[ -n "${CIRCLECI}" ]] || [[ -n "$CI" ]]; then + ulimit -s 16384 + ulimit -a fi +## FUNCTIONS function compileFull() { diff --git a/test/externalTests.sh b/test/externalTests.sh index 482819458..acac38629 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -37,6 +37,7 @@ fi SOLJSON="$1" REPO_ROOT="$(dirname "$0")" +source scripts/common.sh source test/externalTests/common.sh printTask "Running external tests..." diff --git a/test/externalTests/colony.sh b/test/externalTests/colony.sh index 9745d10ce..0e867c997 100755 --- a/test/externalTests/colony.sh +++ b/test/externalTests/colony.sh @@ -18,6 +18,7 @@ # # (c) 2019 solidity contributors. #------------------------------------------------------------------------------ +source scripts/common.sh source test/externalTests/common.sh verify_input "$1" diff --git a/test/externalTests/common.sh b/test/externalTests/common.sh index ebcdaa9a8..d6574da4d 100644 --- a/test/externalTests/common.sh +++ b/test/externalTests/common.sh @@ -20,16 +20,7 @@ #------------------------------------------------------------------------------ set -e -if [ "$CIRCLECI" ] -then - function printTask() { echo ""; echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } - function printError() { echo ""; echo "$(tput setaf 1)$1$(tput setaf 7)"; } - function printLog() { echo "$(tput setaf 3)$1$(tput setaf 7)"; } -else - function printTask() { echo ""; echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } - function printError() { echo ""; echo "$(tput setaf 1)$1$(tput sgr0)"; } - function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; } -fi +# Requires "${REPO_ROOT}/scripts/common.sh" to be included before. function verify_input { diff --git a/test/externalTests/gnosis.sh b/test/externalTests/gnosis.sh index 095207acc..f86816fd0 100755 --- a/test/externalTests/gnosis.sh +++ b/test/externalTests/gnosis.sh @@ -18,6 +18,7 @@ # # (c) 2019 solidity contributors. #------------------------------------------------------------------------------ +source scripts/common.sh source test/externalTests/common.sh verify_input "$1" diff --git a/test/externalTests/zeppelin.sh b/test/externalTests/zeppelin.sh index b85309921..b24617f37 100755 --- a/test/externalTests/zeppelin.sh +++ b/test/externalTests/zeppelin.sh @@ -18,6 +18,7 @@ # # (c) 2019 solidity contributors. #------------------------------------------------------------------------------ +source scripts/common.sh source test/externalTests/common.sh verify_input "$1" From 8b461be2b08de639047a52aaf138d722a9179fcf Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 23 Jun 2019 18:27:14 -0400 Subject: [PATCH 100/115] Add missing space in error recovery message. Also add a space in the Test Title --- liblangutil/ParserBase.cpp | 2 +- test/InteractiveTests.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/liblangutil/ParserBase.cpp b/liblangutil/ParserBase.cpp index 7d00fedc4..4ceb9358f 100644 --- a/liblangutil/ParserBase.cpp +++ b/liblangutil/ParserBase.cpp @@ -106,7 +106,7 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN m_scanner->next(); string const expectedToken = ParserBase::tokenName(_value); - string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + "instead."; + string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + " instead."; if (m_scanner->currentToken() == Token::EOS) { // rollback to where the token started, and raise exception to be caught at a higher level. diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 09cc08a29..3f46558d4 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -56,7 +56,7 @@ Testsuite const g_interactiveTestsuites[] = { {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, - {"ErrorRecovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, + {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SyntaxTest::create}, From 1bf4fc571c0c4f19b9c1c5361acd215875b3b02f Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 20 Jun 2019 13:09:48 +0200 Subject: [PATCH 101/115] More specific bug description. --- docs/bugs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/bugs.json b/docs/bugs.json index 19117fd08..94b1ad77a 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,8 +1,8 @@ [ { "name": "ABIEncoderV2StorageArrayWithMultiSlotElement", - "summary": "Storage arrays containing structs or other arrays are not read properly when directly encoded in function calls or in abi.encode*.", - "description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable.", + "summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.", + "description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.", "introduced": "0.4.16", "fixed": "0.5.10", "severity": "low", From 7f16e161836ec43dcd86143a53157715d8013dce Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 24 Jun 2019 12:42:05 +0200 Subject: [PATCH 102/115] State what is being tested. --- .circleci/soltest.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh index 242d53f7c..673540b3c 100755 --- a/.circleci/soltest.sh +++ b/.circleci/soltest.sh @@ -60,4 +60,6 @@ test "${SOLTEST_IPC}" = "1" || SOLTEST_ARGS="$SOLTEST_ARGS --no-ipc" test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize" test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2 --optimize-yul" +echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS}" + ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS} From 215f41776c867187f460f52f20fd87bea83bb499 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Mon, 24 Jun 2019 12:40:16 +0200 Subject: [PATCH 103/115] Clarify implicit conversion --- docs/types/conversion.rst | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/types/conversion.rst b/docs/types/conversion.rst index 49a3f0100..51e373405 100644 --- a/docs/types/conversion.rst +++ b/docs/types/conversion.rst @@ -8,25 +8,28 @@ Conversions between Elementary Types Implicit Conversions -------------------- -If an operator is applied to different types, the compiler tries to -implicitly convert one of the operands to the type of the other (the same is -true for assignments). In general, an implicit conversion between value-types -is possible if it -makes sense semantically and no information is lost: ``uint8`` is convertible to -``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256`` -(because ``uint256`` cannot hold e.g. ``-1``). +If an operator is applied to different types, the compiler tries to implicitly +convert one of the operands to the type of the other (the same is true for assignments). +This means that operations are always performed in the type of one of the operands. +In general, an implicit conversion between value-types is possible if it makes +sense semantically and no information is lost. + +For example, ``uint8`` is convertible to +``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256``, +because ``uint256`` cannot hold values such as ``-1``. For more details, please consult the sections about the types themselves. Explicit Conversions -------------------- -If the compiler does not allow implicit conversion but you know what you are -doing, an explicit type conversion is sometimes possible. Note that this may -give you some unexpected behaviour and allows you to bypass some security +If the compiler does not allow implicit conversion but you are confident a conversion will work, +an explicit type conversion is sometimes possible. This may +result in unexpected behaviour and allows you to bypass some security features of the compiler, so be sure to test that the -result is what you want! Take the following example where you are converting -a negative ``int`` to a ``uint``: +result is what you want and expect! + +Take the following example that converts a negative ``int`` to a ``uint``: :: @@ -42,7 +45,7 @@ cut off:: uint32 a = 0x12345678; uint16 b = uint16(a); // b will be 0x5678 now -If an integer is explicitly converted to a larger type, it is padded on the left (i.e. at the higher order end). +If an integer is explicitly converted to a larger type, it is padded on the left (i.e., at the higher order end). The result of the conversion will compare equal to the original integer:: uint16 a = 0x1234; From 0fd1db533e260970f8af2285f1c290afa6fa1881 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Fri, 21 Jun 2019 17:33:35 +0200 Subject: [PATCH 104/115] yul: adds reindent() function to reindent yul source code and makes use of it in IRGenerator. This function does count curly and round braces and reindents accordingly the beginning of each line. It does consider line-comments (// and ///) but not multiline comments (/* ... */). --- libsolidity/codegen/ir/IRGenerator.cpp | 4 +- libyul/Utilities.cpp | 47 +++++++++++++++++++ libyul/Utilities.h | 2 + .../standard_ir_requested/output.json | 2 +- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 17b44fcd8..34c2bf3b3 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -45,8 +46,7 @@ using namespace dev::solidity; pair IRGenerator::run(ContractDefinition const& _contract) { - // TODO Would be nice to pretty-print this while retaining comments. - string ir = generate(_contract); + string const ir = yul::reindent(generate(_contract)); yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); if (!asmStack.parseAndAnalyze("", ir)) diff --git a/libyul/Utilities.cpp b/libyul/Utilities.cpp index b69df0e4f..6894c2319 100644 --- a/libyul/Utilities.cpp +++ b/libyul/Utilities.cpp @@ -26,10 +26,57 @@ #include #include +#include + +#include +#include +#include +#include + using namespace std; using namespace dev; using namespace yul; +using boost::split; +using boost::is_any_of; + +string yul::reindent(string const& _code) +{ + auto const static countBraces = [](string const& _s) noexcept -> int + { + auto const i = _s.find("//"); + auto const e = i == _s.npos ? end(_s) : next(begin(_s), i); + auto const opening = count_if(begin(_s), e, [](auto ch) { return ch == '{' || ch == '('; }); + auto const closing = count_if(begin(_s), e, [](auto ch) { return ch == '}' || ch == ')'; }); + return opening - closing; + }; + + vector lines; + split(lines, _code, is_any_of("\n")); + for (string& line: lines) + boost::trim(line); + + stringstream out; + int depth = 0; + + for (string const& line: lines) + { + int const diff = countBraces(line); + if (diff < 0) + depth += diff; + + for (int i = 0; i < depth; ++i) + out << '\t'; + + out << line << '\n'; + + if (diff > 0) + depth += diff; + } + + return out.str(); +} + u256 yul::valueOfNumberLiteral(Literal const& _literal) { yulAssert(_literal.kind == LiteralKind::Number, "Expected number literal!"); diff --git a/libyul/Utilities.h b/libyul/Utilities.h index 0103d74ec..092dc90c3 100644 --- a/libyul/Utilities.h +++ b/libyul/Utilities.h @@ -26,6 +26,8 @@ namespace yul { +std::string reindent(std::string const& _code); + dev::u256 valueOfNumberLiteral(Literal const& _literal); dev::u256 valueOfStringLiteral(Literal const& _literal); dev::u256 valueOfBoolLiteral(Literal const& _literal); diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index 6dad16f40..e64f4eea5 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -1 +1 @@ -{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\tnewValue :=\n\t\t\t\t\n\t\t\t\t\tshr(224, value)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}} +{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\nobject \"C_6\" {\n\tcode {\n\t\tmstore(64, 128)\n\t\t\n\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\t\n\t\t\n\t\tfunction fun_f_5() {\n\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t\n\t}\n\tobject \"C_6_deployed\" {\n\t\tcode {\n\t\t\tmstore(64, 128)\n\t\t\t\n\t\t\tif iszero(lt(calldatasize(), 4))\n\t\t\t{\n\t\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\t\tswitch selector\n\t\t\t\t\n\t\t\t\tcase 0x26121ff0\n\t\t\t\t{\n\t\t\t\t\t// f()\n\t\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t\tabi_decode_tuple_(4, calldatasize())\n\t\t\t\t\tfun_f_5()\n\t\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tdefault {}\n\t\t\t}\n\t\t\trevert(0, 0)\n\t\t\t\n\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\tnewValue :=\n\t\t\t\t\n\t\t\t\tshr(224, value)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t}\n\t}\n}\n\n"}}},"sources":{"A":{"id":0}}} From 30e843a2175f7292e04149bbbf8ad7daaa7e1021 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Fri, 21 Jun 2019 14:56:27 +0200 Subject: [PATCH 105/115] State what default value of enum in example is Fix formatting issue and add mention of the enum default type Other files --- docs/control-structures.rst | 4 ++-- docs/examples/safe-remote.rst | 1 + docs/types/value-types.rst | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 85e594707..bd04ce58b 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -298,8 +298,8 @@ Scoping and Declarations A variable which is declared will have an initial default value whose byte-representation is all zeros. The "default values" of variables are the typical "zero-state" of whatever the type is. For example, the default value for a ``bool`` is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For statically-sized arrays and ``bytes1`` to ``bytes32``, each individual -element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes`` -and ``string``, the default value is an empty array or string. +element will be initialized to the default value corresponding to its type. For dynamically-sized arrays, ``bytes`` +and ``string``, the default value is an empty array or string. For the ``enum`` type, the default value is its first member. Scoping in Solidity follows the widespread scoping rules of C99 (and many other languages): Variables are visible from the point right after their declaration diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst index 765bdfc35..c4e684ce1 100644 --- a/docs/examples/safe-remote.rst +++ b/docs/examples/safe-remote.rst @@ -13,6 +13,7 @@ Safe Remote Purchase address payable public seller; address payable public buyer; enum State { Created, Locked, Inactive } + // The state variable has a default value of the first member, `State.created` State public state; // Ensure that `msg.value` is an even number. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index b8d67399a..f970a2e2c 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -512,7 +512,7 @@ Enums Enums are one way to create a user-defined type in Solidity. They are explicitly convertible to and from all integer types but implicit conversion is not allowed. The explicit conversion from integer checks at runtime that the value lies inside the range of the enum and causes a failing assert otherwise. -Enums needs at least one member. +Enums require at least one member, and its default value when declared is the first member. The data representation is the same as for enums in C: The options are represented by subsequent unsigned integer values starting from ``0``. From 1388ffaeeee2ed2adf4f231c143a85dab9f0a612 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 24 Jun 2019 14:41:58 +0200 Subject: [PATCH 106/115] Sort changelog. --- Changelog.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index ed77a9afb..aef2625f3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,17 +4,12 @@ Important Bugfixes: * Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots -Language Features: - - - Compiler Features: - * Optimizer: Add rule to simplify SUB(~0, X) to NOT(X). * Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch. + * Optimizer: Add rule to simplify ``SUB(~0, X)`` to ``NOT(X)``. * Yul Optimizer: Make the optimizer work for all dialects of Yul including eWasm. - Bugfixes: * Yul / Inline Assembly Parser: Disallow trailing commas in function call arguments. * Set state mutability of the function type members ``gas`` and ``value`` to pure (while their return type inherits state mutability from the function type). From 0e812b16c26e613370dc075f70965b1eb7c2c3e6 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 13 Jun 2019 17:22:24 +0200 Subject: [PATCH 107/115] Minor indent fix --- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 8a223b1dd..2bd593171 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -76,9 +76,9 @@ struct CopyTranslate: public yul::ASTCopier return yul::Literal{ _identifier.location, - yul::LiteralKind::Number, - yul::YulString{value}, - yul::YulString{"uint256"} + yul::LiteralKind::Number, + yul::YulString{value}, + yul::YulString{"uint256"} }; } } From ed275fd76029086cca9c3aed3cd1afa42d5ff2b4 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Fri, 21 Jun 2019 13:20:48 +0200 Subject: [PATCH 108/115] [SMTChecker] Collect assertions in EncodingContext --- libsolidity/formal/EncodingContext.cpp | 54 ++++++++++++++++++----- libsolidity/formal/EncodingContext.h | 37 ++++++++++++---- libsolidity/formal/SMTChecker.cpp | 60 +++++++++++++------------- libsolidity/formal/SMTChecker.h | 8 +--- libsolidity/formal/SMTPortfolio.cpp | 16 ------- libsolidity/formal/SMTPortfolio.h | 2 - 6 files changed, 104 insertions(+), 73 deletions(-) diff --git a/libsolidity/formal/EncodingContext.cpp b/libsolidity/formal/EncodingContext.cpp index 150b5813d..2442c7df7 100644 --- a/libsolidity/formal/EncodingContext.cpp +++ b/libsolidity/formal/EncodingContext.cpp @@ -23,15 +23,15 @@ using namespace std; using namespace dev; using namespace dev::solidity::smt; -EncodingContext::EncodingContext(SolverInterface& _solver): - m_solver(_solver), - m_thisAddress(make_unique("this", m_solver)) +EncodingContext::EncodingContext(std::shared_ptr _solver): + m_thisAddress(make_unique("this", *_solver)), + m_solver(_solver) { auto sort = make_shared( make_shared(Kind::Int), make_shared(Kind::Int) ); - m_balances = make_unique(sort, "balances", m_solver); + m_balances = make_unique(sort, "balances", *m_solver); } void EncodingContext::reset() @@ -41,6 +41,7 @@ void EncodingContext::reset() m_globalContext.clear(); m_thisAddress->increaseIndex(); m_balances->increaseIndex(); + m_assertions.clear(); } /// Variables. @@ -55,7 +56,7 @@ bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDe { solAssert(!knownVariable(_varDecl), ""); auto const& type = _varDecl.type(); - auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), m_solver); + auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *m_solver); m_variables.emplace(&_varDecl, result.second); return result.first; } @@ -105,7 +106,7 @@ void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl) void EncodingContext::setZeroValue(SymbolicVariable& _variable) { - setSymbolicZeroValue(_variable, m_solver); + setSymbolicZeroValue(_variable, *m_solver); } void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl) @@ -116,7 +117,7 @@ void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl void EncodingContext::setUnknownValue(SymbolicVariable& _variable) { - setSymbolicUnknownValue(_variable, m_solver); + setSymbolicUnknownValue(_variable, *m_solver); } /// Expressions @@ -143,7 +144,7 @@ bool EncodingContext::createExpression(solidity::Expression const& _e, shared_pt } else { - auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), m_solver); + auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *m_solver); m_expressions.emplace(&_e, result.second); return result.first; } @@ -165,7 +166,7 @@ shared_ptr EncodingContext::globalSymbol(string const& _name) bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr) { solAssert(!knownGlobalSymbol(_name), ""); - auto result = newSymbolicVariable(*_expr.annotation().type, _name, m_solver); + auto result = newSymbolicVariable(*_expr.annotation().type, _name, *m_solver); m_globalContext.emplace(_name, result.second); setUnknownValue(*result.second); return result.first; @@ -207,9 +208,40 @@ void EncodingContext::transfer(Expression _from, Expression _to, Expression _val m_balances->valueAtIndex(indexBefore), m_balances->valueAtIndex(indexAfter) ); - m_solver.addAssertion(m_balances->currentValue() == newBalances); + m_solver->addAssertion(m_balances->currentValue() == newBalances); } +/// Solver. + +Expression EncodingContext::assertions() +{ + if (m_assertions.empty()) + return Expression(true); + + return m_assertions.back(); +} + +void EncodingContext::pushSolver() +{ + m_assertions.push_back(assertions()); +} + +void EncodingContext::popSolver() +{ + solAssert(!m_assertions.empty(), ""); + m_assertions.pop_back(); +} + +void EncodingContext::addAssertion(Expression const& _expr) +{ + if (m_assertions.empty()) + m_assertions.push_back(_expr); + else + m_assertions.back() = _expr && move(m_assertions.back()); +} + +/// Private helpers. + void EncodingContext::addBalance(Expression _address, Expression _value) { auto newBalances = Expression::store( @@ -218,5 +250,5 @@ void EncodingContext::addBalance(Expression _address, Expression _value) balance(_address) + move(_value) ); m_balances->increaseIndex(); - m_solver.addAssertion(newBalances == m_balances->currentValue()); + m_solver->addAssertion(newBalances == m_balances->currentValue()); } diff --git a/libsolidity/formal/EncodingContext.h b/libsolidity/formal/EncodingContext.h index 4a7d05863..4532d6fb4 100644 --- a/libsolidity/formal/EncodingContext.h +++ b/libsolidity/formal/EncodingContext.h @@ -36,12 +36,12 @@ namespace smt class EncodingContext { public: - EncodingContext(SolverInterface& _solver); + EncodingContext(std::shared_ptr _solver); /// Resets the entire context. void reset(); - /// Methods related to variables. + /// Variables. //@{ /// @returns the symbolic representation of a program variable. std::shared_ptr variable(solidity::VariableDeclaration const& _varDecl); @@ -74,7 +74,7 @@ public: void setUnknownValue(SymbolicVariable& _variable); //@} - /// Methods related to expressions. + /// Expressions. ////@{ /// @returns the symbolic representation of an AST node expression. std::shared_ptr expression(solidity::Expression const& _e); @@ -88,12 +88,13 @@ public: bool knownExpression(solidity::Expression const& _e) const; //@} - /// Methods related to global variables and functions. + /// Global variables and functions. //@{ /// Global variables and functions. std::shared_ptr globalSymbol(std::string const& _name); - /// @returns all symbolic variables. + /// @returns all symbolic globals. std::unordered_map> const& globalSymbols() const { return m_globalContext; } + /// Defines a new global variable or function /// and @returns true if type was abstracted. bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr); @@ -101,7 +102,7 @@ public: bool knownGlobalSymbol(std::string const& _var) const; //@} - /// Blockchain related methods. + /// Blockchain. //@{ /// Value of `this` address. Expression thisAddress(); @@ -113,12 +114,22 @@ public: void transfer(Expression _from, Expression _to, Expression _value); //@} + /// Solver. + //@{ + /// @returns conjunction of all added assertions. + Expression assertions(); + void pushSolver(); + void popSolver(); + void addAssertion(Expression const& _e); + std::shared_ptr solver() { return m_solver; } + //@} + private: /// Adds _value to _account's balance. void addBalance(Expression _account, Expression _value); - SolverInterface& m_solver; - + /// Symbolic expressions. + //{@ /// Symbolic variables. std::unordered_map> m_variables; @@ -134,6 +145,16 @@ private: /// Symbolic balances. std::unique_ptr m_balances; + //@} + + /// Solver related. + //@{ + /// Solver can be SMT solver or Horn solver in the future. + std::shared_ptr m_solver; + + /// Assertion stack. + std::vector m_assertions; + //@} }; } diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 38caa566b..484a3afaf 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -34,7 +34,7 @@ using namespace langutil; using namespace dev::solidity; SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): - m_interface(_smtlib2Responses), + m_interface(make_shared(_smtlib2Responses)), m_errorReporterReference(_errorReporter), m_errorReporter(m_smtErrors), m_context(m_interface) @@ -59,11 +59,11 @@ void SMTChecker::analyze(SourceUnit const& _source, shared_ptr const& _ _source.accept(*this); - solAssert(m_interface.solvers() > 0, ""); + solAssert(m_interface->solvers() > 0, ""); // If this check is true, Z3 and CVC4 are not available // and the query answers were not provided, since SMTPortfolio // guarantees that SmtLib2Interface is the first solver. - if (!m_interface.unhandledQueries().empty() && m_interface.solvers() == 1) + if (!m_interface->unhandledQueries().empty() && m_interface->solvers() == 1) { if (!m_noSolverWarning) { @@ -109,7 +109,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) // Not visited by a function call if (m_callStack.empty()) { - m_interface.reset(); + m_interface->reset(); m_context.reset(); m_pathConditions.clear(); m_callStack.clear(); @@ -305,13 +305,13 @@ bool SMTChecker::visit(ForStatement const& _node) checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE."); } - m_interface.push(); + m_interface->push(); if (_node.condition()) - m_interface.addAssertion(expr(*_node.condition())); + m_interface->addAssertion(expr(*_node.condition())); _node.body().accept(*this); if (_node.loopExpression()) _node.loopExpression()->accept(*this); - m_interface.pop(); + m_interface->pop(); auto indicesAfterLoop = copyVariableIndices(); // We reset the execution to before the loop @@ -696,7 +696,7 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) solAssert(value, ""); smt::Expression thisBalance = m_context.balance(); - setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_interface); + setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), *m_interface); checkCondition(thisBalance < expr(*value), _funCall.location(), "Insufficient funds", "address(this).balance", &thisBalance); m_context.transfer(m_context.thisAddress(), expr(address), expr(*value)); @@ -740,7 +740,7 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) // We set the current value to unknown anyway to add type constraints. m_context.setUnknownValue(*symbolicVar); if (index > 0) - m_interface.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); + m_interface->addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); } void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) @@ -822,7 +822,7 @@ void SMTChecker::abstractFunctionCall(FunctionCall const& _funCall) smtArguments.push_back(expr(*arg)); defineExpr(_funCall, (*m_context.expression(_funCall.expression()))(smtArguments)); m_uninterpretedTerms.insert(&_funCall); - setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, m_interface); + setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, *m_interface); } void SMTChecker::endVisit(Identifier const& _identifier) @@ -914,7 +914,7 @@ void SMTChecker::endVisit(Literal const& _literal) auto stringType = TypeProvider::stringMemory(); auto stringLit = dynamic_cast(_literal.annotation().type); solAssert(stringLit, ""); - auto result = smt::newSymbolicVariable(*stringType, stringLit->richIdentifier(), m_interface); + auto result = smt::newSymbolicVariable(*stringType, stringLit->richIdentifier(), *m_interface); m_context.createExpression(_literal, result.second); } m_errorReporter.warning( @@ -939,10 +939,10 @@ void SMTChecker::endVisit(Return const& _return) solAssert(components.size() == returnParams.size(), ""); for (unsigned i = 0; i < returnParams.size(); ++i) if (components.at(i)) - m_interface.addAssertion(expr(*components.at(i)) == m_context.newValue(*returnParams.at(i))); + m_interface->addAssertion(expr(*components.at(i)) == m_context.newValue(*returnParams.at(i))); } else if (returnParams.size() == 1) - m_interface.addAssertion(expr(*_return.expression()) == m_context.newValue(*returnParams.front())); + m_interface->addAssertion(expr(*_return.expression()) == m_context.newValue(*returnParams.front())); } } @@ -984,7 +984,7 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) if (_memberAccess.memberName() == "balance") { defineExpr(_memberAccess, m_context.balance(expr(_memberAccess.expression()))); - setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_interface); + setSymbolicUnknownValue(*m_context.expression(_memberAccess), *m_interface); m_uninterpretedTerms.insert(&_memberAccess); return false; } @@ -1031,7 +1031,7 @@ void SMTChecker::endVisit(IndexAccess const& _indexAccess) setSymbolicUnknownValue( expr(_indexAccess), _indexAccess.annotation().type, - m_interface + *m_interface ); m_uninterpretedTerms.insert(&_indexAccess); } @@ -1083,7 +1083,7 @@ void SMTChecker::arrayIndexAssignment(Expression const& _expr, smt::Expression c expr(*indexAccess.indexExpression()), _rightHandSide ); - m_interface.addAssertion(m_context.newValue(*varDecl) == store); + m_interface->addAssertion(m_context.newValue(*varDecl) == store); // Update the SMT select value after the assignment, // necessary for sound models. defineExpr(indexAccess, smt::Expression::select( @@ -1214,7 +1214,7 @@ smt::Expression SMTChecker::arithmeticOperation( if (_op == Token::Div || _op == Token::Mod) { checkCondition(_right == 0, _location, "Division by zero", "", &_right); - m_interface.addAssertion(_right != 0); + m_interface->addAssertion(_right != 0); } addOverflowTarget( @@ -1396,7 +1396,7 @@ void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expressio addOverflowTarget(OverflowTarget::Type::All, TypeProvider::uint(160), _value, _location); else if (type->category() == Type::Category::Mapping) arrayAssignment(); - m_interface.addAssertion(m_context.newValue(_variable) == _value); + m_interface->addAssertion(m_context.newValue(_variable) == _value); } SMTChecker::VariableIndices SMTChecker::visitBranch(ASTNode const* _statement, smt::Expression _condition) @@ -1425,7 +1425,7 @@ void SMTChecker::checkCondition( smt::Expression const* _additionalValue ) { - m_interface.push(); + m_interface->push(); addPathConjoinedExpression(_condition); vector expressionsToEvaluate; @@ -1537,7 +1537,7 @@ void SMTChecker::checkCondition( m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); break; } - m_interface.pop(); + m_interface->pop(); } void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string const& _description) @@ -1546,15 +1546,15 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co if (dynamic_cast(&_condition)) return; - m_interface.push(); + m_interface->push(); addPathConjoinedExpression(expr(_condition)); auto positiveResult = checkSatisfiable(); - m_interface.pop(); + m_interface->pop(); - m_interface.push(); + m_interface->push(); addPathConjoinedExpression(!expr(_condition)); auto negatedResult = checkSatisfiable(); - m_interface.pop(); + m_interface->pop(); if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver."); @@ -1599,7 +1599,7 @@ SMTChecker::checkSatisfiableAndGenerateModel(vector const& _exp vector values; try { - tie(result, values) = m_interface.check(_expressionsToEvaluate); + tie(result, values) = m_interface->check(_expressionsToEvaluate); } catch (smt::SolverError const& _e) { @@ -1635,7 +1635,7 @@ void SMTChecker::initializeFunctionCallParameters(CallableDeclaration const& _fu for (unsigned i = 0; i < funParams.size(); ++i) if (createVariable(*funParams[i])) { - m_interface.addAssertion(_callArgs[i] == m_context.newValue(*funParams[i])); + m_interface->addAssertion(_callArgs[i] == m_context.newValue(*funParams[i])); if (funParams[i]->annotation().type->category() == Type::Category::Mapping) m_arrayAssignmentHappened = true; } @@ -1701,7 +1701,7 @@ void SMTChecker::mergeVariables(set const& _variable int trueIndex = _indicesEndTrue.at(decl); int falseIndex = _indicesEndFalse.at(decl); solAssert(trueIndex != falseIndex, ""); - m_interface.addAssertion(m_context.newValue(*decl) == smt::Expression::ite( + m_interface->addAssertion(m_context.newValue(*decl) == smt::Expression::ite( _condition, valueAtIndex(*decl, trueIndex), valueAtIndex(*decl, falseIndex)) @@ -1761,7 +1761,7 @@ void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) { createExpr(_e); solAssert(smt::smtKind(_e.annotation().type->category()) != smt::Kind::Function, "Equality operator applied to type that is not fully supported"); - m_interface.addAssertion(expr(_e) == _value); + m_interface->addAssertion(expr(_e) == _value); } void SMTChecker::popPathCondition() @@ -1810,12 +1810,12 @@ void SMTChecker::pushCallStack(CallStackEntry _entry) void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e) { - m_interface.addAssertion(currentPathConditions() && _e); + m_interface->addAssertion(currentPathConditions() && _e); } void SMTChecker::addPathImpliedExpression(smt::Expression const& _e) { - m_interface.addAssertion(smt::Expression::implies(currentPathConditions(), _e)); + m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e)); } bool SMTChecker::isRootFunction() diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 661dff342..c3e973528 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -53,7 +53,7 @@ public: /// This is used if the SMT solver is not directly linked into this binary. /// @returns a list of inputs to the SMT solver that were not part of the argument to /// the constructor. - std::vector unhandledQueries() { return m_interface.unhandledQueries(); } + std::vector unhandledQueries() { return m_interface->unhandledQueries(); } /// @returns the FunctionDefinition of a called function if possible and should inline, /// otherwise nullptr. @@ -91,10 +91,6 @@ private: void endVisit(IndexAccess const& _node) override; bool visit(InlineAssembly const& _node) override; - smt::Expression assertions() { return m_interface.assertions(); } - void push() { m_interface.push(); } - void pop() { m_interface.pop(); } - /// Do not visit subtree if node is a RationalNumber. /// Symbolic _expr is the rational literal. bool shortcutRationalNumber(Expression const& _expr); @@ -274,7 +270,7 @@ private: /// @returns the VariableDeclaration referenced by an Identifier or nullptr. VariableDeclaration const* identifierToVariable(Expression const& _expr); - smt::SMTPortfolio m_interface; + std::shared_ptr m_interface; smt::VariableUsage m_variableUsage; bool m_loopExecutionHappened = false; bool m_arrayAssignmentHappened = false; diff --git a/libsolidity/formal/SMTPortfolio.cpp b/libsolidity/formal/SMTPortfolio.cpp index ea2f3d9ee..09a311f4f 100644 --- a/libsolidity/formal/SMTPortfolio.cpp +++ b/libsolidity/formal/SMTPortfolio.cpp @@ -43,21 +43,18 @@ SMTPortfolio::SMTPortfolio(map const& _smtlib2Responses) void SMTPortfolio::reset() { - m_assertions.clear(); for (auto const& s: m_solvers) s->reset(); } void SMTPortfolio::push() { - m_assertions.push_back(Expression(true)); for (auto const& s: m_solvers) s->push(); } void SMTPortfolio::pop() { - m_assertions.pop_back(); for (auto const& s: m_solvers) s->pop(); } @@ -70,23 +67,10 @@ void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort) void SMTPortfolio::addAssertion(Expression const& _expr) { - if (m_assertions.empty()) - m_assertions.push_back(_expr); - else - m_assertions.back() = _expr && move(m_assertions.back()); - for (auto const& s: m_solvers) s->addAssertion(_expr); } -Expression SMTPortfolio::assertions() -{ - if (m_assertions.empty()) - return Expression(true); - - return m_assertions.back(); -} - /* * Broadcasts the SMT query to all solvers and returns a single result. * This comment explains how this result is decided. diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index 1f61ead8d..3eb0a793b 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -53,8 +53,6 @@ public: void addAssertion(Expression const& _expr) override; - Expression assertions(); - std::pair> check(std::vector const& _expressionsToEvaluate) override; std::vector unhandledQueries() override; From 5f072d30df6873832f3ae34fc9c15ae4220d4627 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 20 Jun 2019 18:02:29 +0200 Subject: [PATCH 109/115] Enable yul for more end to end tests --- test/libsolidity/SolidityEndToEndTest.cpp | 1302 ++++++++++++--------- 1 file changed, 753 insertions(+), 549 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index bee191fcb..d1b3a79de 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -82,8 +82,10 @@ BOOST_AUTO_TEST_CASE(exp_operator_const) function f() public returns(uint d) { return 2 ** 3; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(8))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(8))); + ) } BOOST_AUTO_TEST_CASE(exp_operator_const_signed) @@ -93,8 +95,10 @@ BOOST_AUTO_TEST_CASE(exp_operator_const_signed) function f() public returns(int d) { return (-2) ** 3; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(-8))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(-8))); + ) } BOOST_AUTO_TEST_CASE(exp_zero) @@ -115,8 +119,10 @@ BOOST_AUTO_TEST_CASE(exp_zero_literal) function f() public returns(uint d) { return 0 ** 0; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(1))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(1))); + ) } @@ -350,11 +356,13 @@ BOOST_AUTO_TEST_CASE(c99_scoping_activation) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(3)); - ABI_CHECK(callContractFunction("g()"), encodeArgs(0)); - ABI_CHECK(callContractFunction("h()"), encodeArgs(3, 3, 4)); - ABI_CHECK(callContractFunction("i()"), encodeArgs(3, 3)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(0)); + ABI_CHECK(callContractFunction("h()"), encodeArgs(3, 3, 4)); + ABI_CHECK(callContractFunction("i()"), encodeArgs(3, 3)); + ) } BOOST_AUTO_TEST_CASE(recursive_calls) @@ -367,16 +375,18 @@ BOOST_AUTO_TEST_CASE(recursive_calls) } } )"; - compileAndRun(sourceCode); - function recursive_calls_cpp = [&recursive_calls_cpp](u256 const& n) -> u256 - { - if (n <= 1) - return 1; - else - return n * recursive_calls_cpp(n - 1); - }; + ALSO_VIA_YUL( + compileAndRun(sourceCode); + function recursive_calls_cpp = [&recursive_calls_cpp](u256 const& n) -> u256 + { + if (n <= 1) + return 1; + else + return n * recursive_calls_cpp(n - 1); + }; - testContractAgainstCppOnRange("f(uint256)", recursive_calls_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", recursive_calls_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(multiple_functions) @@ -389,12 +399,14 @@ BOOST_AUTO_TEST_CASE(multiple_functions) function f() public returns(uint n) { return 3; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("a()", bytes()), toBigEndian(u256(0))); - ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(1))); - ABI_CHECK(callContractFunction("c()", bytes()), toBigEndian(u256(2))); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(3))); - ABI_CHECK(callContractFunction("i_am_not_there()", bytes()), bytes()); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("a()", bytes()), toBigEndian(u256(0))); + ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(1))); + ABI_CHECK(callContractFunction("c()", bytes()), toBigEndian(u256(2))); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(3))); + ABI_CHECK(callContractFunction("i_am_not_there()", bytes()), bytes()); + ) } BOOST_AUTO_TEST_CASE(named_args) @@ -405,8 +417,10 @@ BOOST_AUTO_TEST_CASE(named_args) function b() public returns (uint r) { r = a({a: 1, b: 2, c: 3}); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ) } BOOST_AUTO_TEST_CASE(disorder_named_args) @@ -417,8 +431,10 @@ BOOST_AUTO_TEST_CASE(disorder_named_args) function b() public returns (uint r) { r = a({c: 3, a: 1, b: 2}); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ) } BOOST_AUTO_TEST_CASE(while_loop) @@ -432,19 +448,21 @@ BOOST_AUTO_TEST_CASE(while_loop) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto while_loop_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - u256 i = 2; - while (i <= n) - nfac *= i++; + auto while_loop_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + u256 i = 2; + while (i <= n) + nfac *= i++; - return nfac; - }; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", while_loop_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", while_loop_cpp, 0, 5); + ) } @@ -459,22 +477,24 @@ BOOST_AUTO_TEST_CASE(do_while_loop) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto do_while_loop_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - u256 i = 2; - do + auto do_while_loop_cpp = [](u256 const& n) -> u256 { - nfac *= i++; - } - while (i <= n); + u256 nfac = 1; + u256 i = 2; + do + { + nfac *= i++; + } + while (i <= n); - return nfac; - }; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", do_while_loop_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", do_while_loop_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(do_while_loop_continue) @@ -493,9 +513,11 @@ BOOST_AUTO_TEST_CASE(do_while_loop_continue) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(42)); + ABI_CHECK(callContractFunction("f()"), encodeArgs(42)); + ) } BOOST_AUTO_TEST_CASE(array_multiple_local_vars) @@ -562,30 +584,32 @@ BOOST_AUTO_TEST_CASE(do_while_loop_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto do_while = [](u256 n) -> u256 - { - u256 i = 0; - do + auto do_while = [](u256 n) -> u256 { - u256 z = n * 2; - if (z < 4) break; - else { - u256 k = z + 1; - if (k < 8) { - n++; - continue; + u256 i = 0; + do + { + u256 z = n * 2; + if (z < 4) break; + else { + u256 k = z + 1; + if (k < 8) { + n++; + continue; + } } - } - if (z > 12) return 0; - n++; - i++; - } while (true); - return 42; - }; + if (z > 12) return 0; + n++; + i++; + } while (true); + return 42; + }; - testContractAgainstCppOnRange("f(uint256)", do_while, 0, 12); + testContractAgainstCppOnRange("f(uint256)", do_while, 0, 12); + ) } BOOST_AUTO_TEST_CASE(nested_loops) @@ -610,33 +634,35 @@ BOOST_AUTO_TEST_CASE(nested_loops) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto nested_loops_cpp = [](u256 n) -> u256 - { - while (n > 1) + auto nested_loops_cpp = [](u256 n) -> u256 { - if (n == 10) - break; - while (n > 5) + while (n > 1) { - if (n == 8) + if (n == 10) break; + while (n > 5) + { + if (n == 8) + break; + n--; + if (n == 6) + continue; + return n; + } n--; - if (n == 6) + if (n == 3) continue; - return n; + break; } - n--; - if (n == 3) - continue; - break; - } - return n; - }; + return n; + }; - testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + ) } BOOST_AUTO_TEST_CASE(nested_loops_multiple_local_vars) @@ -674,33 +700,35 @@ BOOST_AUTO_TEST_CASE(nested_loops_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto nested_loops_cpp = [](u256 n) -> u256 - { - while (n > 0) + auto nested_loops_cpp = [](u256 n) -> u256 { - u256 z = n + 10; - u256 k = z + 1; - if (k > 20) break; - if (k > 15) { - n--; - continue; - } - while (k > 10) + while (n > 0) { - u256 m = k - 1; - if (m == 10) return n; - return k; + u256 z = n + 10; + u256 k = z + 1; + if (k > 20) break; + if (k > 15) { + n--; + continue; + } + while (k > 10) + { + u256 m = k - 1; + if (m == 10) return n; + return k; + } + n--; + break; } - n--; - break; - } - return n; - }; + return n; + }; - testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + ) } BOOST_AUTO_TEST_CASE(for_loop_multiple_local_vars) @@ -726,28 +754,30 @@ BOOST_AUTO_TEST_CASE(for_loop_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop = [](u256 n) -> u256 - { - for (u256 i = 0; i < 12; i++) + auto for_loop = [](u256 n) -> u256 { - u256 z = n + 1; - if (z < 4) break; - else { - u256 k = z * 2; - if (i + k < 10) { - n++; - continue; + for (u256 i = 0; i < 12; i++) + { + u256 z = n + 1; + if (z < 4) break; + else { + u256 k = z * 2; + if (i + k < 10) { + n++; + continue; + } } + if (z > 8) return 0; + n++; } - if (z > 8) return 0; - n++; - } - return 42; - }; + return 42; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + ) } BOOST_AUTO_TEST_CASE(nested_for_loop_multiple_local_vars) @@ -785,30 +815,32 @@ BOOST_AUTO_TEST_CASE(nested_for_loop_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop = [](u256 n) -> u256 - { - for (u256 i = 0; i < 5; i++) + auto for_loop = [](u256 n) -> u256 { - u256 z = n + 1; - if (z < 3) break; - for (u256 j = 0; j < 5; j++) + for (u256 i = 0; i < 5; i++) { - u256 k = z * 2; - if (j + k < 8) { + u256 z = n + 1; + if (z < 3) break; + for (u256 j = 0; j < 5; j++) + { + u256 k = z * 2; + if (j + k < 8) { + n++; + continue; + } n++; - continue; + if (n > 20) return 84; } - n++; - if (n > 20) return 84; + if (n > 30) return 42; } - if (n > 30) return 42; - } - return 42; - }; + return 42; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + ) } BOOST_AUTO_TEST_CASE(for_loop) @@ -823,17 +855,19 @@ BOOST_AUTO_TEST_CASE(for_loop) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - for (auto i = 2; i <= n; i++) - nfac *= i; - return nfac; - }; + auto for_loop_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + for (auto i = 2; i <= n; i++) + nfac *= i; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", for_loop_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(for_loop_empty) @@ -849,20 +883,22 @@ BOOST_AUTO_TEST_CASE(for_loop_empty) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop_empty_cpp = []() -> u256 - { - u256 ret = 1; - for (;;) + auto for_loop_empty_cpp = []() -> u256 { - ret += 1; - if (ret >= 10) break; - } - return ret; - }; + u256 ret = 1; + for (;;) + { + ret += 1; + if (ret >= 10) break; + } + return ret; + }; - testContractAgainstCpp("f()", for_loop_empty_cpp); + testContractAgainstCpp("f()", for_loop_empty_cpp); + ) } BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr) @@ -877,18 +913,20 @@ BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop_simple_init_expr_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - u256 i; - for (i = 2; i <= n; i++) - nfac *= i; - return nfac; - }; + auto for_loop_simple_init_expr_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + u256 i; + for (i = 2; i <= n; i++) + nfac *= i; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop_simple_init_expr_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", for_loop_simple_init_expr_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(for_loop_break_continue) @@ -998,16 +1036,18 @@ BOOST_AUTO_TEST_CASE(many_local_variables) } } )"; - compileAndRun(sourceCode); - auto f = [](u256 const& x1, u256 const& x2, u256 const& x3) -> u256 - { - u256 a = 0x1; - u256 b = 0x10; - u256 c = 0x100; - u256 y = a + b + c + x1 + x2 + x3; - return y + b + x2; - }; - testContractAgainstCpp("run(uint256,uint256,uint256)", f, u256(0x1000), u256(0x10000), u256(0x100000)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + auto f = [](u256 const& x1, u256 const& x2, u256 const& x3) -> u256 + { + u256 a = 0x1; + u256 b = 0x10; + u256 c = 0x100; + u256 y = a + b + c + x1 + x2 + x3; + return y + b + x2; + }; + testContractAgainstCpp("run(uint256,uint256,uint256)", f, u256(0x1000), u256(0x10000), u256(0x100000)); + ) } BOOST_AUTO_TEST_CASE(packing_unpacking_types) @@ -1038,11 +1078,13 @@ BOOST_AUTO_TEST_CASE(packing_signed_types) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("run()"), - fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa") - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("run()"), + fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa") + ); + ) } BOOST_AUTO_TEST_CASE(multiple_return_values) @@ -1054,8 +1096,10 @@ BOOST_AUTO_TEST_CASE(multiple_return_values) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("run(bool,uint256)", true, 0xcd), encodeArgs(0xcd, true, 0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("run(bool,uint256)", true, 0xcd), encodeArgs(0xcd, true, 0)); + ) } BOOST_AUTO_TEST_CASE(short_circuiting) @@ -1068,15 +1112,17 @@ BOOST_AUTO_TEST_CASE(short_circuiting) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto short_circuiting_cpp = [](u256 n) -> u256 - { - (void)(n == 0 || (n = 8) > 0); - return n; - }; + auto short_circuiting_cpp = [](u256 n) -> u256 + { + (void)(n == 0 || (n = 8) > 0); + return n; + }; - testContractAgainstCppOnRange("run(uint256)", short_circuiting_cpp, 0, 2); + testContractAgainstCppOnRange("run(uint256)", short_circuiting_cpp, 0, 2); + ) } BOOST_AUTO_TEST_CASE(high_bits_cleaning) @@ -1766,8 +1812,10 @@ BOOST_AUTO_TEST_CASE(deleteLocal) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(deleteLocals) @@ -1784,8 +1832,10 @@ BOOST_AUTO_TEST_CASE(deleteLocals) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(6, 7)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(6, 7)); + ) } BOOST_AUTO_TEST_CASE(deleteLength) @@ -1801,9 +1851,11 @@ BOOST_AUTO_TEST_CASE(deleteLength) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); - BOOST_CHECK(storageEmpty(m_contractAddress)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + BOOST_CHECK(storageEmpty(m_contractAddress)); + ) } BOOST_AUTO_TEST_CASE(constructor) @@ -1819,15 +1871,19 @@ BOOST_AUTO_TEST_CASE(constructor) } } )"; - compileAndRun(sourceCode); + map data; data[7] = 8; auto get = [&](u256 const& _x) -> u256 { return data[_x]; }; - testContractAgainstCpp("get(uint256)", get, u256(6)); - testContractAgainstCpp("get(uint256)", get, u256(7)); + + ALSO_VIA_YUL( + compileAndRun(sourceCode); + testContractAgainstCpp("get(uint256)", get, u256(6)); + testContractAgainstCpp("get(uint256)", get, u256(7)); + ) } BOOST_AUTO_TEST_CASE(simple_accessor) @@ -1975,8 +2031,10 @@ BOOST_AUTO_TEST_CASE(balance) } } )"; - compileAndRun(sourceCode, 23); - ABI_CHECK(callContractFunction("getBalance()"), encodeArgs(23)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 23); + ABI_CHECK(callContractFunction("getBalance()"), encodeArgs(23)); + ) } BOOST_AUTO_TEST_CASE(blockchain) @@ -2006,8 +2064,10 @@ BOOST_AUTO_TEST_CASE(msg_sig) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ) } BOOST_AUTO_TEST_CASE(msg_sig_after_internal_call_is_same) @@ -2022,8 +2082,10 @@ BOOST_AUTO_TEST_CASE(msg_sig_after_internal_call_is_same) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ) } BOOST_AUTO_TEST_CASE(now) @@ -2036,15 +2098,17 @@ BOOST_AUTO_TEST_CASE(now) } } )"; - compileAndRun(sourceCode); - u256 startBlock = m_blockNumber; - size_t startTime = blockTimestamp(startBlock); - auto ret = callContractFunction("someInfo()"); - u256 endBlock = m_blockNumber; - size_t endTime = blockTimestamp(endBlock); - BOOST_CHECK(startBlock != endBlock); - BOOST_CHECK(startTime != endTime); - ABI_CHECK(ret, encodeArgs(true, endTime)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 startBlock = m_blockNumber; + size_t startTime = blockTimestamp(startBlock); + auto ret = callContractFunction("someInfo()"); + u256 endBlock = m_blockNumber; + size_t endTime = blockTimestamp(endBlock); + BOOST_CHECK(startBlock != endBlock); + BOOST_CHECK(startTime != endTime); + ABI_CHECK(ret, encodeArgs(true, endTime)); + ) } BOOST_AUTO_TEST_CASE(type_conversions_cleanup) @@ -2056,10 +2120,12 @@ BOOST_AUTO_TEST_CASE(type_conversions_cleanup) function test() public returns (uint ret) { return uint(address(Test(address(0x11223344556677889900112233445566778899001122)))); } } )"; - compileAndRun(sourceCode); - BOOST_REQUIRE(callContractFunction("test()") == bytes({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22})); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_REQUIRE(callContractFunction("test()") == bytes({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, + 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22})); + ) } // fixed bytes to fixed bytes conversion tests BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_smaller_size) @@ -2071,8 +2137,10 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_smaller_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("ab")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("ab")); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_greater_size) @@ -2084,8 +2152,10 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_greater_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("bytesToBytes(bytes2)", "ab"), encodeArgs("ab")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("bytesToBytes(bytes2)", "ab"), encodeArgs("ab")); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_same_size) @@ -2097,8 +2167,10 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_same_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("abcd")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("abcd")); + ) } // fixed bytes to uint conversion tests @@ -2111,11 +2183,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes32)", string("abc2")), - encodeArgs(u256("0x6162633200000000000000000000000000000000000000000000000000000000")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes32)", string("abc2")), + encodeArgs(u256("0x6162633200000000000000000000000000000000000000000000000000000000")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_min_size) @@ -2127,11 +2201,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_min_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes1)", string("a")), - encodeArgs(u256("0x61")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes1)", string("a")), + encodeArgs(u256("0x61")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_smaller_size) @@ -2143,11 +2219,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_smaller_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes4)", string("abcd")), - encodeArgs(u256("0x6364")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes4)", string("abcd")), + encodeArgs(u256("0x6364")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_greater_size) @@ -2159,11 +2237,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_greater_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes4)", string("abcd")), - encodeArgs(u256("0x61626364")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes4)", string("abcd")), + encodeArgs(u256("0x61626364")) + ); + ) } // uint fixed bytes conversion tests @@ -2176,9 +2256,11 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_size) } } )"; - compileAndRun(sourceCode); - u256 a("0x6162630000000000000000000000000000000000000000000000000000000000"); - ABI_CHECK(callContractFunction("uintToBytes(uint256)", a), encodeArgs(a)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 a("0x6162630000000000000000000000000000000000000000000000000000000000"); + ABI_CHECK(callContractFunction("uintToBytes(uint256)", a), encodeArgs(a)); + ) } BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_min_size) @@ -2190,11 +2272,13 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_min_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("UintToBytes(uint8)", u256("0x61")), - encodeArgs(string("a")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("UintToBytes(uint8)", u256("0x61")), + encodeArgs(string("a")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_smaller_size) @@ -2206,11 +2290,13 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_smaller_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("uintToBytes(uint32)", u160("0x61626364")), - encodeArgs(string("cd")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("uintToBytes(uint32)", u160("0x61626364")), + encodeArgs(string("cd")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_greater_size) @@ -2222,11 +2308,13 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_greater_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("UintToBytes(uint16)", u256("0x6162")), - encodeArgs(string("\0\0\0\0\0\0ab", 8)) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("UintToBytes(uint16)", u256("0x6162")), + encodeArgs(string("\0\0\0\0\0\0ab", 8)) + ); + ) } BOOST_AUTO_TEST_CASE(send_ether) @@ -2307,8 +2395,10 @@ BOOST_AUTO_TEST_CASE(blockhash_shadow_resolution) function f() public returns(bytes32) { return blockhash(3); } } )"; - compileAndRun(code, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(code, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(log0) @@ -2931,8 +3021,10 @@ BOOST_AUTO_TEST_CASE(functions_called_by_constructor) function setName(bytes3 _name) private { name = _name; } } )"; - compileAndRun(sourceCode); - BOOST_REQUIRE(callContractFunction("getName()") == encodeArgs("abc")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_REQUIRE(callContractFunction("getName()") == encodeArgs("abc")); + ) } BOOST_AUTO_TEST_CASE(contracts_as_addresses) @@ -3021,9 +3113,11 @@ BOOST_AUTO_TEST_CASE(gaslimit) } } )"; - compileAndRun(sourceCode); - auto result = callContractFunction("f()"); - ABI_CHECK(result, encodeArgs(gasLimit())); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + auto result = callContractFunction("f()"); + ABI_CHECK(result, encodeArgs(gasLimit())); + ) } BOOST_AUTO_TEST_CASE(gasprice) @@ -3035,8 +3129,10 @@ BOOST_AUTO_TEST_CASE(gasprice) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(gasPrice())); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(gasPrice())); + ) } BOOST_AUTO_TEST_CASE(blockhash) @@ -3151,9 +3247,11 @@ BOOST_AUTO_TEST_CASE(virtual_function_calls) function g() public returns (uint i) { return 2; } } )"; - compileAndRun(sourceCode, 0, "Derived"); - ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); - ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Derived"); + ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(access_base_storage) @@ -3176,10 +3274,12 @@ BOOST_AUTO_TEST_CASE(access_base_storage) } } )"; - compileAndRun(sourceCode, 0, "Derived"); - ABI_CHECK(callContractFunction("setData(uint256,uint256)", 1, 2), encodeArgs(true)); - ABI_CHECK(callContractFunction("getViaBase()"), encodeArgs(1)); - ABI_CHECK(callContractFunction("getViaDerived()"), encodeArgs(1, 2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Derived"); + ABI_CHECK(callContractFunction("setData(uint256,uint256)", 1, 2), encodeArgs(true)); + ABI_CHECK(callContractFunction("getViaBase()"), encodeArgs(1)); + ABI_CHECK(callContractFunction("getViaDerived()"), encodeArgs(1, 2)); + ) } BOOST_AUTO_TEST_CASE(single_copy_with_multiple_inheritance) @@ -3194,10 +3294,12 @@ BOOST_AUTO_TEST_CASE(single_copy_with_multiple_inheritance) contract B is Base { function getViaB() public returns (uint i) { return getViaBase(); } } contract Derived is Base, B, A { } )"; - compileAndRun(sourceCode, 0, "Derived"); - ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(0)); - ABI_CHECK(callContractFunction("setViaA(uint256)", 23), encodeArgs()); - ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(23)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Derived"); + ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(0)); + ABI_CHECK(callContractFunction("setViaA(uint256)", 23), encodeArgs()); + ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(23)); + ) } BOOST_AUTO_TEST_CASE(explicit_base_class) @@ -3502,8 +3604,10 @@ BOOST_AUTO_TEST_CASE(crazy_elementary_typenames_on_stack) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(-7))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(-7))); + ) } BOOST_AUTO_TEST_CASE(super) @@ -3535,8 +3639,10 @@ BOOST_AUTO_TEST_CASE(super_alone) char const* sourceCode = R"( contract A { function f() public { super; } } )"; - compileAndRun(sourceCode, 0, "A"); - ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "A"); + ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(fallback_function) @@ -3548,10 +3654,12 @@ BOOST_AUTO_TEST_CASE(fallback_function) function getData() public returns (uint r) { return data; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); - ABI_CHECK(callContractFunction(""), encodeArgs()); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); + ABI_CHECK(callContractFunction(""), encodeArgs()); + ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); + ) } BOOST_AUTO_TEST_CASE(inherited_fallback_function) @@ -3564,10 +3672,12 @@ BOOST_AUTO_TEST_CASE(inherited_fallback_function) } contract B is A {} )"; - compileAndRun(sourceCode, 0, "B"); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); - ABI_CHECK(callContractFunction(""), encodeArgs()); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "B"); + ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); + ABI_CHECK(callContractFunction(""), encodeArgs()); + ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); + ) } BOOST_AUTO_TEST_CASE(default_fallback_throws) @@ -3662,7 +3772,7 @@ BOOST_AUTO_TEST_CASE(event_emit) } )"; ALSO_VIA_YUL( - compileAndRun(sourceCode); + compileAndRun(sourceCode); u256 value(18); u256 id(0x1234); callContractFunctionWithValue("deposit(bytes32)", value, id); @@ -3746,9 +3856,9 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) } } )"; - ALSO_VIA_YUL( - u160 const c_loggedAddress = m_contractAddress; + u160 const c_loggedAddress = m_contractAddress; + ALSO_VIA_YUL( compileAndRun(sourceCode); ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); @@ -3807,9 +3917,9 @@ BOOST_AUTO_TEST_CASE(events_with_same_name_inherited_emit) } } )"; - ALSO_VIA_YUL( - u160 const c_loggedAddress = m_contractAddress; + u160 const c_loggedAddress = m_contractAddress; + ALSO_VIA_YUL( compileAndRun(sourceCode); ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); @@ -4106,14 +4216,16 @@ BOOST_AUTO_TEST_CASE(event_dynamic_array_storage) } } )"; - compileAndRun(sourceCode); - u256 x(42); - callContractFunction("createEvent(uint256)", x); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 x(42); + callContractFunction("createEvent(uint256)", x); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ) } BOOST_AUTO_TEST_CASE(event_dynamic_array_storage_v2) @@ -4132,14 +4244,16 @@ BOOST_AUTO_TEST_CASE(event_dynamic_array_storage_v2) } } )"; - compileAndRun(sourceCode); - u256 x(42); - callContractFunction("createEvent(uint256)", x); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 x(42); + callContractFunction("createEvent(uint256)", x); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ) } BOOST_AUTO_TEST_CASE(event_dynamic_nested_array_storage_v2) @@ -4217,9 +4331,11 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter_with_named_one) } } )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("f(uint256,uint256)", 5, 9) != encodeArgs(5, 8)); - ABI_CHECK(callContractFunction("f(uint256,uint256)", 5, 9), encodeArgs(9, 8)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", 5, 9) != encodeArgs(5, 8)); + ABI_CHECK(callContractFunction("f(uint256,uint256)", 5, 9), encodeArgs(9, 8)); + ) } BOOST_AUTO_TEST_CASE(empty_name_return_parameter) @@ -4231,8 +4347,10 @@ BOOST_AUTO_TEST_CASE(empty_name_return_parameter) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f(uint256)", 9), encodeArgs(9)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint256)", 9), encodeArgs(9)); + ) } BOOST_AUTO_TEST_CASE(sha256_empty) @@ -4947,14 +5065,16 @@ BOOST_AUTO_TEST_CASE(enum_explicit_overflow) ActionChoices choice; } )"; - compileAndRun(sourceCode); - // These should throw - ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 3), encodeArgs()); - ABI_CHECK(callContractFunction("getChoiceFromSigned(int256)", -1), encodeArgs()); - ABI_CHECK(callContractFunction("getChoiceFromNegativeLiteral()"), encodeArgs()); - // These should work - ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 2), encodeArgs(2)); - ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 0), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + // These should throw + ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 3), encodeArgs()); + ABI_CHECK(callContractFunction("getChoiceFromSigned(int256)", -1), encodeArgs()); + ABI_CHECK(callContractFunction("getChoiceFromNegativeLiteral()"), encodeArgs()); + // These should work + ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 2), encodeArgs(2)); + ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 0), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(storing_invalid_boolean) @@ -5063,8 +5183,10 @@ BOOST_AUTO_TEST_CASE(constructing_enums_from_ints) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("test()"), encodeArgs(1)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("test()"), encodeArgs(1)); + ) } BOOST_AUTO_TEST_CASE(struct_referencing) @@ -5194,7 +5316,6 @@ BOOST_AUTO_TEST_CASE(inline_member_init) compileAndRun(sourceCode); ABI_CHECK(callContractFunction("get()"), encodeArgs(5, 6, 8)); } - BOOST_AUTO_TEST_CASE(inline_member_init_inheritence) { char const* sourceCode = R"( @@ -5242,8 +5363,10 @@ BOOST_AUTO_TEST_CASE(external_function) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("test(uint256,uint256)", 2, 3), encodeArgs(2+7, 3)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("test(uint256,uint256)", 2, 3), encodeArgs(2+7, 3)); + ) } BOOST_AUTO_TEST_CASE(bytes_in_arguments) @@ -5344,15 +5467,17 @@ BOOST_AUTO_TEST_CASE(fixed_out_of_bounds_array_access) function length() public returns (uint) { return data.length; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 5), bytes()); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 400, 5), bytes()); - ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); - ABI_CHECK(callContractFunction("get(uint256)", 4), bytes()); - ABI_CHECK(callContractFunction("get(uint256)", 400), bytes()); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 5), bytes()); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 400, 5), bytes()); + ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); + ABI_CHECK(callContractFunction("get(uint256)", 4), bytes()); + ABI_CHECK(callContractFunction("get(uint256)", 400), bytes()); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ) } BOOST_AUTO_TEST_CASE(dynamic_out_of_bounds_array_access) @@ -5366,16 +5491,18 @@ BOOST_AUTO_TEST_CASE(dynamic_out_of_bounds_array_access) function length() public returns (uint) { return data.length; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("length()"), encodeArgs(0)); - ABI_CHECK(callContractFunction("get(uint256)", 3), bytes()); - ABI_CHECK(callContractFunction("enlarge(uint256)", 4), encodeArgs(4)); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); - ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 8), bytes()); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("length()"), encodeArgs(0)); + ABI_CHECK(callContractFunction("get(uint256)", 3), bytes()); + ABI_CHECK(callContractFunction("enlarge(uint256)", 4), encodeArgs(4)); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); + ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 8), bytes()); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ) } BOOST_AUTO_TEST_CASE(fixed_array_cleanup) @@ -6486,7 +6613,9 @@ BOOST_AUTO_TEST_CASE(constant_variables) bytes32 constant st = "abc\x00\xff__"; } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ) } BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_expression) @@ -6673,8 +6802,10 @@ BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_first) function g() public returns(uint d) { return f(3); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("g()"), encodeArgs(3)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("g()"), encodeArgs(3)); + ) } BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_second) @@ -6686,8 +6817,10 @@ BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_second) function g() public returns(uint d) { return f(3, 7); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); + ) } BOOST_AUTO_TEST_CASE(overloaded_function_call_with_if_else) @@ -6704,9 +6837,11 @@ BOOST_AUTO_TEST_CASE(overloaded_function_call_with_if_else) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(3)); - ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs(10)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(3)); + ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs(10)); + ) } BOOST_AUTO_TEST_CASE(derived_overload_base_function_direct) @@ -6718,8 +6853,10 @@ BOOST_AUTO_TEST_CASE(derived_overload_base_function_direct) function g() public returns(uint) { return f(1); } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(derived_overload_base_function_indirect) @@ -6732,9 +6869,11 @@ BOOST_AUTO_TEST_CASE(derived_overload_base_function_indirect) function h() public returns(uint) { return f(1); } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); - ABI_CHECK(callContractFunction("h()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); + ABI_CHECK(callContractFunction("h()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(super_overload) @@ -6760,8 +6899,10 @@ BOOST_AUTO_TEST_CASE(gasleft_shadow_resolution) function f() public returns(uint256) { return gasleft(); } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(bool_conversion) @@ -6958,11 +7099,13 @@ BOOST_AUTO_TEST_CASE(invalid_enum_as_external_ret) } } )"; - compileAndRun(sourceCode, 0, "C"); - // both should throw - ABI_CHECK(callContractFunction("test_return()"), encodeArgs()); - ABI_CHECK(callContractFunction("test_inline_assignment()"), encodeArgs()); - ABI_CHECK(callContractFunction("test_assignment()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + // both should throw + ABI_CHECK(callContractFunction("test_return()"), encodeArgs()); + ABI_CHECK(callContractFunction("test_inline_assignment()"), encodeArgs()); + ABI_CHECK(callContractFunction("test_assignment()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(invalid_enum_as_external_arg) @@ -8738,28 +8881,32 @@ BOOST_AUTO_TEST_CASE(string_as_mapping_key) function get(string memory _s) public returns (uint) { return data[_s]; } } )"; - compileAndRun(sourceCode, 0, "Test"); + vector strings{ "Hello, World!", "Hello, World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1111", "", "1" }; - for (unsigned i = 0; i < strings.size(); i++) - ABI_CHECK(callContractFunction( - "set(string,uint256)", - u256(0x40), - u256(7 + i), - u256(strings[i].size()), - strings[i] - ), encodeArgs()); - for (unsigned i = 0; i < strings.size(); i++) - ABI_CHECK(callContractFunction( - "get(string)", - u256(0x20), - u256(strings[i].size()), - strings[i] - ), encodeArgs(u256(7 + i))); + + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Test"); + for (unsigned i = 0; i < strings.size(); i++) + ABI_CHECK(callContractFunction( + "set(string,uint256)", + u256(0x40), + u256(7 + i), + u256(strings[i].size()), + strings[i] + ), encodeArgs()); + for (unsigned i = 0; i < strings.size(); i++) + ABI_CHECK(callContractFunction( + "get(string)", + u256(0x20), + u256(strings[i].size()), + strings[i] + ), encodeArgs(u256(7 + i))); + ) } BOOST_AUTO_TEST_CASE(string_as_public_mapping_key) @@ -9995,8 +10142,10 @@ BOOST_AUTO_TEST_CASE(lone_struct_array_type) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(3))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(3))); + ) } BOOST_AUTO_TEST_CASE(create_memory_array) @@ -10534,9 +10683,11 @@ BOOST_AUTO_TEST_CASE(byte_optimization_bug) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0))); - ABI_CHECK(callContractFunction("g(uint256)", u256(2)), encodeArgs(u256(2))); + ALSO_VIA_YUL( + 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) @@ -10548,8 +10699,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_write_to_stack) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7), string("abcdef"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7), string("abcdef"))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_read_and_write_stack) @@ -10562,8 +10715,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_read_and_write_stack) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(45))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(45))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_memory_access) @@ -10683,8 +10838,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_function_call_assignment) @@ -10708,8 +10865,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call_assignment) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_function_call2) @@ -10734,8 +10893,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call2) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7), u256(0x10))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7), u256(0x10))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call) @@ -10761,8 +10922,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_if) @@ -10776,11 +10939,13 @@ BOOST_AUTO_TEST_CASE(inline_assembly_if) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_switch) @@ -10797,11 +10962,13 @@ BOOST_AUTO_TEST_CASE(inline_assembly_switch) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(8))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(9))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(8))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(9))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_recursion) @@ -10821,12 +10988,14 @@ BOOST_AUTO_TEST_CASE(inline_assembly_recursion) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); - ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); + ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_for) @@ -10846,12 +11015,14 @@ BOOST_AUTO_TEST_CASE(inline_assembly_for) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); - ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); + ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_for2) @@ -10872,10 +11043,12 @@ BOOST_AUTO_TEST_CASE(inline_assembly_for2) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0), u256(2), u256(0))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1), u256(4), u256(3))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0), u256(2), u256(0))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0), u256(2), u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1), u256(4), u256(3))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0), u256(2), u256(0))); + ) } BOOST_AUTO_TEST_CASE(index_access_with_type_conversion) @@ -11057,7 +11230,6 @@ BOOST_AUTO_TEST_CASE(cleanup_bytes_types) bool v2 = dev::test::Options::get().useABIEncoderV2; ABI_CHECK(callContractFunction("f(bytes2,uint16)", string("abc"), u256(0x040102)), v2 ? encodeArgs() : encodeArgs(0)); } - BOOST_AUTO_TEST_CASE(cleanup_bytes_types_shortening) { char const* sourceCode = R"( @@ -11075,7 +11247,6 @@ BOOST_AUTO_TEST_CASE(cleanup_bytes_types_shortening) compileAndRun(sourceCode, 0, "C"); ABI_CHECK(callContractFunction("f()"), encodeArgs("\xff\xff\xff\xff")); } - BOOST_AUTO_TEST_CASE(cleanup_address_types) { // Checks that address types are properly cleaned before they are compared. @@ -11121,9 +11292,11 @@ BOOST_AUTO_TEST_CASE(cleanup_address_types_shortening) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); - ABI_CHECK(callContractFunction("g()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); + ABI_CHECK(callContractFunction("g()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); + ) } BOOST_AUTO_TEST_CASE(skip_dynamic_types) @@ -11461,7 +11634,9 @@ BOOST_AUTO_TEST_CASE(payable_constructor) constructor() public payable { } } )"; - compileAndRun(sourceCode, 27, "C"); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 27, "C"); + ) } BOOST_AUTO_TEST_CASE(payable_function) @@ -11550,9 +11725,11 @@ BOOST_AUTO_TEST_CASE(no_nonpayable_circumvention_by_modifier) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunctionWithValue("f()", 27), encodeArgs()); - BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 0); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionWithValue("f()", 27), encodeArgs()); + BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 0); + ) } BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) @@ -11904,8 +12081,10 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) } )"; - compileAndRun(sourceCode, 0, "test"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "test"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(2))); + ) } BOOST_AUTO_TEST_CASE(mapping_of_functions) @@ -12067,8 +12246,10 @@ BOOST_AUTO_TEST_CASE(function_delete_stack) } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("test()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(copy_function_storage_array) @@ -12524,19 +12705,21 @@ BOOST_AUTO_TEST_CASE(shift_right_negative_literal) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f1()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f2()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f3()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f4()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f5()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f6()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g1()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g2()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g3()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g4()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g5()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g6()"), encodeArgs(true)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f1()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f2()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f3()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f4()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f5()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f6()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g1()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g2()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g3()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g4()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g5()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g6()"), encodeArgs(true)); + ) } BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_int8) @@ -12923,8 +13106,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_in_modifiers) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); + ) } BOOST_AUTO_TEST_CASE(packed_storage_overflow) @@ -12955,8 +13140,10 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment) **/ contract C2 {} )"; - compileAndRun(sourceCode, 0, "C1"); - compileAndRun(sourceCode, 0, "C2"); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C1"); + compileAndRun(sourceCode, 0, "C2"); + ) } @@ -13022,8 +13209,10 @@ BOOST_AUTO_TEST_CASE(invalid_instruction) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(assert_require) @@ -13043,12 +13232,14 @@ BOOST_AUTO_TEST_CASE(assert_require) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs()); - ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs()); - ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(true)); - ABI_CHECK(callContractFunction("h(bool)", false), encodeArgs()); - ABI_CHECK(callContractFunction("h(bool)", true), encodeArgs(true)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs()); + ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(true)); + ABI_CHECK(callContractFunction("h(bool)", false), encodeArgs()); + ABI_CHECK(callContractFunction("h(bool)", true), encodeArgs(true)); + ) } BOOST_AUTO_TEST_CASE(revert) @@ -13401,13 +13592,15 @@ BOOST_AUTO_TEST_CASE(scientific_notation) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(20000000000))); - ABI_CHECK(callContractFunction("g()"), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("h()"), encodeArgs(u256(25))); - ABI_CHECK(callContractFunction("i()"), encodeArgs(u256(-20000000000))); - ABI_CHECK(callContractFunction("j()"), encodeArgs(u256(-2))); - ABI_CHECK(callContractFunction("k()"), encodeArgs(u256(-25))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(20000000000))); + ABI_CHECK(callContractFunction("g()"), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("h()"), encodeArgs(u256(25))); + ABI_CHECK(callContractFunction("i()"), encodeArgs(u256(-20000000000))); + ABI_CHECK(callContractFunction("j()"), encodeArgs(u256(-2))); + ABI_CHECK(callContractFunction("k()"), encodeArgs(u256(-25))); + ) } BOOST_AUTO_TEST_CASE(interface_contract) @@ -13456,8 +13649,10 @@ BOOST_AUTO_TEST_CASE(keccak256_assembly) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); + ) } BOOST_AUTO_TEST_CASE(multi_modifiers) @@ -13501,8 +13696,10 @@ BOOST_AUTO_TEST_CASE(inlineasm_empty_let) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0), u256(0))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0), u256(0))); + ) } BOOST_AUTO_TEST_CASE(bare_call_invalid_address) @@ -14927,18 +15124,20 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople) } } )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256(1), u256(2)) == encodeArgs(u256(4))); - BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"))); - BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256(1), u256(2)) == encodeArgs(u256(4))); + BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"))); + BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + ) } BOOST_AUTO_TEST_CASE(bitwise_shifting_constants_constantinople) @@ -14997,13 +15196,15 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constants_constantinople) } } )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("shl_1()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shl_2()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shl_3()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr_1()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr_2()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr_3()") == encodeArgs(u256(1))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("shl_1()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shl_2()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shl_3()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr_1()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr_2()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr_3()") == encodeArgs(u256(1))); + ) } BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople_combined) @@ -15087,44 +15288,46 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople_combined) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff"))); - BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff"))); + BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shl_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff0000"))); - BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000"))); - BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x00007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff0000"))); + BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000"))); + BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x00007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + ) } BOOST_AUTO_TEST_CASE(senders_balance) @@ -15411,7 +15614,6 @@ BOOST_AUTO_TEST_CASE(flipping_sign_tests) compileAndRun(sourceCode); ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); } - BOOST_AUTO_TEST_CASE(external_public_override) { char const* sourceCode = R"( @@ -15423,9 +15625,11 @@ BOOST_AUTO_TEST_CASE(external_public_override) function g() public returns (uint) { return f(); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); - ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(base_access_to_function_type_variables) From 7b3aba81fb39b2e34819f528745e19a67ff32821 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 24 Jun 2019 15:31:32 +0200 Subject: [PATCH 110/115] Adds a test to check_style.sh to check for spaces before colon (:) in range based for loops. --- libsolidity/ast/ASTJsonConverter.cpp | 4 +--- scripts/check_style.sh | 1 + solc/CommandLineInterface.cpp | 5 +---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 36540bc08..d791a8a06 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -456,15 +456,13 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node) bool ASTJsonConverter::visit(InlineAssembly const& _node) { Json::Value externalReferences(Json::arrayValue); - for (auto const& it : _node.annotation().externalReferences) - { + for (auto const& it: _node.annotation().externalReferences) if (it.first) { Json::Value tuple(Json::objectValue); tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it); externalReferences.append(tuple); } - } setJsonNode(_node, "InlineAssembly", { make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))), make_pair("externalReferences", std::move(externalReferences)) diff --git a/scripts/check_style.sh b/scripts/check_style.sh index 01e50662a..afecc06bc 100755 --- a/scripts/check_style.sh +++ b/scripts/check_style.sh @@ -18,6 +18,7 @@ fi FORMATERROR=$( ( git grep -nIE "\<(if|for)\(" -- '*.h' '*.cpp' # no space after "if" or "for" + git grep -nIE "\\s*\([^=]*\>\s:\s.*\)" -- '*.h' '*.cpp' # no space before range based for-loop git grep -nIE "\\s*\(.*\)\s*\{\s*$" -- '*.h' '*.cpp' # "{\n" on same line as "if" / "for" git grep -nIE "[,\(<]\s*const " -- '*.h' '*.cpp' # const on left side of type git grep -nIE "^\s*(static)?\s*const " -- '*.h' '*.cpp' # const on left side of type (beginning of line) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index b88284b08..e49359ecd 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -1102,8 +1102,7 @@ void CommandLineInterface::handleAst(string const& _argStr) for (auto const& sourceCode: m_sourceCodes) asts.push_back(&m_compiler->ast(sourceCode.first)); map gasCosts; - for (auto const& contract : m_compiler->contractNames()) - { + for (auto const& contract: m_compiler->contractNames()) if (auto const* assemblyItems = m_compiler->runtimeAssemblyItems(contract)) { auto ret = GasEstimator::breakToStatementLevel( @@ -1114,8 +1113,6 @@ void CommandLineInterface::handleAst(string const& _argStr) gasCosts[it.first] += it.second; } - } - bool legacyFormat = !m_args.count(g_argAstCompactJson); if (m_args.count(g_argOutputDir)) { From 85b6e7508c91ae59253d7ef80afe456db887056f Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Fri, 21 Jun 2019 11:03:10 +0200 Subject: [PATCH 111/115] Clarify what owning means Updates from review --- docs/introduction-to-smart-contracts.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 3e744803c..f59200b24 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -42,8 +42,7 @@ data (its *state*) that resides at a specific address on the Ethereum blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot in a database that you can query and alter by calling functions of the -code that manages the database. In the case of Ethereum, this is always the owning -contract. In this case, the functions ``set`` and ``get`` can be used to modify +code that manages the database. In this example, the contract defines the functions ``set`` and ``get`` that can be used to modify or retrieve the value of the variable. To access a state variable, you do not need the prefix ``this.`` as is common in From f58bd5551e2879bca27c99d80ed316da7d8a2b6f Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 25 Jun 2019 12:16:43 +0200 Subject: [PATCH 112/115] Test for sign bit chopping. --- .../semanticTests/storage/chop_sign_bits.sol | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/libsolidity/semanticTests/storage/chop_sign_bits.sol diff --git a/test/libsolidity/semanticTests/storage/chop_sign_bits.sol b/test/libsolidity/semanticTests/storage/chop_sign_bits.sol new file mode 100644 index 000000000..51cba0261 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/chop_sign_bits.sol @@ -0,0 +1,29 @@ +contract Test { + int16[] public x = [-1, -2]; + int16[2] public y = [-5, -6]; + int16 z; + function f() public returns (int16[] memory) { + int8[] memory t = new int8[](2); + t[0] = -3; + t[1] = -4; + x = t; + return x; + } + function g() public returns (int16[2] memory) { + int8[2] memory t = [-3, -4]; + y = t; + return y; + } + function h(int8 t) public returns (int16) { + z = t; + return z; + } +} +// ---- +// x(uint256): 0 -> -1 +// x(uint256): 1 -> -2 +// y(uint256): 0 -> -5 +// y(uint256): 1 -> -6 +// f() -> 0x20, 2, -3, -4 +// g() -> -3, -4 +// h(int8): -10 -> -10 From fe9aa59b8f0e0e4d800dff2ec0763c1aeb625f90 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 25 Jun 2019 12:02:38 +0200 Subject: [PATCH 113/115] Fix conversion during storing. --- libsolidity/codegen/CompilerUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 77534ee34..700b8db16 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -846,9 +846,9 @@ void CompilerUtils::convertType( cleanHigherOrderBits(targetType); if (chopSignBitsPending) { - if (typeOnStack.numBits() < 256) + if (targetType.numBits() < 256) m_context - << ((u256(1) << typeOnStack.numBits()) - 1) + << ((u256(1) << targetType.numBits()) - 1) << Instruction::AND; chopSignBitsPending = false; } From 04fe3c070d01d0cd996bce88ae9d8ce5f64800c3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 25 Jun 2019 12:46:19 +0200 Subject: [PATCH 114/115] Changelog and bug list entry. --- Changelog.md | 1 + docs/bugs.json | 8 ++++++++ docs/bugs_by_version.json | 30 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/Changelog.md b/Changelog.md index aef2625f3..a312e1721 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Important Bugfixes: * Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots + * Properly zero out higher order bits in elements of an array of negative numbers when assigning to storage and converting the type at the same time. Compiler Features: diff --git a/docs/bugs.json b/docs/bugs.json index 94b1ad77a..e4c3bbcc1 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,12 @@ [ + { + "name": "SignedArrayStorageCopy", + "summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.", + "description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.", + "introduced": "0.4.7", + "fixed": "0.5.10", + "severity": "low/medium" + }, { "name": "ABIEncoderV2StorageArrayWithMultiSlotElement", "summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index b3731f0b9..95128aa74 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -380,6 +380,7 @@ }, "0.4.10": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -394,6 +395,7 @@ }, "0.4.11": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -407,6 +409,7 @@ }, "0.4.12": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -419,6 +422,7 @@ }, "0.4.13": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -431,6 +435,7 @@ }, "0.4.14": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -442,6 +447,7 @@ }, "0.4.15": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -452,6 +458,7 @@ }, "0.4.16": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -464,6 +471,7 @@ }, "0.4.17": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -477,6 +485,7 @@ }, "0.4.18": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -489,6 +498,7 @@ }, "0.4.19": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -518,6 +528,7 @@ }, "0.4.20": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -531,6 +542,7 @@ }, "0.4.21": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -544,6 +556,7 @@ }, "0.4.22": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -557,6 +570,7 @@ }, "0.4.23": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -569,6 +583,7 @@ }, "0.4.24": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -581,6 +596,7 @@ }, "0.4.25": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", @@ -591,6 +607,7 @@ }, "0.4.26": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2" ], @@ -658,6 +675,7 @@ }, "0.4.7": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -672,6 +690,7 @@ }, "0.4.8": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -686,6 +705,7 @@ }, "0.4.9": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -700,6 +720,7 @@ }, "0.5.0": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -710,6 +731,7 @@ }, "0.5.1": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -720,6 +742,7 @@ }, "0.5.2": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -730,6 +753,7 @@ }, "0.5.3": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -740,6 +764,7 @@ }, "0.5.4": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -750,6 +775,7 @@ }, "0.5.5": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -762,6 +788,7 @@ }, "0.5.6": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -773,6 +800,7 @@ }, "0.5.7": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", @@ -782,6 +810,7 @@ }, "0.5.8": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2" ], @@ -789,6 +818,7 @@ }, "0.5.9": { "bugs": [ + "SignedArrayStorageCopy", "ABIEncoderV2StorageArrayWithMultiSlotElement" ], "released": "2019-05-28" From a03687ccfab6ef4a5a9c417b0da67d6eaac2deef Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 25 Jun 2019 11:29:33 +0200 Subject: [PATCH 115/115] Prepare changelog for 0.5.10 release. --- Changelog.md | 10 +++++----- docs/bugs_by_version.json | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Changelog.md b/Changelog.md index a312e1721..63879d665 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,8 +1,8 @@ -### 0.5.10 (unreleased) +### 0.5.10 (2019-06-25) Important Bugfixes: - * Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots - * Properly zero out higher order bits in elements of an array of negative numbers when assigning to storage and converting the type at the same time. + * ABIEncoderV2: Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots + * Code Generator: Properly zero out higher order bits in elements of an array of negative numbers when assigning to storage and converting the type at the same time. Compiler Features: @@ -12,15 +12,15 @@ Compiler Features: Bugfixes: + * Type Checker: Set state mutability of the function type members ``gas`` and ``value`` to pure (while their return type inherits state mutability from the function type). * Yul / Inline Assembly Parser: Disallow trailing commas in function call arguments. - * Set state mutability of the function type members ``gas`` and ``value`` to pure (while their return type inherits state mutability from the function type). Build System: * Attempt to use stock Z3 cmake files to find Z3 and only fall back to manual discovery. - * Generate a cmake error for gcc versions older than 5.0. * CMake: use imported targets for boost. * Emscripten build: upgrade to boost 1.70. + * Generate a cmake error for gcc versions older than 5.0. diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 95128aa74..247f1230f 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -740,6 +740,10 @@ ], "released": "2018-12-03" }, + "0.5.10": { + "bugs": [], + "released": "2019-06-25" + }, "0.5.2": { "bugs": [ "SignedArrayStorageCopy",