diff options
64 files changed, 2822 insertions, 1320 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b29bc414..a11c56ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.14") +set(PROJECT_VERSION "0.4.16") project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies diff --git a/Changelog.md b/Changelog.md index 36186462..0381d434 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,17 +1,43 @@ -### 0.4.14 (unreleased) +### 0.4.16 (unreleased) + +Features: + * Parser: Display previous visibility specifier in error if multiple are found. + +Bugfixes: + +### 0.4.15 (2017-08-08) + +Features: + * Type Checker: Show unimplemented function if trying to instantiate an abstract class. + +Bugfixes: + * Code Generator: ``.delegatecall()`` should always return execution outcome. + * Code Generator: Provide "new account gas" for low-level ``callcode`` and ``delegatecall``. + * Type Checker: Constructors must be implemented if declared. + * Type Checker: Disallow the ``.gas()`` modifier on ``ecrecover``, ``sha256`` and ``ripemd160``. + * Type Checker: Do not mark overloaded functions as shadowing other functions. + * Type Checker: Internal library functions must be implemented if declared. + +### 0.4.14 (2017-07-31) Features: * C API (``jsonCompiler``): Export the ``license`` method. + * Code Generator: Optimise the fallback function, by removing a useless jump. * Inline Assembly: Show useful error message if trying to access calldata variables. * Inline Assembly: Support variable declaration without initial value (defaults to 0). + * Metadata: Only include files which were used to compile the given contract. * Type Checker: Disallow value transfers to contracts without a payable fallback function. * Type Checker: Include types in explicit conversion error message. * Type Checker: Raise proper error for arrays too large for ABI encoding. * Type checker: Warn if using ``this`` in a constructor. + * Type checker: Warn when existing symbols, including builtins, are overwritten. Bugfixes: + * Code Generator: Properly clear return memory area for ecrecover. + * Type Checker: Fix crash for some assignment to non-lvalue. * Type Checker: Fix invalid "specify storage keyword" warning for reference members of structs. * Type Checker: Mark modifiers as internal. + * Type Checker: Re-allow multiple mentions of the same modifier per function. ### 0.4.13 (2017-07-06) diff --git a/docs/assembly.rst b/docs/assembly.rst index 4e665b7e..bdb708a9 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -96,6 +96,31 @@ you really know what you are doing. } } } + + // Same as above, but accomplish the entire code within inline assembly. + function sumPureAsm(uint[] _data) returns (uint o_sum) { + assembly { + // Load the length (first 32 bytes) + let len := mload(_data) + + // Skip over the length field. + // + // Keep temporary variable so it can be incremented in place. + // + // NOTE: incrementing _data would result in an unusable + // _data variable after this assembly block + let data := add(_data, 0x20) + + // Iterate until the bound is not met. + for + { let end := add(data, len) } + lt(data, end) + { data := add(data, 0x20) } + { + o_sum := add(o_sum, mload(data)) + } + } + } } @@ -125,7 +150,7 @@ following list can be used as a reference of its opcodes. If an opcode takes arguments (always from the top of the stack), they are given in parentheses. Note that the order of arguments can be seen to be reversed in non-functional style (explained below). Opcodes marked with ``-`` do not push an item onto the stack, those marked with ``*`` are -special and all others push exactly one item onte the stack. +special and all others push exactly one item onto the stack. In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to (excluding) position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``. diff --git a/docs/bugs.json b/docs/bugs.json index a0c0e7c4..ac322a48 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,7 +1,22 @@ [ { + "name": "DelegateCallReturnValue", + "summary": "The low-level .delegatecall() does not return the execution outcome, but converts the value returned by the functioned called to a boolean instead.", + "description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successuful.", + "introduced": "0.3.0", + "fixed": "0.4.15", + "severity": "low" + }, + { + "name": "ECRecoverMalformedInput", + "summary": "The ecrecover() builtin can return garbage for malformed input.", + "description": "The ecrecover precompile does not properly signal failure for malformed input (especially in the 'v' argument) and thus the Solidity function can return data that was previously present in the return area in memory.", + "fixed": "0.4.14", + "severity": "medium" + }, + { "name": "SkipEmptyStringLiteral", - "summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.", + "summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.", "description": "If the empty string literal \"\" is used as an argument in a function call, it is skipped by the encoder. This has the effect that the encoding of all arguments following this is shifted left by 32 bytes and thus the function call data is corrupted.", "fixed": "0.4.12", "severity": "low" @@ -107,4 +122,4 @@ "severity": "high", "fixed": "0.3.0" } -]
\ No newline at end of file +] diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index d6802eec..33f7bae9 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -1,6 +1,7 @@ { "0.1.0": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -16,6 +17,7 @@ }, "0.1.1": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -31,6 +33,7 @@ }, "0.1.2": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -46,6 +49,7 @@ }, "0.1.3": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -61,6 +65,7 @@ }, "0.1.4": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -76,6 +81,7 @@ }, "0.1.5": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -91,6 +97,7 @@ }, "0.1.6": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -107,6 +114,7 @@ }, "0.1.7": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -123,6 +131,7 @@ }, "0.2.0": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -139,6 +148,7 @@ }, "0.2.1": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -155,6 +165,7 @@ }, "0.2.2": { "bugs": [ + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -171,6 +182,8 @@ }, "0.3.0": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -186,6 +199,8 @@ }, "0.3.1": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -200,6 +215,8 @@ }, "0.3.2": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -214,6 +231,8 @@ }, "0.3.3": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -227,6 +246,8 @@ }, "0.3.4": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -240,6 +261,8 @@ }, "0.3.5": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -253,6 +276,8 @@ }, "0.3.6": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -264,6 +289,8 @@ }, "0.4.0": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -275,6 +302,8 @@ }, "0.4.1": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -286,6 +315,8 @@ }, "0.4.10": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], @@ -293,20 +324,40 @@ }, "0.4.11": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral" ], "released": "2017-05-03" }, "0.4.12": { - "bugs": [], + "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput" + ], "released": "2017-07-03" }, "0.4.13": { - "bugs": [], + "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput" + ], "released": "2017-07-06" }, + "0.4.14": { + "bugs": [ + "DelegateCallReturnValue" + ], + "released": "2017-07-31" + }, + "0.4.15": { + "bugs": [], + "released": "2017-08-08" + }, "0.4.2": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -317,6 +368,8 @@ }, "0.4.3": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -326,6 +379,8 @@ }, "0.4.4": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored" @@ -334,6 +389,8 @@ }, "0.4.5": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", @@ -343,6 +400,8 @@ }, "0.4.6": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored" @@ -351,6 +410,8 @@ }, "0.4.7": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], @@ -358,6 +419,8 @@ }, "0.4.8": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], @@ -365,6 +428,8 @@ }, "0.4.9": { "bugs": [ + "DelegateCallReturnValue", + "ECRecoverMalformedInput", "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], diff --git a/docs/contracts.rst b/docs/contracts.rst index 11e674d1..da797702 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -84,7 +84,7 @@ This means that cyclic creation dependencies are impossible. // State variables are accessed via their name // and not via e.g. this.owner. This also applies // to functions and especially in the constructors, - // you can only call them like that ("internall"), + // you can only call them like that ("internally"), // because the contract itself does not exist yet. owner = msg.sender; // We do an explicit type conversion from `address` @@ -246,7 +246,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value } .. index:: ! getter;function, ! function;getter -.. _getter_functions: +.. _getter-functions: Getter Functions ================ diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 128e6fae..796e9238 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -381,13 +381,11 @@ 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 difference between the two is that ``assert`` should only be used for internal errors -and ``require`` should be used to check external conditions (invalid inputs or errors in external components). -The idea behind that is that analysis tools can check your contract and try to come up with situations and -series of function calls that will reach a failing assertion. If this is possible, this means there is a bug -in your contract you should fix. +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. -There are two other ways to trigger execptions: The ``revert`` function can be used to flag an error and +There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and revert the current call. In the future it might be possible to also include details about the error in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``. @@ -395,6 +393,9 @@ When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are reth and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case of an exception instead of "bubbling up". +.. warning:: + The low-level ``call``, ``delegatecall`` and ``callcode`` will return success if the calling 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/index.rst b/docs/index.rst index 3cdda62d..dea11a86 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,9 +41,6 @@ Available Solidity Integrations * `Remix <https://remix.ethereum.org/>`_ Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. -* `Ethereum Studio <https://live.ether.camp/>`_ - Specialized web IDE that also provides shell access to a complete Ethereum environment. - * `IntelliJ IDEA plugin <https://plugins.jetbrains.com/plugin/9475-intellij-solidity>`_ Solidity plugin for IntelliJ IDEA (and all other JetBrains IDEs) @@ -82,6 +79,8 @@ Discontinued: * `Mix IDE <https://github.com/ethereum/mix/>`_ Qt based IDE for designing, debugging and testing solidity smart contracts. +* `Ethereum Studio <https://live.ether.camp/>`_ + Specialized web IDE that also provides shell access to a complete Ethereum environment. Solidity Tools -------------- diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 9b5ba9f2..35eb2d93 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -56,7 +56,7 @@ repository contains potentially unstable changes in the develop branch. docker run ethereum/solc:stable solc --version -Currenty, the docker image only contains the compiler executable, +Currently, the docker image only contains the compiler executable, so you have to do some additional work to link in the source and output directories. @@ -82,6 +82,18 @@ If you want to use the cutting edge developer version: sudo add-apt-repository ppa:ethereum/ethereum-dev sudo apt-get update sudo apt-get install solc + +We are also releasing a `snap package <https://snapcraft.io/>`_, which is installable in all the `supported Linux distros <https://snapcraft.io/docs/core/install>`_. To install the latest stable version of solc: + +.. code:: bash + + sudo snap install solc + +Or if you want to help testing the unstable solc with the most recent changes from the development branch: + +.. code:: bash + + sudo snap install solc --edge Arch Linux also has packages, albeit limited to the latest development version: diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 1fcdb2fc..e364bee7 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -469,7 +469,7 @@ Global Variables - ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component) - ``revert()``: abort execution and revert state changes - ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments -- ``sha3(...) returns (bytes32)``: an alias to `keccak256()` +- ``sha3(...) returns (bytes32)``: an alias to `keccak256` - ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments - ``ripemd160(...) returns (bytes20)``: compute the RIPEMD-160 hash of the (tightly packed) arguments - ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error @@ -478,6 +478,7 @@ Global Variables - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address +- ``suicide(address recipieint)``: an alias to `selfdestruct`` - ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei - ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure - ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure @@ -515,7 +516,7 @@ Reserved Keywords These keywords are reserved in Solidity. They might become part of the syntax in the future: -``abstract``, ``after``, ``case``, ``catch``, ``default``, ``final``, ``in``, ``inline``, ``interface``, ``let``, ``match``, ``null``, +``abstract``, ``after``, ``case``, ``catch``, ``default``, ``final``, ``in``, ``inline``, ``let``, ``match``, ``null``, ``of``, ``pure``, ``relocatable``, ``static``, ``switch``, ``try``, ``type``, ``typeof``, ``view``. Language Grammar diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 658c12b2..6586cb5f 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -14,7 +14,7 @@ the source code is often available. Of course you always have to consider how much is at stake: You can compare a smart contract with a web service that is open to the -public (and thus, also to malicous actors) and perhaps even open source. +public (and thus, also to malicious actors) and perhaps even open source. If you only store your grocery list on that web service, you might not have to take too much care, but if you manage your bank account using that web service, you should be more careful. @@ -280,8 +280,7 @@ Formal Verification Using formal verification, it is possible to perform an automated mathematical proof that your source code fulfills a certain formal specification. The specification is still formal (just as the source code), but usually much -simpler. There is a prototype in Solidity that performs formal verification and -it will be better documented soon. +simpler. Note that formal verification itself can only help you understand the difference between what you did (the specification) and how you did it diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index e1fd4914..dde4495b 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -101,7 +101,7 @@ of votes. /// Delegate your vote to the voter `to`. function delegate(address to) { // assigns reference - Voter sender = voters[msg.sender]; + Voter storage sender = voters[msg.sender]; require(!sender.voted); // Self-delegation is not allowed. @@ -141,7 +141,7 @@ of votes. /// Give your vote (including votes delegated to you) /// to proposal `proposals[proposal].name`. function vote(uint proposal) { - Voter sender = voters[msg.sender]; + Voter storage sender = voters[msg.sender]; require(!sender.voted); sender.voted = true; sender.vote = proposal; @@ -277,9 +277,9 @@ activate themselves. if (highestBidder != 0) { // Sending back the money by simply using // highestBidder.send(highestBid) is a security risk - // because it can be prevented by the caller by e.g. - // raising the call stack to 1023. It is always safer - // to let the recipients withdraw their money themselves. + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; @@ -289,7 +289,7 @@ activate themselves. /// Withdraw a bid that was overbid. function withdraw() returns (bool) { - var amount = pendingReturns[msg.sender]; + uint amount = pendingReturns[msg.sender]; if (amount > 0) { // It is important to set this to zero because the recipient // can call this function again as part of the receiving call @@ -491,8 +491,8 @@ high or low invalid bids. } /// Withdraw a bid that was overbid. - function withdraw() returns (bool) { - var amount = pendingReturns[msg.sender]; + function withdraw() { + uint amount = pendingReturns[msg.sender]; if (amount > 0) { // It is important to set this to zero because the recipient // can call this function again as part of the receiving call @@ -500,13 +500,8 @@ high or low invalid bids. // conditions -> effects -> interaction). pendingReturns[msg.sender] = 0; - if (!msg.sender.send(amount)){ - // No need to call throw here, just reset the amount owing - pendingReturns[msg.sender] = amount; - return false; - } + msg.sender.transfer(amount); } - return true; } /// End the auction and send the highest bid diff --git a/docs/types.rst b/docs/types.rst index 7085610a..403d032c 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -135,6 +135,9 @@ The ``.gas()`` option is available on all three methods, while the ``.value()`` All contracts inherit the members of address, so it is possible to query the balance of the current contract using ``this.balance``. +.. note:: + The use of ``callcode`` is discouraged and will be removed in the future. + .. warning:: All these functions are low-level functions and should be used with care. Specifically, any unknown contract might be malicious and if you call it, you @@ -437,7 +440,8 @@ Another example that uses external function types:: } } -Note that lambda or inline functions are planned but not yet supported. +.. note:: + Lambda or inline functions are planned but not yet supported. .. index:: ! type;reference, ! reference type, storage, memory, location, array, struct @@ -740,7 +744,7 @@ shown in the following example: } function contribute(uint campaignID) payable { - Campaign c = campaigns[campaignID]; + Campaign storage c = campaigns[campaignID]; // Creates a new temporary memory struct, initialised with the given values // and copies it over to storage. // Note that you can also use Funder(msg.sender, msg.value) to initialise. @@ -749,7 +753,7 @@ shown in the following example: } function checkGoalReached(uint campaignID) returns (bool reached) { - Campaign c = campaigns[campaignID]; + Campaign storage c = campaigns[campaignID]; if (c.amount < c.fundingGoal) return false; uint amount = c.amount; @@ -816,9 +820,10 @@ for each ``_KeyType``, recursively. } contract MappingUser { - address contractAddress = 0x42; function f() returns (uint) { - return MappingExample(contractAddress).balances(this); + MappingExample m = new MappingExample(); + m.update(100); + return m.balances(this); } } diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 7d21f065..64795306 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -35,7 +35,9 @@ These suffixes cannot be applied to variables. If you want to interpret some input variable in e.g. days, you can do it in the following way:: function f(uint start, uint daysAfter) { - if (now >= start + daysAfter * 1 days) { ... } + if (now >= start + daysAfter * 1 days) { + // ... + } } Special Variables and Functions @@ -70,6 +72,7 @@ Block and Transaction Properties ``msg.value`` can change for every **external** function call. This includes calls to library functions. +.. note:: If you want to implement access restrictions in library functions using ``msg.sender``, you have to manually supply the value of ``msg.sender`` as an argument. @@ -102,10 +105,10 @@ Mathematical and Cryptographic Functions compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``. ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments -``sha3(...) returns (bytes32)``: - alias to ``keccak256()`` ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments +``sha3(...) returns (bytes32)``: + alias to ``keccak256`` ``ripemd160(...) returns (bytes20)``: compute RIPEMD-160 hash of the (tightly packed) arguments ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: @@ -157,6 +160,9 @@ For more information, see the section on :ref:`address`. to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: Use a pattern where the recipient withdraws the money. +.. note:: + The use of ``callcode`` is discouraged and will be removed in the future. + .. index:: this, selfdestruct Contract Related @@ -168,5 +174,8 @@ Contract Related ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given :ref:`address` +``suicide(address recipient)``: + alias to ``selfdestruct`` + Furthermore, all functions of the current contract are callable directly including the current function. diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 4297f606..ab4bfe68 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -166,6 +166,12 @@ template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U con _a.push_back(i); return _a; } +/// Concatenate the contents of a container onto a set +template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U const& _b) +{ + _a.insert(_b.begin(), _b.end()); + return _a; +} /// Concatenate two vectors of elements. template <class T> inline std::vector<T> operator+(std::vector<T> const& _a, std::vector<T> const& _b) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 597fdae1..42b923df 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -350,38 +350,65 @@ void Assembly::injectStart(AssemblyItem const& _i) Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) { - optimiseInternal(_enable, _isCreation, _runs); + OptimiserSettings settings; + settings.isCreation = _isCreation; + settings.runPeephole = true; + if (_enable) + { + settings.runDeduplicate = true; + settings.runCSE = true; + settings.runConstantOptimiser = true; + } + settings.expectedExecutionsPerDeployment = _runs; + optimiseInternal(settings); return *this; } -map<u256, u256> Assembly::optimiseInternal(bool _enable, bool _isCreation, size_t _runs) + +Assembly& Assembly::optimise(OptimiserSettings _settings) { + optimiseInternal(_settings); + return *this; +} + +map<u256, u256> Assembly::optimiseInternal(OptimiserSettings _settings) +{ + // Run optimisation for sub-assemblies. for (size_t subId = 0; subId < m_subs.size(); ++subId) { - map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(_enable, false, _runs); + OptimiserSettings settings = _settings; + // Disable creation mode for sub-assemblies. + settings.isCreation = false; + map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(settings); + // Apply the replacements (can be empty). BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); } map<u256, u256> tagReplacements; + // Iterate until no new optimisation possibilities are found. for (unsigned count = 1; count > 0;) { count = 0; - PeepholeOptimiser peepOpt(m_items); - while (peepOpt.optimise()) - count++; - - if (!_enable) - continue; + if (_settings.runPeephole) + { + PeepholeOptimiser peepOpt(m_items); + while (peepOpt.optimise()) + count++; + } // This only modifies PushTags, we have to run again to actually remove code. - BlockDeduplicator dedup(m_items); - if (dedup.deduplicate()) + if (_settings.runDeduplicate) { - tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end()); - count++; + BlockDeduplicator dedup(m_items); + if (dedup.deduplicate()) + { + tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end()); + count++; + } } + if (_settings.runCSE) { // Control flow graph optimization has been here before but is disabled because it // assumes we only jump to tags that are pushed. This is not the case anymore with @@ -429,10 +456,10 @@ map<u256, u256> Assembly::optimiseInternal(bool _enable, bool _isCreation, size_ } } - if (_enable) + if (_settings.runConstantOptimiser) ConstantOptimisationMethod::optimiseConstants( - _isCreation, - _isCreation ? 1 : _runs, + _settings.isCreation, + _settings.isCreation ? 1 : _settings.expectedExecutionsPerDeployment, *this, m_items ); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 0d40abcf..451b4ea0 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -97,12 +97,28 @@ public: LinkerObject const& assemble() const; bytes const& data(h256 const& _i) const { return m_data.at(_i); } + struct OptimiserSettings + { + bool isCreation = false; + bool runPeephole = false; + bool runDeduplicate = false; + bool runCSE = false; + bool runConstantOptimiser = false; + /// This specifies an estimate on how often each opcode in this assembly will be executed, + /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. + size_t expectedExecutionsPerDeployment = 200; + }; + + /// Execute optimisation passes as defined by @a _settings and return the optimised assembly. + Assembly& optimise(OptimiserSettings _settings); + /// Modify (if @a _enable is set) and return the current assembly such that creation and /// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly. /// @a _runs specifes an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime. /// If @a _enable is not set, will perform some simple peephole optimizations. Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200); + Json::Value stream( std::ostream& _out, std::string const& _prefix = "", @@ -113,7 +129,7 @@ public: protected: /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. - std::map<u256, u256> optimiseInternal(bool _enable, bool _isCreation, size_t _runs); + std::map<u256, u256> optimiseInternal(OptimiserSettings _settings); unsigned bytesRequired(unsigned subTagSize) const; diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h index 9c7c89e7..301998b7 100644 --- a/libsolidity/analysis/DeclarationContainer.h +++ b/libsolidity/analysis/DeclarationContainer.h @@ -53,6 +53,7 @@ public: bool registerDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr, bool _invisible = false, bool _update = false); std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const; ASTNode const* enclosingNode() const { return m_enclosingNode; } + DeclarationContainer const* enclosingContainer() const { return m_enclosingContainer; } std::map<ASTString, std::vector<Declaration const*>> const& declarations() const { return m_declarations; } /// @returns whether declaration is valid, and if not also returns previous declaration. Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const; diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index aac90311..523e7176 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -104,29 +104,18 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So } else for (Declaration const* declaration: declarations) - { - ASTString const* name = alias.second ? alias.second.get() : &declaration->name(); - if (!target.registerDeclaration(*declaration, name)) - { - m_errorReporter.declarationError( - imp->location(), - "Identifier \"" + *name + "\" already declared." - ); + if (!DeclarationRegistrationHelper::registerDeclaration( + target, *declaration, alias.second.get(), &imp->location(), true, m_errorReporter + )) error = true; - } - } } else if (imp->name().empty()) for (auto const& nameAndDeclaration: scope->second->declarations()) for (auto const& declaration: nameAndDeclaration.second) - if (!target.registerDeclaration(*declaration, &nameAndDeclaration.first)) - { - m_errorReporter.declarationError( - imp->location(), - "Identifier \"" + nameAndDeclaration.first + "\" already declared." - ); - error = true; - } + if (!DeclarationRegistrationHelper::registerDeclaration( + target, *declaration, &nameAndDeclaration.first, &imp->location(), true, m_errorReporter + )) + error = true; } return !error; } @@ -450,6 +439,71 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper( solAssert(m_currentScope == _currentScope, "Scopes not correctly closed."); } +bool DeclarationRegistrationHelper::registerDeclaration( + DeclarationContainer& _container, + Declaration const& _declaration, + string const* _name, + SourceLocation const* _errorLocation, + bool _warnOnShadow, + ErrorReporter& _errorReporter +) +{ + if (!_errorLocation) + _errorLocation = &_declaration.location(); + + Declaration const* shadowedDeclaration = nullptr; + if (_warnOnShadow && !_declaration.name().empty() && _container.enclosingContainer()) + for (auto const* decl: _container.enclosingContainer()->resolveName(_declaration.name(), true)) + shadowedDeclaration = decl; + + if (!_container.registerDeclaration(_declaration, _name, !_declaration.isVisibleInContract())) + { + SourceLocation firstDeclarationLocation; + SourceLocation secondDeclarationLocation; + Declaration const* conflictingDeclaration = _container.conflictingDeclaration(_declaration, _name); + solAssert(conflictingDeclaration, ""); + bool const comparable = + _errorLocation->sourceName && + conflictingDeclaration->location().sourceName && + *_errorLocation->sourceName == *conflictingDeclaration->location().sourceName; + if (comparable && _errorLocation->start < conflictingDeclaration->location().start) + { + firstDeclarationLocation = *_errorLocation; + secondDeclarationLocation = conflictingDeclaration->location(); + } + else + { + firstDeclarationLocation = conflictingDeclaration->location(); + secondDeclarationLocation = *_errorLocation; + } + + _errorReporter.declarationError( + secondDeclarationLocation, + SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation), + "Identifier already declared." + ); + return false; + } + else if (shadowedDeclaration) + { + if (dynamic_cast<MagicVariableDeclaration const*>(shadowedDeclaration)) + _errorReporter.warning( + _declaration.location(), + "This declaration shadows a builtin symbol." + ); + else + { + auto shadowedLocation = shadowedDeclaration->location(); + _errorReporter.warning( + _declaration.location(), + "This declaration shadows an existing declaration.", + SecondarySourceLocation().append("The shadowed declaration is here:", shadowedLocation) + ); + } + } + return true; +} + bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit) { if (!m_scopes[&_sourceUnit]) @@ -590,30 +644,21 @@ void DeclarationRegistrationHelper::closeCurrentScope() void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) { solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope."); - if (!m_scopes[m_currentScope]->registerDeclaration(_declaration, nullptr, !_declaration.isVisibleInContract())) - { - SourceLocation firstDeclarationLocation; - SourceLocation secondDeclarationLocation; - Declaration const* conflictingDeclaration = m_scopes[m_currentScope]->conflictingDeclaration(_declaration); - solAssert(conflictingDeclaration, ""); - if (_declaration.location().start < conflictingDeclaration->location().start) - { - firstDeclarationLocation = _declaration.location(); - secondDeclarationLocation = conflictingDeclaration->location(); - } - else - { - firstDeclarationLocation = conflictingDeclaration->location(); - secondDeclarationLocation = _declaration.location(); - } + bool warnAboutShadowing = true; + // Do not warn about shadowing for structs and enums because their members are + // not accessible without prefixes. + if ( + dynamic_cast<StructDefinition const*>(m_currentScope) || + dynamic_cast<EnumDefinition const*>(m_currentScope) + ) + warnAboutShadowing = false; + // Do not warn about the constructor shadowing the contract. + if (auto fun = dynamic_cast<FunctionDefinition const*>(&_declaration)) + if (fun->isConstructor()) + warnAboutShadowing = false; - m_errorReporter.declarationError( - secondDeclarationLocation, - SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation), - "Identifier already declared." - ); - } + registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, m_errorReporter); _declaration.setScope(m_currentScope); if (_opensScope) diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 84628778..59bd3b1f 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -136,6 +136,15 @@ public: ASTNode const* _currentScope = nullptr ); + static bool registerDeclaration( + DeclarationContainer& _container, + Declaration const& _declaration, + std::string const* _name, + SourceLocation const* _errorLocation, + bool _warnOnShadow, + ErrorReporter& _errorReporter + ); + private: bool visit(SourceUnit& _sourceUnit) override; void endVisit(SourceUnit& _sourceUnit) override; @@ -160,6 +169,8 @@ private: void closeCurrentScope(); void registerDeclaration(Declaration& _declaration, bool _opensScope); + static bool isOverloadedFunction(Declaration const& _declaration1, Declaration const& _declaration2); + /// @returns the canonical name of the current scope. std::string currentCanonicalName() const; diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index bde0e616..d2571cd3 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -18,6 +18,7 @@ #include <libsolidity/analysis/SyntaxChecker.h> #include <memory> #include <libsolidity/ast/AST.h> +#include <libsolidity/ast/ExperimentalFeatures.h> #include <libsolidity/analysis/SemVerHandler.h> #include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/Version.h> @@ -33,9 +34,10 @@ bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot) return Error::containsOnlyWarnings(m_errorReporter.errors()); } -bool SyntaxChecker::visit(SourceUnit const&) +bool SyntaxChecker::visit(SourceUnit const& _sourceUnit) { m_versionPragmaFound = false; + m_sourceUnit = &_sourceUnit; return true; } @@ -57,15 +59,46 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit) m_errorReporter.warning(_sourceUnit.location(), errorString); } + m_sourceUnit = nullptr; } bool SyntaxChecker::visit(PragmaDirective const& _pragma) { solAssert(!_pragma.tokens().empty(), ""); solAssert(_pragma.tokens().size() == _pragma.literals().size(), ""); - if (_pragma.tokens()[0] != Token::Identifier || _pragma.literals()[0] != "solidity") - m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); - else + if (_pragma.tokens()[0] != Token::Identifier) + m_errorReporter.syntaxError(_pragma.location(), "Invalid pragma \"" + _pragma.literals()[0] + "\""); + else if (_pragma.literals()[0] == "experimental") + { + solAssert(m_sourceUnit, ""); + vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); + if (literals.size() == 0) + m_errorReporter.syntaxError( + _pragma.location(), + "Experimental feature name is missing." + ); + else if (literals.size() > 1) + m_errorReporter.syntaxError( + _pragma.location(), + "Stray arguments." + ); + else + { + string const literal = literals[0]; + if (literal.empty()) + m_errorReporter.syntaxError(_pragma.location(), "Empty experimental feature name is invalid."); + else if (!ExperimentalFeatureNames.count(literal)) + m_errorReporter.syntaxError(_pragma.location(), "Unsupported experimental feature name."); + else if (m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeatureNames.at(literal))) + m_errorReporter.syntaxError(_pragma.location(), "Duplicate experimental feature name."); + else + { + m_sourceUnit->annotation().experimentalFeatures.insert(ExperimentalFeatureNames.at(literal)); + m_errorReporter.warning(_pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments."); + } + } + } + else if (_pragma.literals()[0] == "solidity") { vector<Token::Value> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end()); vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); @@ -81,6 +114,8 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) ); m_versionPragmaFound = true; } + else + m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); return true; } diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index fb5cc6d7..fa34bab3 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -77,6 +77,8 @@ private: bool m_versionPragmaFound = false; int m_inLoopDepth = 0; + + SourceUnit const* m_sourceUnit = nullptr; }; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 23f01752..6852c13d 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -93,7 +93,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract) FunctionDefinition const* fallbackFunction = nullptr; for (FunctionDefinition const* function: _contract.definedFunctions()) { - if (function->name().empty()) + if (function->isFallback()) { if (fallbackFunction) { @@ -112,8 +112,6 @@ bool TypeChecker::visit(ContractDefinition const& _contract) m_errorReporter.typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values."); } } - if (!function->isImplemented()) - _contract.annotation().isFullyImplemented = false; } for (auto const& n: _contract.subNodes()) @@ -188,20 +186,13 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>; map<string, vector<FunTypeAndFlag>> functions; - bool allBaseConstructorsImplemented = true; // Search from base to derived for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (FunctionDefinition const* function: contract->definedFunctions()) { // Take constructors out of overload hierarchy if (function->isConstructor()) - { - if (!function->isImplemented()) - // Base contract's constructor is not fully implemented, no way to get - // out of this. - allBaseConstructorsImplemented = false; continue; - } auto& overloads = functions[function->name()]; FunctionTypePointer funType = make_shared<FunctionType>(*function); auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) @@ -219,16 +210,15 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont it->second = true; } - if (!allBaseConstructorsImplemented) - _contract.annotation().isFullyImplemented = false; - // Set to not fully implemented if at least one flag is false. for (auto const& it: functions) for (auto const& funAndFlag: it.second) if (!funAndFlag.second) { - _contract.annotation().isFullyImplemented = false; - return; + FunctionDefinition const* function = dynamic_cast<FunctionDefinition const*>(&funAndFlag.first->declaration()); + solAssert(function, ""); + _contract.annotation().unimplementedFunctions.push_back(function); + break; } } @@ -266,7 +256,8 @@ void TypeChecker::checkContractAbstractConstructors(ContractDefinition const& _c } } if (!argumentsNeeded.empty()) - _contract.annotation().isFullyImplemented = false; + for (ContractDefinition const* contract: argumentsNeeded) + _contract.annotation().unimplementedFunctions.push_back(contract->constructor()); } void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract) @@ -482,7 +473,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) { if (isLibraryFunction) m_errorReporter.typeError(_function.location(), "Library functions cannot be payable."); - if (!_function.isConstructor() && !_function.name().empty() && !_function.isPartOfExternalInterface()) + if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface()) m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable."); if (_function.isDeclaredConst()) m_errorReporter.typeError(_function.location(), "Functions cannot be constant and payable at the same time."); @@ -510,8 +501,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function) { if (dynamic_cast<ContractDefinition const*>(decl)) m_errorReporter.declarationError(modifier->location(), "Base constructor already provided."); - else - m_errorReporter.declarationError(modifier->location(), "Modifier already used for this function."); } else modifiers.insert(decl); @@ -527,6 +516,10 @@ bool TypeChecker::visit(FunctionDefinition const& _function) } if (_function.isImplemented()) _function.body().accept(*this); + else if (_function.isConstructor()) + m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared."); + else if (isLibraryFunction && _function.visibility() <= FunctionDefinition::Visibility::Internal) + m_errorReporter.typeError(_function.location(), "Internal library function must be implemented if declared."); return false; } @@ -1052,7 +1045,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) { auto kind = callType->kind(); if ( - kind == FunctionType::Kind::Bare || + kind == FunctionType::Kind::BareCall || kind == FunctionType::Kind::BareCallCode || kind == FunctionType::Kind::BareDelegateCall ) @@ -1124,7 +1117,9 @@ bool TypeChecker::visit(Assignment const& _assignment) _assignment.annotation().type = make_shared<TupleType>(); expectType(_assignment.rightHandSide(), *tupleType); - checkDoubleStorageAssignment(_assignment); + // expectType does not cause fatal errors, so we have to check again here. + if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get())) + checkDoubleStorageAssignment(_assignment); } else if (t->category() == Type::Category::Mapping) { @@ -1523,8 +1518,15 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) if (!contract) m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); - if (!contract->annotation().isFullyImplemented) - m_errorReporter.typeError(_newExpression.location(), "Trying to create an instance of an abstract contract."); + if (!contract->annotation().unimplementedFunctions.empty()) + m_errorReporter.typeError( + _newExpression.location(), + SecondarySourceLocation().append( + "Missing implementation:", + contract->annotation().unimplementedFunctions.front()->location() + ), + "Trying to create an instance of an abstract contract." + ); if (!contract->constructorIsPublic()) m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 724a908f..1d68231e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -84,13 +84,35 @@ SourceUnitAnnotation& SourceUnit::annotation() const return dynamic_cast<SourceUnitAnnotation&>(*m_annotation); } -string Declaration::sourceUnitName() const +set<SourceUnit const*> SourceUnit::referencedSourceUnits(bool _recurse, set<SourceUnit const*> _skipList) const +{ + set<SourceUnit const*> sourceUnits; + for (ImportDirective const* importDirective: filteredNodes<ImportDirective>(nodes())) + { + auto const& sourceUnit = importDirective->annotation().sourceUnit; + if (!_skipList.count(sourceUnit)) + { + _skipList.insert(sourceUnit); + sourceUnits.insert(sourceUnit); + if (_recurse) + sourceUnits += sourceUnit->referencedSourceUnits(true, _skipList); + } + } + return sourceUnits; +} + +SourceUnit const& Declaration::sourceUnit() const { solAssert(!!m_scope, ""); ASTNode const* scope = m_scope; while (dynamic_cast<Declaration const*>(scope) && dynamic_cast<Declaration const*>(scope)->m_scope) scope = dynamic_cast<Declaration const*>(scope)->m_scope; - return dynamic_cast<SourceUnit const&>(*scope).annotation().path; + return dynamic_cast<SourceUnit const&>(*scope); +} + +string Declaration::sourceUnitName() const +{ + return sourceUnit().annotation().path; } ImportAnnotation& ImportDirective::annotation() const @@ -140,7 +162,7 @@ FunctionDefinition const* ContractDefinition::fallbackFunction() const { for (ContractDefinition const* contract: annotation().linearizedBaseContracts) for (FunctionDefinition const* f: contract->definedFunctions()) - if (f->name().empty()) + if (f->isFallback()) return f; return nullptr; } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 81ddc754..bdf20e81 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -136,6 +136,9 @@ public: std::vector<ASTPointer<ASTNode>> nodes() const { return m_nodes; } + /// @returns a set of referenced SourceUnits. Recursively if @a _recurse is true. + std::set<SourceUnit const*> referencedSourceUnits(bool _recurse = false, std::set<SourceUnit const*> _skipList = std::set<SourceUnit const*>()) const; + private: std::vector<ASTPointer<ASTNode>> m_nodes; }; @@ -149,6 +152,24 @@ public: /// Visibility ordered from restricted to unrestricted. enum class Visibility { Default, Private, Internal, Public, External }; + static std::string visibilityToString(Declaration::Visibility _visibility) + { + switch(_visibility) + { + case Declaration::Visibility::Public: + return "public"; + case Declaration::Visibility::Internal: + return "internal"; + case Declaration::Visibility::Private: + return "private"; + case Declaration::Visibility::External: + return "external"; + default: + solAssert(false, "Invalid visibility specifier."); + } + return std::string(); + } + Declaration( SourceLocation const& _location, ASTPointer<ASTString> const& _name, @@ -168,6 +189,9 @@ public: ASTNode const* scope() const { return m_scope; } void setScope(ASTNode const* _scope) { m_scope = _scope; } + /// @returns the source unit this declaration is present in. + SourceUnit const& sourceUnit() const; + /// @returns the source name this declaration is present in. /// Can be combined with annotation().canonicalName to form a globally unique name. std::string sourceUnitName() const; @@ -583,6 +607,7 @@ public: virtual void accept(ASTConstVisitor& _visitor) const override; bool isConstructor() const { return m_isConstructor; } + bool isFallback() const { return name().empty(); } bool isDeclaredConst() const { return m_isDeclaredConst; } bool isPayable() const { return m_isPayable; } std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; } @@ -590,9 +615,9 @@ public: Block const& body() const { solAssert(m_body, ""); return *m_body; } virtual bool isVisibleInContract() const override { - return Declaration::isVisibleInContract() && !isConstructor() && !name().empty(); + return Declaration::isVisibleInContract() && !isConstructor() && !isFallback(); } - virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstructor && !name().empty(); } + virtual bool isPartOfExternalInterface() const override { return isPublic() && !isConstructor() && !isFallback(); } /// @returns the external signature of the function /// That consists of the name of the function followed by the types of the @@ -791,11 +816,11 @@ public: Declaration(SourceLocation(), std::make_shared<ASTString>(_name)), m_type(_type) {} virtual void accept(ASTVisitor&) override { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("MagicVariableDeclaration used inside real AST.")); + solAssert(false, "MagicVariableDeclaration used inside real AST."); } virtual void accept(ASTConstVisitor&) const override { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("MagicVariableDeclaration used inside real AST.")); + solAssert(false, "MagicVariableDeclaration used inside real AST."); } virtual TypePointer type() const override { return m_type; } diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 45a6dd1a..fd9efb4d 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -23,6 +23,7 @@ #pragma once #include <libsolidity/ast/ASTForward.h> +#include <libsolidity/ast/ExperimentalFeatures.h> #include <map> #include <memory> @@ -61,6 +62,8 @@ struct SourceUnitAnnotation: ASTAnnotation std::string path; /// The exported symbols (all global symbols). std::map<ASTString, std::vector<Declaration const*>> exportedSymbols; + /// Experimental features. + std::set<ExperimentalFeature> experimentalFeatures; }; struct ImportAnnotation: ASTAnnotation @@ -79,8 +82,8 @@ struct TypeDeclarationAnnotation: ASTAnnotation struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation { - /// Whether all functions are implemented. - bool isFullyImplemented = true; + /// List of functions without a body. Can also contain functions from base classes. + std::vector<FunctionDefinition const*> unimplementedFunctions; /// List of all (direct and indirect) base contracts in order from derived to /// base, including the contract itself. std::vector<ContractDefinition const*> linearizedBaseContracts; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index a90debb2..abee55ee 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -253,7 +253,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) make_pair("name", _node.name()), make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("contractKind", contractKind(_node.contractKind())), - make_pair("fullyImplemented", _node.annotation().isFullyImplemented), + make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), make_pair("baseContracts", toJson(_node.baseContracts())), make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies)), @@ -285,7 +285,7 @@ bool ASTJsonConverter::visit(StructDefinition const& _node) { setJsonNode(_node, "StructDefinition", { make_pair("name", _node.name()), - make_pair("visibility", visibility(_node.visibility())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("canonicalName", _node.annotation().canonicalName), make_pair("members", toJson(_node.members())), make_pair("scope", idOrNull(_node.scope())) @@ -325,7 +325,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) make_pair("name", _node.name()), make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()), make_pair("payable", _node.isPayable()), - make_pair("visibility", visibility(_node.visibility())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("parameters", toJson(_node.parameterList())), make_pair("isConstructor", _node.isConstructor()), make_pair("returnParameters", toJson(*_node.returnParameterList())), @@ -346,7 +346,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node) make_pair("constant", _node.isConstant()), make_pair("stateVariable", _node.isStateVariable()), make_pair("storageLocation", location(_node.referenceLocation())), - make_pair("visibility", visibility(_node.visibility())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue), make_pair("scope", idOrNull(_node.scope())), make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) @@ -361,7 +361,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node) { setJsonNode(_node, "ModifierDefinition", { make_pair("name", _node.name()), - make_pair("visibility", visibility(_node.visibility())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("parameters", toJson(_node.parameterList())), make_pair("body", toJson(_node.body())) }); @@ -418,7 +418,7 @@ bool ASTJsonConverter::visit(FunctionTypeName const& _node) { setJsonNode(_node, "FunctionTypeName", { make_pair("payable", _node.isPayable()), - make_pair("visibility", visibility(_node.visibility())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()), make_pair("parameterTypes", toJson(*_node.parameterTypeList())), make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())), @@ -730,23 +730,6 @@ void ASTJsonConverter::endVisit(EventDefinition const&) m_inEvent = false; } -string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility) -{ - switch (_visibility) - { - case Declaration::Visibility::Private: - return "private"; - case Declaration::Visibility::Internal: - return "internal"; - case Declaration::Visibility::Public: - return "public"; - case Declaration::Visibility::External: - return "external"; - default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown declaration visibility.")); - } -} - string ASTJsonConverter::location(VariableDeclaration::Location _location) { switch (_location) @@ -758,7 +741,7 @@ string ASTJsonConverter::location(VariableDeclaration::Location _location) case VariableDeclaration::Location::Memory: return "memory"; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown declaration location.")); + solAssert(false, "Unknown declaration location."); } } @@ -773,7 +756,7 @@ string ASTJsonConverter::contractKind(ContractDefinition::ContractKind _kind) case ContractDefinition::ContractKind::Library: return "library"; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of contract.")); + solAssert(false, "Unknown kind of contract."); } } @@ -788,7 +771,7 @@ string ASTJsonConverter::functionCallKind(FunctionCallKind _kind) case FunctionCallKind::StructConstructorCall: return "structConstructorCall"; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of function call .")); + solAssert(false, "Unknown kind of function call."); } } @@ -804,7 +787,7 @@ string ASTJsonConverter::literalTokenKind(Token::Value _token) case dev::solidity::Token::FalseLiteral: return "bool"; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of literal token.")); + solAssert(false, "Unknown kind of literal token."); } } diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 27114c2a..70e260db 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -128,7 +128,6 @@ private: return _node ? toJson(*_node) : Json::nullValue; } Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _info); - std::string visibility(Declaration::Visibility const& _visibility); std::string location(VariableDeclaration::Location _location); std::string contractKind(ContractDefinition::ContractKind _kind); std::string functionCallKind(FunctionCallKind _kind); diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h new file mode 100644 index 00000000..b0a07142 --- /dev/null +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -0,0 +1,35 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * List of experimental features. + */ + +#pragma once + +#include <map> + +namespace dev +{ +namespace solidity +{ + +enum class ExperimentalFeature {}; + +static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames = {}; + +} +} diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index bcfccc3e..a66ccda5 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -211,9 +211,10 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) return make_shared<ArrayType>(DataLocation::Storage, true); //no types found default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment( + solAssert( + false, "Unable to convert elementary typename " + _type.toString() + " to type." - )); + ); } } @@ -476,7 +477,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons if (isAddress()) return { {"balance", make_shared<IntegerType >(256)}, - {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::Bare, true, false, true)}, + {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, false, true)}, {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)}, {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)}, {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, @@ -524,19 +525,20 @@ bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const { - // "delete" is ok for all fixed types - if (_operator == Token::Delete) + switch(_operator) + { + case Token::Delete: + // "delete" is ok for all fixed types return make_shared<TupleType>(); - // for fixed, we allow +, -, ++ and -- - else if ( - _operator == Token::Add || - _operator == Token::Sub || - _operator == Token::Inc || - _operator == Token::Dec - ) + case Token::Add: + case Token::Sub: + case Token::Inc: + case Token::Dec: + // for fixed, we allow +, -, ++ and -- return shared_from_this(); - else + default: return TypePointer(); + } } bool FixedPointType::operator==(Type const& _other) const @@ -1176,7 +1178,7 @@ u256 BoolType::literalValue(Literal const* _literal) const else if (_literal->token() == Token::FalseLiteral) return u256(0); else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Bool type constructed from non-boolean literal.")); + solAssert(false, "Bool type constructed from non-boolean literal."); } TypePointer BoolType::unaryOperatorResult(Token::Value _operator) const @@ -1407,6 +1409,11 @@ unsigned ArrayType::calldataEncodedSize(bool _padded) const return unsigned(size); } +bool ArrayType::isDynamicallyEncoded() const +{ + return isDynamicallySized() || baseType()->isDynamicallyEncoded(); +} + u256 ArrayType::storageSize() const { if (isDynamicallySized()) @@ -1709,6 +1716,11 @@ unsigned StructType::calldataEncodedSize(bool _padded) const return size; } +bool StructType::isDynamicallyEncoded() const +{ + solAssert(false, "Structs are not yet supported in the ABI."); +} + u256 StructType::memorySize() const { u256 size; @@ -1938,10 +1950,7 @@ string TupleType::toString(bool _short) const u256 TupleType::storageSize() const { - BOOST_THROW_EXCEPTION( - InternalCompilerError() << - errinfo_comment("Storage size of non-storable tuple type requested.") - ); + solAssert(false, "Storage size of non-storable tuple type requested."); } unsigned TupleType::sizeOnStack() const @@ -2180,7 +2189,7 @@ string FunctionType::identifier() const case Kind::External: id += "external"; break; case Kind::CallCode: id += "callcode"; break; case Kind::DelegateCall: id += "delegatecall"; break; - case Kind::Bare: id += "bare"; break; + case Kind::BareCall: id += "barecall"; break; case Kind::BareCallCode: id += "barecallcode"; break; case Kind::BareDelegateCall: id += "baredelegatecall"; break; case Kind::Creation: id += "creation"; break; @@ -2212,6 +2221,8 @@ string FunctionType::identifier() const } if (isConstant()) id += "_constant"; + if (isPayable()) + id += "_payable"; id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); if (m_gasSet) id += "gas"; @@ -2226,23 +2237,22 @@ bool FunctionType::operator==(Type const& _other) const { if (_other.category() != category()) return false; - FunctionType const& other = dynamic_cast<FunctionType const&>(_other); - if (m_kind != other.m_kind) - return false; - if (m_isConstant != other.isConstant()) + FunctionType const& other = dynamic_cast<FunctionType const&>(_other); + if ( + m_kind != other.m_kind || + m_isConstant != other.isConstant() || + m_isPayable != other.isPayable() || + m_parameterTypes.size() != other.m_parameterTypes.size() || + m_returnParameterTypes.size() != other.m_returnParameterTypes.size() + ) return false; - if (m_parameterTypes.size() != other.m_parameterTypes.size() || - m_returnParameterTypes.size() != other.m_returnParameterTypes.size()) - return false; auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; - - if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), - other.m_parameterTypes.cbegin(), typeCompare)) - return false; - if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), - other.m_returnParameterTypes.cbegin(), typeCompare)) + if ( + !equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), other.m_parameterTypes.cbegin(), typeCompare) || + !equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare) + ) return false; //@todo this is ugly, but cannot be prevented right now if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet) @@ -2323,9 +2333,7 @@ u256 FunctionType::storageSize() const if (m_kind == Kind::External || m_kind == Kind::Internal) return 1; else - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_comment("Storage size of non-storable function type requested.")); + solAssert(false, "Storage size of non-storable function type requested."); } unsigned FunctionType::storageBytes() const @@ -2335,9 +2343,7 @@ unsigned FunctionType::storageBytes() const else if (m_kind == Kind::Internal) return 8; // it should really not be possible to create larger programs else - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_comment("Storage size of non-storable function type requested.")); + solAssert(false, "Storage size of non-storable function type requested."); } unsigned FunctionType::sizeOnStack() const @@ -2350,14 +2356,26 @@ unsigned FunctionType::sizeOnStack() const } unsigned size = 0; - if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall) + + switch(kind) + { + case Kind::External: + case Kind::CallCode: + case Kind::DelegateCall: size = 2; - else if (kind == Kind::Bare || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall) - size = 1; - else if (kind == Kind::Internal) - size = 1; - else if (kind == Kind::ArrayPush || kind == Kind::ByteArrayPush) + break; + case Kind::BareCall: + case Kind::BareCallCode: + case Kind::BareDelegateCall: + case Kind::Internal: + case Kind::ArrayPush: + case Kind::ByteArrayPush: size = 1; + break; + default: + break; + } + if (m_gasSet) size++; if (m_valueSet) @@ -2395,10 +2413,15 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const return FunctionTypePointer(); return make_shared<FunctionType>( - paramTypes, retParamTypes, - m_parameterNames, m_returnParameterNames, - m_kind, m_arbitraryParameters, - m_declaration, m_isConstant, m_isPayable + paramTypes, + retParamTypes, + m_parameterNames, + m_returnParameterNames, + m_kind, + m_arbitraryParameters, + m_declaration, + m_isConstant, + m_isPayable ); } @@ -2408,10 +2431,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { case Kind::External: case Kind::Creation: - case Kind::ECRecover: - case Kind::SHA256: - case Kind::RIPEMD160: - case Kind::Bare: + case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: { @@ -2515,7 +2535,7 @@ bool FunctionType::isBareCall() const { switch (m_kind) { - case Kind::Bare: + case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: case Kind::ECRecover: @@ -2530,6 +2550,7 @@ bool FunctionType::isBareCall() const string FunctionType::externalSignature() const { solAssert(m_declaration != nullptr, "External signature of function needs declaration"); + solAssert(!m_declaration->name().empty(), "Fallback function has no signature."); bool _inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary(); @@ -2695,9 +2716,7 @@ bool TypeType::operator==(Type const& _other) const u256 TypeType::storageSize() const { - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_comment("Storage size of non-storable type type requested.")); + solAssert(false, "Storage size of non-storable type type requested."); } unsigned TypeType::sizeOnStack() const @@ -2764,9 +2783,7 @@ ModifierType::ModifierType(const ModifierDefinition& _modifier) u256 ModifierType::storageSize() const { - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_comment("Storage size of non-storable type type requested.")); + solAssert(false, "Storage size of non-storable type type requested."); } string ModifierType::identifier() const @@ -2875,7 +2892,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const {"gasprice", make_shared<IntegerType>(256)} }); default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); + solAssert(false, "Unknown kind of magic."); } } @@ -2890,6 +2907,6 @@ string MagicType::toString(bool) const case Kind::Transaction: return "tx"; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); + solAssert(false, "Unknown kind of magic."); } } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 3d7dad16..8c9232c6 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -187,6 +187,7 @@ public: /// @returns number of bytes used by this type when encoded for CALL. If it is a dynamic type, /// returns the size of the pointer (usually 32). Returns 0 if the type cannot be encoded /// in calldata. + /// @note: This should actually not be called on types, where isDynamicallyEncoded returns true. /// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes. virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; return 0; } /// @returns the size of this data type in bytes when stored in memory. For memory-reference @@ -194,8 +195,10 @@ public: virtual unsigned memoryHeadSize() const { return calldataEncodedSize(); } /// Convenience version of @see calldataEncodedSize(bool) unsigned calldataEncodedSize() const { return calldataEncodedSize(true); } - /// @returns true if the type is dynamically encoded in calldata + /// @returns true if the type is a dynamic array virtual bool isDynamicallySized() const { return false; } + /// @returns true if the type is dynamically encoded in the ABI + virtual bool isDynamicallyEncoded() const { return false; } /// @returns the number of storage slots required to hold this value in storage. /// For dynamically "allocated" types, it returns the size of the statically allocated head, virtual u256 storageSize() const { return 1; } @@ -245,10 +248,7 @@ public: virtual std::string canonicalName(bool /*_addDataLocation*/) const { return toString(true); } virtual u256 literalValue(Literal const*) const { - BOOST_THROW_EXCEPTION( - InternalCompilerError() << - errinfo_comment("Literal value requested for type without literals.") - ); + solAssert(false, "Literal value requested for type without literals."); } /// @returns a (simpler) type that is encoded in the same way for external function calls. @@ -612,6 +612,7 @@ public: virtual bool operator==(const Type& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool isDynamicallySized() const override { return m_hasDynamicLength; } + virtual bool isDynamicallyEncoded() const override; virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } virtual unsigned sizeOnStack() const override; @@ -726,6 +727,7 @@ public: virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; + virtual bool isDynamicallyEncoded() const override; u256 memorySize() const; virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return true; } @@ -841,7 +843,7 @@ public: External, ///< external call using CALL CallCode, ///< external call using CALLCODE, i.e. not exchanging the storage DelegateCall, ///< external call using DELEGATECALL, i.e. not exchanging the storage - Bare, ///< CALL without function hash + BareCall, ///< CALL without function hash BareCallCode, ///< CALLCODE without function hash BareDelegateCall, ///< DELEGATECALL without function hash Creation, ///< external call using CREATE diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 6875bda1..bc4de3ee 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -124,14 +124,15 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); - solAssert(m_localVariables.count(&_declaration) == 0, "Variable already present"); - m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; + m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent); } void CompilerContext::removeVariable(VariableDeclaration const& _declaration) { - solAssert(!!m_localVariables.count(&_declaration), ""); - m_localVariables.erase(&_declaration); + solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), ""); + m_localVariables[&_declaration].pop_back(); + if (m_localVariables[&_declaration].empty()) + m_localVariables.erase(&_declaration); } eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const @@ -196,15 +197,15 @@ ModifierDefinition const& CompilerContext::functionModifier(string const& _name) for (ModifierDefinition const* modifier: contract->functionModifiers()) if (modifier->name() == _name) return *modifier; - BOOST_THROW_EXCEPTION(InternalCompilerError() - << errinfo_comment("Function modifier " + _name + " not found.")); + solAssert(false, "Function modifier " + _name + " not found."); } unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const { auto res = m_localVariables.find(&_declaration); solAssert(res != m_localVariables.end(), "Variable not found on stack."); - return res->second; + solAssert(!res->second.empty(), ""); + return res->second.back(); } unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const @@ -310,6 +311,7 @@ void CompilerContext::appendInlineAssembly( if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( CompilerError() << + errinfo_sourceLocation(_identifier.location) << errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.") ); if (_context == julia::IdentifierContext::RValue) diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 1968c1e1..13821f67 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -272,7 +272,10 @@ private: /// Storage offsets of state variables std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). - std::map<Declaration const*, unsigned> m_localVariables; + /// This needs to be a stack because if a modifier contains a local variable and this + /// modifier is applied twice, the position of the variable needs to be restored + /// after the nested modifier is left. + std::map<Declaration const*, std::vector<unsigned>> m_localVariables; /// List of current inheritance hierarchy from derived to base. std::vector<ContractDefinition const*> m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index cad388df..fd0998d4 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -267,18 +267,13 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << notFound; if (fallback) { - m_context.setStackOffset(0); if (!fallback->isPayable()) appendCallValueCheck(); - // Return tag is used to jump out of the function. - eth::AssemblyItem returnTag = m_context.pushNewTag(); - fallback->accept(*this); - m_context << returnTag; + solAssert(fallback->isFallback(), ""); solAssert(FunctionType(*fallback).parameterTypes().empty(), ""); solAssert(FunctionType(*fallback).returnParameterTypes().empty(), ""); - // Return tag gets consumed. - m_context.adjustStackOffset(-1); + fallback->accept(*this); m_context << Instruction::STOP; } else @@ -536,7 +531,8 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) m_context.adjustStackOffset(-(int)c_returnValuesSize); - if (!_function.isConstructor()) + /// The constructor and the fallback function doesn't to jump out. + if (!_function.isConstructor() && !_function.isFallback()) m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); return false; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 82518e8c..521d485f 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -174,7 +174,12 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& retSizeOnStack = returnTypes.front()->sizeOnStack(); } solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), ""); - solAssert(retSizeOnStack <= 15, "Stack is too deep."); + if (retSizeOnStack > 15) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_varDecl.location()) << + errinfo_comment("Stack too deep.") + ); m_context << dupInstruction(retSizeOnStack + 1); m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); } @@ -373,8 +378,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) m_context << u256(0) << Instruction::SUB; break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " + - string(Token::toString(_unaryOperation.getOperator())))); + solAssert(false, "Invalid unary operator: " + string(Token::toString(_unaryOperation.getOperator()))); } return false; } @@ -542,7 +546,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::External: case FunctionType::Kind::CallCode: case FunctionType::Kind::DelegateCall: - case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: _functionCall.expression().accept(*this); @@ -638,7 +642,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypePointers{}, strings(), strings(), - FunctionType::Kind::Bare, + FunctionType::Kind::BareCall, false, nullptr, false, @@ -895,7 +899,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; } default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type.")); + solAssert(false, "Invalid function type."); } } return false; @@ -969,7 +973,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case FunctionType::Kind::DelegateCall: case FunctionType::Kind::CallCode: case FunctionType::Kind::Send: - case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::Transfer: @@ -1061,7 +1065,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) true ); else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); + solAssert(false, "Invalid member access to integer"); break; case Type::Category::Function: solAssert(!!_memberAccess.expression().annotation().type->memberType(member), @@ -1095,7 +1099,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) m_context << u256(0) << Instruction::CALLDATALOAD << (u256(0xffffffff) << (256 - 32)) << Instruction::AND; else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); + solAssert(false, "Unknown magic member."); break; case Type::Category::Struct: { @@ -1172,7 +1176,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) break; } default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type.")); + solAssert(false, "Member access to unknown type."); } return false; } @@ -1327,7 +1331,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } else { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); + solAssert(false, "Identifier type not expected in expression context."); } } @@ -1410,7 +1414,7 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type m_context << (isSigned ? Instruction::SLT : Instruction::LT); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator.")); + solAssert(false, "Unknown comparison operator."); } } } @@ -1422,7 +1426,7 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator else if (Token::isBitOp(_operator)) appendBitOperatorCode(_operator); else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator.")); + solAssert(false, "Unknown binary operator."); } void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) @@ -1461,7 +1465,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty m_context << Instruction::EXP; break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator.")); + solAssert(false, "Unknown arithmetic operator."); } } @@ -1479,7 +1483,7 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) m_context << Instruction::XOR; break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown bit operator.")); + solAssert(false, "Unknown bit operator."); } } @@ -1523,7 +1527,7 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co break; case Token::SHR: default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator.")); + solAssert(false, "Unknown shift operator."); } } @@ -1556,7 +1560,7 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); auto funKind = _functionType.kind(); - bool returnSuccessCondition = funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode; + bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; @@ -1575,7 +1579,7 @@ void ExpressionCompiler::appendExternalFunctionCall( TypePointers parameterTypes = _functionType.parameterTypes(); bool manualFunctionId = false; if ( - (funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) && + (funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) && !_arguments.empty() ) { @@ -1618,7 +1622,7 @@ void ExpressionCompiler::appendExternalFunctionCall( // zero bytes (which we cannot detect). solAssert(0 < retSize && retSize <= 32, ""); utils().fetchFreeMemoryPointer(); - m_context << Instruction::DUP1 << u256(0) << Instruction::MSTORE; + m_context << u256(0) << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(32) << Instruction::ADD; utils().storeFreeMemoryPointer(); } @@ -1708,7 +1712,7 @@ void ExpressionCompiler::appendExternalFunctionCall( u256 gasNeededByCaller = eth::GasCosts::callGas + 10; if (_functionType.valueSet()) gasNeededByCaller += eth::GasCosts::callValueTransferGas; - if (!isCallCode && !isDelegateCall && !existenceChecked) + if (!existenceChecked) gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB; } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 7a87875c..7c66a843 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -87,6 +87,7 @@ void CompilerStack::reset(bool _keepSources) m_stackState = Empty; m_sources.clear(); } + m_libraries.clear(); m_optimize = false; m_optimizeRuns = 200; m_globalContext.reset(); @@ -106,12 +107,6 @@ bool CompilerStack::addSource(string const& _name, string const& _content, bool return existed; } -void CompilerStack::setSource(string const& _sourceCode) -{ - reset(); - addSource("", _sourceCode); -} - bool CompilerStack::parse() { //reset @@ -252,44 +247,17 @@ bool CompilerStack::analyze() return false; } -bool CompilerStack::parse(string const& _sourceCode) -{ - setSource(_sourceCode); - return parse(); -} - bool CompilerStack::parseAndAnalyze() { return parse() && analyze(); } -bool CompilerStack::parseAndAnalyze(std::string const& _sourceCode) -{ - setSource(_sourceCode); - return parseAndAnalyze(); -} - -vector<string> CompilerStack::contractNames() const -{ - if (m_stackState < AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); - vector<string> contractNames; - for (auto const& contract: m_contracts) - contractNames.push_back(contract.first); - return contractNames; -} - - -bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> const& _libraries) +bool CompilerStack::compile() { if (m_stackState < AnalysisSuccessful) if (!parseAndAnalyze()) return false; - m_optimize = _optimize; - m_optimizeRuns = _runs; - m_libraries = _libraries; - map<ContractDefinition const*, eth::Assembly const*> compiledContracts; for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) @@ -300,11 +268,6 @@ bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> co return true; } -bool CompilerStack::compile(string const& _sourceCode, bool _optimize, unsigned _runs) -{ - return parseAndAnalyze(_sourceCode) && compile(_optimize, _runs); -} - void CompilerStack::link() { for (auto& contract: m_contracts) @@ -315,6 +278,16 @@ void CompilerStack::link() } } +vector<string> CompilerStack::contractNames() const +{ + if (m_stackState < AnalysisSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + vector<string> contractNames; + for (auto const& contract: m_contracts) + contractNames.push_back(contract.first); + return contractNames; +} + eth::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const { Contract const& currentContract = contract(_contractName); @@ -462,7 +435,7 @@ Json::Value const& CompilerStack::natspec(Contract const& _contract, Documentati doc->reset(new Json::Value(Natspec::devDocumentation(*_contract.contract))); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal documentation type.")); + solAssert(false, "Illegal documentation type."); } return *(*doc); @@ -666,7 +639,7 @@ void CompilerStack::compileContract( { if ( _compiledContracts.count(&_contract) || - !_contract.annotation().isFullyImplemented || + !_contract.annotation().unimplementedFunctions.empty() || !_contract.constructorIsPublic() ) return; @@ -692,11 +665,11 @@ void CompilerStack::compileContract( } catch(eth::OptimizerException const&) { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for bytecode")); + solAssert(false, "Assembly optimizer exception for bytecode"); } catch(eth::AssemblyException const&) { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for bytecode")); + solAssert(false, "Assembly exception for bytecode"); } try @@ -705,11 +678,11 @@ void CompilerStack::compileContract( } catch(eth::OptimizerException const&) { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for deployed bytecode")); + solAssert(false, "Assembly optimizer exception for deployed bytecode"); } catch(eth::AssemblyException const&) { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for deployed bytecode")); + solAssert(false, "Assembly exception for deployed bytecode"); } compiledContract.metadata = metadata; @@ -780,9 +753,18 @@ string CompilerStack::createMetadata(Contract const& _contract) const meta["language"] = "Solidity"; meta["compiler"]["version"] = VersionStringStrict; + /// All the source files (including self), which should be included in the metadata. + set<string> referencedSources; + referencedSources.insert(_contract.contract->sourceUnit().annotation().path); + for (auto const sourceUnit: _contract.contract->sourceUnit().referencedSourceUnits(true)) + referencedSources.insert(sourceUnit->annotation().path); + meta["sources"] = Json::objectValue; for (auto const& s: m_sources) { + if (!referencedSources.count(s.first)) + continue; + solAssert(s.second.scanner, "Scanner not available"); meta["sources"][s.first]["keccak256"] = "0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes()); @@ -953,7 +935,7 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const for (auto const& it: contract.definedFunctions()) { /// Exclude externally visible functions, constructor and the fallback function - if (it->isPartOfExternalInterface() || it->isConstructor() || it->name().empty()) + if (it->isPartOfExternalInterface() || it->isConstructor() || it->isFallback()) continue; size_t entry = functionEntryPoint(_contractName, *it); @@ -961,12 +943,14 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const if (entry > 0) gas = GasEstimator::functionalEstimation(*items, entry, *it); + /// TODO: This could move into a method shared with externalSignature() FunctionType type(*it); string sig = it->name() + "("; auto paramTypes = type.parameterTypes(); for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ","); sig += ")"; + internalFunctions[sig] = gasToJson(gas); } diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 6b8fb538..d287f224 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -93,87 +93,116 @@ public: m_errorList(), m_errorReporter(m_errorList) {} - /// Sets path remappings in the format "context:prefix=target" - void setRemappings(std::vector<std::string> const& _remappings); + /// @returns the list of errors that occured during parsing and type checking. + ErrorList const& errors() { return m_errorReporter.errors(); } + + /// @returns the current state. + State state() const { return m_stackState; } /// Resets the compiler to a state where the sources are not parsed or even removed. + /// Sets the state to SourcesSet if @a _keepSources is true, otherwise to Empty. + /// All settings, with the exception of remappings, are reset. void reset(bool _keepSources = false); - /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. - /// @returns true if a source object by the name already existed and was replaced. - void addSources(StringMap const& _nameContents, bool _isLibrary = false) + /// Sets path remappings in the format "context:prefix=target" + void setRemappings(std::vector<std::string> const& _remappings); + + /// Sets library addresses. Addresses are cleared iff @a _libraries is missing. + /// Will not take effect before running compile. + void setLibraries(std::map<std::string, h160> const& _libraries = std::map<std::string, h160>{}) { - for (auto const& i: _nameContents) addSource(i.first, i.second, _isLibrary); + m_libraries = _libraries; } + + /// Changes the optimiser settings. + /// Will not take effect before running compile. + void setOptimiserSettings(bool _optimize, unsigned _runs = 200) + { + m_optimize = _optimize; + m_optimizeRuns = _runs; + } + + /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. + /// @returns true if a source object by the name already existed and was replaced. bool addSource(std::string const& _name, std::string const& _content, bool _isLibrary = false); - void setSource(std::string const& _sourceCode); + /// Parses all source units that were added /// @returns false on error. bool parse(); - /// Sets the given source code as the only source unit apart from standard sources and parses it. - /// @returns false on error. - bool parse(std::string const& _sourceCode); - /// performs the analyisis steps (imports, scopesetting, syntaxCheck, referenceResolving, + + /// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving, /// typechecking, staticAnalysis) on previously set sources /// @returns false on error. bool analyze(); + /// Parses and analyzes all source units that were added /// @returns false on error. bool parseAndAnalyze(); - /// Sets the given source code as the only source unit apart from standard sources and parses and analyzes it. - /// @returns false on error. - bool parseAndAnalyze(std::string const& _sourceCode); + /// @returns a list of the contract names in the sources. std::vector<std::string> contractNames() const; /// Compiles the source units that were previously added and parsed. /// @returns false on error. - bool compile( - bool _optimize = false, - unsigned _runs = 200, - std::map<std::string, h160> const& _libraries = std::map<std::string, h160>{} - ); - /// Parses and compiles the given source code. - /// @returns false on error. - bool compile(std::string const& _sourceCode, bool _optimize = false, unsigned _runs = 200); + bool compile(); + + /// @returns the list of sources (paths) used + std::vector<std::string> sourceNames() const; + + /// @returns a mapping assigning each source name its index inside the vector returned + /// by sourceNames(). + std::map<std::string, unsigned> sourceIndices() const; + + /// @returns the previously used scanner, useful for counting lines during error reporting. + Scanner const& scanner(std::string const& _sourceName = "") const; + + /// @returns the parsed source unit with the supplied name. + SourceUnit const& ast(std::string const& _sourceName = "") const; + + /// Helper function for logs printing. Do only use in error cases, it's quite expensive. + /// line and columns are numbered starting from 1 with following order: + /// start line, start column, end line, end column + std::tuple<int, int, int, int> positionFromSourceLocation(SourceLocation const& _sourceLocation) const; + + /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use + std::string const filesystemFriendlyName(std::string const& _contractName) const; /// @returns the assembled object for a contract. eth::LinkerObject const& object(std::string const& _contractName = "") const; + /// @returns the runtime object for the contract. eth::LinkerObject const& runtimeObject(std::string const& _contractName = "") const; + /// @returns the bytecode of a contract that uses an already deployed contract via DELEGATECALL. /// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to /// substituted by the actual address. Note that this sequence starts end ends in three X /// characters but can contain anything in between. eth::LinkerObject const& cloneObject(std::string const& _contractName = "") const; + /// @returns normal contract assembly items eth::AssemblyItems const* assemblyItems(std::string const& _contractName = "") const; + /// @returns runtime contract assembly items eth::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName = "") const; + /// @returns the string that provides a mapping between bytecode and sourcecode or a nullptr /// if the contract does not (yet) have bytecode. std::string const* sourceMapping(std::string const& _contractName = "") const; + /// @returns the string that provides a mapping between runtime bytecode and sourcecode. /// if the contract does not (yet) have bytecode. std::string const* runtimeSourceMapping(std::string const& _contractName = "") const; - /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use - std::string const filesystemFriendlyName(std::string const& _contractName) const; - /// Streams a verbose version of the assembly to @a _outStream. /// @arg _sourceCodes is the map of input files to source code strings /// @arg _inJsonFromat shows whether the out should be in Json format /// Prerequisite: Successful compilation. Json::Value streamAssembly(std::ostream& _outStream, std::string const& _contractName = "", StringMap _sourceCodes = StringMap(), bool _inJsonFormat = false) const; - /// @returns the list of sources (paths) used - std::vector<std::string> sourceNames() const; - /// @returns a mapping assigning each source name its index inside the vector returned - /// by sourceNames(). - std::map<std::string, unsigned> sourceIndices() const; /// @returns a JSON representing the contract ABI. /// Prerequisite: Successful call to parse or compile. Json::Value const& contractABI(std::string const& _contractName = "") const; + /// @returns a JSON representing the contract's documentation. /// Prerequisite: Successful call to parse or compile. /// @param type The type of the documentation to get. @@ -183,27 +212,13 @@ public: /// @returns a JSON representing a map of method identifiers (hashes) to function names. Json::Value methodIdentifiers(std::string const& _contractName) const; + /// @returns the Contract Metadata std::string const& metadata(std::string const& _contractName) const; void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions Json::Value gasEstimates(std::string const& _contractName) const; - /// @returns the previously used scanner, useful for counting lines during error reporting. - Scanner const& scanner(std::string const& _sourceName = "") const; - /// @returns the parsed source unit with the supplied name. - SourceUnit const& ast(std::string const& _sourceName = "") const; - - /// Helper function for logs printing. Do only use in error cases, it's quite expensive. - /// line and columns are numbered starting from 1 with following order: - /// start line, start column, end line, end column - std::tuple<int, int, int, int> positionFromSourceLocation(SourceLocation const& _sourceLocation) const; - - /// @returns the list of errors that occured during parsing and type checking. - ErrorList const& errors() { return m_errorReporter.errors(); } - - State state() const { return m_stackState; } - private: /** * Information pertaining to one source unit, filled gradually during parsing and compilation. @@ -230,6 +245,7 @@ private: mutable std::unique_ptr<std::string const> sourceMapping; mutable std::unique_ptr<std::string const> runtimeSourceMapping; }; + /// Loads the missing sources from @a _ast (named @a _path) using the callback /// @a m_readFile and stores the absolute paths of all imports in the AST annotations. /// @returns the newly loaded sources. diff --git a/libsolidity/interface/ErrorReporter.cpp b/libsolidity/interface/ErrorReporter.cpp index 6e2667a5..e6171756 100644 --- a/libsolidity/interface/ErrorReporter.cpp +++ b/libsolidity/interface/ErrorReporter.cpp @@ -42,11 +42,23 @@ void ErrorReporter::warning(string const& _description) error(Error::Type::Warning, SourceLocation(), _description); } -void ErrorReporter::warning(SourceLocation const& _location, string const& _description) +void ErrorReporter::warning( + SourceLocation const& _location, + string const& _description +) { error(Error::Type::Warning, _location, _description); } +void ErrorReporter::warning( + SourceLocation const& _location, + string const& _description, + SecondarySourceLocation const& _secondaryLocation +) +{ + error(Error::Type::Warning, _location, _secondaryLocation, _description); +} + void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description) { auto err = make_shared<Error>(_type); @@ -139,6 +151,16 @@ void ErrorReporter::syntaxError(SourceLocation const& _location, string const& _ ); } +void ErrorReporter::typeError(SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +{ + error( + Error::Type::TypeError, + _location, + _secondaryLocation, + _description + ); +} + void ErrorReporter::typeError(SourceLocation const& _location, string const& _description) { error( diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h index e5605d24..42b0c8b6 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/libsolidity/interface/ErrorReporter.h @@ -41,30 +41,30 @@ public: ErrorReporter& operator=(ErrorReporter const& _errorReporter); - void warning(std::string const& _description = std::string()); + void warning(std::string const& _description); + + void warning(SourceLocation const& _location, std::string const& _description); void warning( - SourceLocation const& _location = SourceLocation(), - std::string const& _description = std::string() + SourceLocation const& _location, + std::string const& _description, + SecondarySourceLocation const& _secondaryLocation ); void error( Error::Type _type, - SourceLocation const& _location = SourceLocation(), - std::string const& _description = std::string() - ); - - void declarationError( SourceLocation const& _location, - SecondarySourceLocation const& _secondaryLocation = SecondarySourceLocation(), - std::string const& _description = std::string() + std::string const& _description ); void declarationError( SourceLocation const& _location, - std::string const& _description = std::string() + SecondarySourceLocation const& _secondaryLocation, + std::string const& _description ); + void declarationError(SourceLocation const& _location, std::string const& _description); + void fatalDeclarationError(SourceLocation const& _location, std::string const& _description); void parserError(SourceLocation const& _location, std::string const& _description); @@ -73,6 +73,12 @@ public: void syntaxError(SourceLocation const& _location, std::string const& _description); + void typeError( + SourceLocation const& _location, + SecondarySourceLocation const& _secondaryLocation, + std::string const& _description + ); + void typeError(SourceLocation const& _location, std::string const& _description); void fatalTypeError(SourceLocation const& _location, std::string const& _description); diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 7730a99a..62d22999 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -101,6 +101,8 @@ void SourceReferenceFormatter::printExceptionInformation( _stream << _name; if (string const* description = boost::get_error_info<errinfo_comment>(_exception)) _stream << ": " << *description << endl; + else + _stream << endl; printSourceLocation(_stream, location, _scannerFromSourceName); @@ -108,9 +110,8 @@ void SourceReferenceFormatter::printExceptionInformation( { for (auto info: secondarylocation->infos) { - _stream << info.first << " "; printSourceName(_stream, &info.second, _scannerFromSourceName); - _stream << endl; + _stream << info.first << endl; printSourceLocation(_stream, &info.second, _scannerFromSourceName); } _stream << endl; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 82eeac3d..dd135ce5 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -249,6 +249,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) Json::Value optimizerSettings = settings.get("optimizer", Json::Value()); bool const optimize = optimizerSettings.get("enabled", Json::Value(false)).asBool(); unsigned const optimizeRuns = optimizerSettings.get("runs", Json::Value(200u)).asUInt(); + m_compilerStack.setOptimiserSettings(optimize, optimizeRuns); map<string, h160> libraries; Json::Value jsonLibraries = settings.get("libraries", Json::Value()); @@ -259,6 +260,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // @TODO use libraries only for the given source libraries[library] = h160(jsonSourceName[library].asString()); } + m_compilerStack.setLibraries(libraries); Json::Value metadataSettings = settings.get("metadata", Json::Value()); m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool()); @@ -267,7 +269,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) try { - m_compilerStack.compile(optimize, optimizeRuns, libraries); + m_compilerStack.compile(); for (auto const& error: m_compilerStack.errors()) { @@ -283,24 +285,27 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) )); } } + /// This is only thrown in a very few locations. catch (Error const& _error) { - if (_error.type() == Error::Type::DocstringParsingError) - errors.append(formatError( - false, - "DocstringParsingError", - "general", - "Documentation parsing error: " + *boost::get_error_info<errinfo_comment>(_error) - )); - else - errors.append(formatErrorWithException( - _error, - false, - _error.typeName(), - "general", - "", - scannerFromSourceName - )); + errors.append(formatErrorWithException( + _error, + false, + _error.typeName(), + "general", + "Uncaught error: ", + scannerFromSourceName + )); + } + /// This should not be leaked from compile(). + catch (FatalError const& _exception) + { + errors.append(formatError( + false, + "FatalError", + "general", + "Uncaught fatal error: " + boost::diagnostic_information(_exception) + )); } catch (CompilerError const& _exception) { @@ -320,7 +325,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, "InternalCompilerError", "general", - "Internal compiler error (" + _exception.lineInfo() + ")", scannerFromSourceName + "Internal compiler error (" + _exception.lineInfo() + ")", + scannerFromSourceName )); } catch (UnimplementedFeatureError const& _exception) @@ -331,7 +337,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) "UnimplementedFeatureError", "general", "Unimplemented feature (" + _exception.lineInfo() + ")", - scannerFromSourceName)); + scannerFromSourceName + )); } catch (Exception const& _exception) { diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index b98991f3..a6d6e025 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -321,7 +321,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN while (true) { Token::Value token = m_scanner->currentToken(); - if (token == Token::Const) + if (token == Token::Constant) { if (result.isDeclaredConst) parserError(string("Multiple \"constant\" specifiers.")); @@ -354,7 +354,11 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN { if (result.visibility != Declaration::Visibility::Default) { - parserError(string("Multiple visibility specifiers.")); + parserError(string( + "Visibility already specified as \"" + + Declaration::visibilityToString(result.visibility) + + "\"." + )); m_scanner->next(); } else @@ -512,7 +516,11 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( { if (visibility != Declaration::Visibility::Default) { - parserError(string("Visibility already specified.")); + parserError(string( + "Visibility already specified as \"" + + Declaration::visibilityToString(visibility) + + "\"." + )); m_scanner->next(); } else @@ -522,7 +530,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( { if (_options.allowIndexed && token == Token::Indexed) isIndexed = true; - else if (token == Token::Const) + else if (token == Token::Constant) isDeclaredConst = true; else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token)) { diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 19631c58..82ab91a6 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -73,6 +73,7 @@ private: ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); + std::string visibilitySpecifierName(Declaration::Visibility _visibility); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index d412b3f0..468cbcb7 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -143,7 +143,7 @@ namespace solidity K(As, "as", 0) \ K(Assembly, "assembly", 0) \ K(Break, "break", 0) \ - K(Const, "constant", 0) \ + K(Constant, "constant", 0) \ K(Continue, "continue", 0) \ K(Contract, "contract", 0) \ K(Do, "do", 0) \ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c9f67566..db976bc3 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -13,7 +13,7 @@ description: | It is possible to create contracts for voting, crowdfunding, blind auctions, multi-signature wallets and more. -grade: devel # must be 'stable' to release into candidate/stable channels +grade: stable confinement: strict apps: diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index db497d84..740061a1 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -774,10 +774,14 @@ bool CommandLineInterface::processInput() m_compiler->setRemappings(m_args[g_argInputFile].as<vector<string>>()); for (auto const& sourceCode: m_sourceCodes) m_compiler->addSource(sourceCode.first, sourceCode.second); + if (m_args.count(g_argLibraries)) + m_compiler->setLibraries(m_libraries); // TODO: Perhaps we should not compile unless requested bool optimize = m_args.count(g_argOptimize) > 0; unsigned runs = m_args[g_argOptimizeRuns].as<unsigned>(); - bool successful = m_compiler->compile(optimize, runs, m_libraries); + m_compiler->setOptimiserSettings(optimize, runs); + + bool successful = m_compiler->compile(); for (auto const& error: m_compiler->errors()) SourceReferenceFormatter::printExceptionInformation( diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index de797b3c..ab928ac0 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -38,6 +38,9 @@ extern "C" { typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); } +namespace +{ + ReadFile::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullptr) { ReadFile::Callback readCallback; @@ -260,6 +263,8 @@ string compileStandardInternal(string const& _input, CStyleReadFileCallback _rea return compiler.compile(_input); } +} + static string s_outputBuffer; extern "C" diff --git a/solc/main.cpp b/solc/main.cpp index c61da6e9..6d559542 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -40,7 +40,7 @@ specified default locale if it is valid, and if not then it will modify the environment the process is running in to use a sensible default. This also means that users do not need to install language packs for their OS. */ -void setDefaultOrCLocale() +static void setDefaultOrCLocale() { #if __unix__ if (!std::setlocale(LC_ALL, "")) diff --git a/test/boostTest.cpp b/test/boostTest.cpp index c2121940..d8c5b678 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -46,6 +46,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) if (dev::test::Options::get().disableIPC) { for (auto suite: { + "ABIEncoderTest", "SolidityAuctionRegistrar", "SolidityFixedFeeRegistrar", "SolidityWallet", diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index fb8c1c68..773b14b9 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -223,7 +223,8 @@ protected: { m_compiler.reset(false); m_compiler.addSource("", registrarCode); - ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); + m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); s_compiledRegistrar.reset(new bytes(m_compiler.object("GlobalRegistrar").bytecode)); } sendMessage(*s_compiledRegistrar, true); diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index d2904b5f..967d60b6 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -135,7 +135,8 @@ protected: { m_compiler.reset(false); m_compiler.addSource("", registrarCode); - ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); + m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); s_compiledRegistrar.reset(new bytes(m_compiler.object("FixedFeeRegistrar").bytecode)); } sendMessage(*s_compiledRegistrar, true); diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index ef345d86..d12b7493 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -450,7 +450,8 @@ protected: { m_compiler.reset(false); m_compiler.addSource("", walletCode); - ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); + m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); s_compiledWallet.reset(new bytes(m_compiler.object("Wallet").bytecode)); } bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp new file mode 100644 index 00000000..5aa81af5 --- /dev/null +++ b/test/libevmasm/Optimiser.cpp @@ -0,0 +1,871 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Tests for the Solidity optimizer. + */ + +#include <libevmasm/CommonSubexpressionEliminator.h> +#include <libevmasm/PeepholeOptimiser.h> +#include <libevmasm/ControlFlowGraph.h> +#include <libevmasm/BlockDeduplicator.h> +#include <libevmasm/Assembly.h> + +#include <boost/test/unit_test.hpp> +#include <boost/lexical_cast.hpp> + +#include <chrono> +#include <string> +#include <tuple> +#include <memory> + +using namespace std; +using namespace dev::eth; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ + AssemblyItems addDummyLocations(AssemblyItems const& _input) + { + // add dummy locations to each item so that we can check that they are not deleted + AssemblyItems input = _input; + for (AssemblyItem& item: input) + item.setLocation(SourceLocation(1, 3, make_shared<string>(""))); + return input; + } + + eth::KnownState createInitialState(AssemblyItems const& _input) + { + eth::KnownState state; + for (auto const& item: addDummyLocations(_input)) + state.feedItem(item, true); + return state; + } + + AssemblyItems CSE(AssemblyItems const& _input, eth::KnownState const& _state = eth::KnownState()) + { + AssemblyItems input = addDummyLocations(_input); + + eth::CommonSubexpressionEliminator cse(_state); + BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); + AssemblyItems output = cse.getOptimizedItems(); + + for (AssemblyItem const& item: output) + { + BOOST_CHECK(item == Instruction::POP || !item.location().isEmpty()); + } + return output; + } + + void checkCSE( + AssemblyItems const& _input, + AssemblyItems const& _expectation, + KnownState const& _state = eth::KnownState() + ) + { + AssemblyItems output = CSE(_input, _state); + BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); + } + + AssemblyItems CFG(AssemblyItems const& _input) + { + AssemblyItems output = _input; + // Running it four times should be enough for these tests. + for (unsigned i = 0; i < 4; ++i) + { + ControlFlowGraph cfg(output); + AssemblyItems optItems; + for (BasicBlock const& block: cfg.optimisedBlocks()) + copy(output.begin() + block.begin, output.begin() + block.end, + back_inserter(optItems)); + output = move(optItems); + } + return output; + } + + void checkCFG(AssemblyItems const& _input, AssemblyItems const& _expectation) + { + AssemblyItems output = CFG(_input); + BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); + } +} + +BOOST_AUTO_TEST_SUITE(Optimiser) + +BOOST_AUTO_TEST_CASE(cse_intermediate_swap) +{ + eth::KnownState state; + eth::CommonSubexpressionEliminator cse(state); + AssemblyItems input{ + Instruction::SWAP1, Instruction::POP, Instruction::ADD, u256(0), Instruction::SWAP1, + Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1, + Instruction::DIV, u256(0xff), Instruction::AND + }; + BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); + AssemblyItems output = cse.getOptimizedItems(); + BOOST_CHECK(!output.empty()); +} + +BOOST_AUTO_TEST_CASE(cse_negative_stack_access) +{ + AssemblyItems input{Instruction::DUP2, u256(0)}; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_negative_stack_end) +{ + AssemblyItems input{Instruction::ADD}; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_intermediate_negative_stack) +{ + AssemblyItems input{Instruction::ADD, u256(1), Instruction::DUP1}; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_pop) +{ + checkCSE({Instruction::POP}, {Instruction::POP}); +} + +BOOST_AUTO_TEST_CASE(cse_unneeded_items) +{ + AssemblyItems input{ + Instruction::ADD, + Instruction::SWAP1, + Instruction::POP, + u256(7), + u256(8), + }; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_constant_addition) +{ + AssemblyItems input{u256(7), u256(8), Instruction::ADD}; + checkCSE(input, {u256(7 + 8)}); +} + +BOOST_AUTO_TEST_CASE(cse_invariants) +{ + AssemblyItems input{ + Instruction::DUP1, + Instruction::DUP1, + u256(0), + Instruction::OR, + Instruction::OR + }; + checkCSE(input, {Instruction::DUP1}); +} + +BOOST_AUTO_TEST_CASE(cse_subself) +{ + checkCSE({Instruction::DUP1, Instruction::SUB}, {Instruction::POP, u256(0)}); +} + +BOOST_AUTO_TEST_CASE(cse_subother) +{ + checkCSE({Instruction::SUB}, {Instruction::SUB}); +} + +BOOST_AUTO_TEST_CASE(cse_double_negation) +{ + checkCSE({Instruction::DUP5, Instruction::NOT, Instruction::NOT}, {Instruction::DUP5}); +} + +BOOST_AUTO_TEST_CASE(cse_double_iszero) +{ + checkCSE({Instruction::GT, Instruction::ISZERO, Instruction::ISZERO}, {Instruction::GT}); + checkCSE({Instruction::GT, Instruction::ISZERO}, {Instruction::GT, Instruction::ISZERO}); + checkCSE( + {Instruction::ISZERO, Instruction::ISZERO, Instruction::ISZERO}, + {Instruction::ISZERO} + ); +} + +BOOST_AUTO_TEST_CASE(cse_associativity) +{ + AssemblyItems input{ + Instruction::DUP1, + Instruction::DUP1, + u256(0), + Instruction::OR, + Instruction::OR + }; + checkCSE(input, {Instruction::DUP1}); +} + +BOOST_AUTO_TEST_CASE(cse_associativity2) +{ + AssemblyItems input{ + u256(0), + Instruction::DUP2, + u256(2), + u256(1), + Instruction::DUP6, + Instruction::ADD, + u256(2), + Instruction::ADD, + Instruction::ADD, + Instruction::ADD, + Instruction::ADD + }; + checkCSE(input, {Instruction::DUP2, Instruction::DUP2, Instruction::ADD, u256(5), Instruction::ADD}); +} + +BOOST_AUTO_TEST_CASE(cse_storage) +{ + AssemblyItems input{ + u256(0), + Instruction::SLOAD, + u256(0), + Instruction::SLOAD, + Instruction::ADD, + u256(0), + Instruction::SSTORE + }; + checkCSE(input, { + u256(0), + Instruction::DUP1, + Instruction::SLOAD, + Instruction::DUP1, + Instruction::ADD, + Instruction::SWAP1, + Instruction::SSTORE + }); +} + +BOOST_AUTO_TEST_CASE(cse_noninterleaved_storage) +{ + // two stores to the same location should be replaced by only one store, even if we + // read in the meantime + AssemblyItems input{ + u256(7), + Instruction::DUP2, + Instruction::SSTORE, + Instruction::DUP1, + Instruction::SLOAD, + u256(8), + Instruction::DUP3, + Instruction::SSTORE + }; + checkCSE(input, { + u256(8), + Instruction::DUP2, + Instruction::SSTORE, + u256(7) + }); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage) +{ + // stores and reads to/from two unknown locations, should not optimize away the first store + AssemblyItems input{ + u256(7), + Instruction::DUP2, + Instruction::SSTORE, // store to "DUP1" + Instruction::DUP2, + Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1" + u256(0), + Instruction::DUP3, + Instruction::SSTORE // store different value to "DUP1" + }; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage_same_value) +{ + // stores and reads to/from two unknown locations, should not optimize away the first store + // but it should optimize away the second, since we already know the value will be the same + AssemblyItems input{ + u256(7), + Instruction::DUP2, + Instruction::SSTORE, // store to "DUP1" + Instruction::DUP2, + Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1" + u256(6), + u256(1), + Instruction::ADD, + Instruction::DUP3, + Instruction::SSTORE // store same value to "DUP1" + }; + checkCSE(input, { + u256(7), + Instruction::DUP2, + Instruction::SSTORE, + Instruction::DUP2, + Instruction::SLOAD + }); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location) +{ + // stores and reads to/from two known locations, should optimize away the first store, + // because we know that the location is different + AssemblyItems input{ + u256(0x70), + u256(1), + Instruction::SSTORE, // store to 1 + u256(2), + Instruction::SLOAD, // read from 2, is different from 1 + u256(0x90), + u256(1), + Instruction::SSTORE // store different value at 1 + }; + checkCSE(input, { + u256(2), + Instruction::SLOAD, + u256(0x90), + u256(1), + Instruction::SSTORE + }); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location_offset) +{ + // stores and reads to/from two locations which are known to be different, + // should optimize away the first store, because we know that the location is different + AssemblyItems input{ + u256(0x70), + Instruction::DUP2, + u256(1), + Instruction::ADD, + Instruction::SSTORE, // store to "DUP1"+1 + Instruction::DUP1, + u256(2), + Instruction::ADD, + Instruction::SLOAD, // read from "DUP1"+2, is different from "DUP1"+1 + u256(0x90), + Instruction::DUP3, + u256(1), + Instruction::ADD, + Instruction::SSTORE // store different value at "DUP1"+1 + }; + checkCSE(input, { + u256(2), + Instruction::DUP2, + Instruction::ADD, + Instruction::SLOAD, + u256(0x90), + u256(1), + Instruction::DUP4, + Instruction::ADD, + Instruction::SSTORE + }); +} + +BOOST_AUTO_TEST_CASE(cse_deep_stack) +{ + AssemblyItems input{ + Instruction::ADD, + Instruction::SWAP1, + Instruction::POP, + Instruction::SWAP8, + Instruction::POP, + Instruction::SWAP8, + Instruction::POP, + Instruction::SWAP8, + Instruction::SWAP5, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + }; + checkCSE(input, { + Instruction::SWAP4, + Instruction::SWAP12, + Instruction::SWAP3, + Instruction::SWAP11, + Instruction::POP, + Instruction::SWAP1, + Instruction::SWAP3, + Instruction::ADD, + Instruction::SWAP8, + Instruction::POP, + Instruction::SWAP6, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + }); +} + +BOOST_AUTO_TEST_CASE(cse_jumpi_no_jump) +{ + AssemblyItems input{ + u256(0), + u256(1), + Instruction::DUP2, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + checkCSE(input, { + u256(0), + u256(1) + }); +} + +BOOST_AUTO_TEST_CASE(cse_jumpi_jump) +{ + AssemblyItems input{ + u256(1), + u256(1), + Instruction::DUP2, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + checkCSE(input, { + u256(1), + Instruction::DUP1, + AssemblyItem(PushTag, 1), + Instruction::JUMP + }); +} + +BOOST_AUTO_TEST_CASE(cse_empty_keccak256) +{ + AssemblyItems input{ + u256(0), + Instruction::DUP2, + Instruction::KECCAK256 + }; + checkCSE(input, { + u256(dev::keccak256(bytesConstRef())) + }); +} + +BOOST_AUTO_TEST_CASE(cse_partial_keccak256) +{ + AssemblyItems input{ + u256(0xabcd) << (256 - 16), + u256(0), + Instruction::MSTORE, + u256(2), + u256(0), + Instruction::KECCAK256 + }; + checkCSE(input, { + u256(0xabcd) << (256 - 16), + u256(0), + Instruction::MSTORE, + u256(dev::keccak256(bytes{0xab, 0xcd})) + }); +} + +BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_location) +{ + // Keccak-256 twice from same dynamic location + AssemblyItems input{ + Instruction::DUP2, + Instruction::DUP1, + Instruction::MSTORE, + u256(64), + Instruction::DUP2, + Instruction::KECCAK256, + u256(64), + Instruction::DUP3, + Instruction::KECCAK256 + }; + checkCSE(input, { + Instruction::DUP2, + Instruction::DUP1, + Instruction::MSTORE, + u256(64), + Instruction::DUP2, + Instruction::KECCAK256, + Instruction::DUP1 + }); +} + +BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content) +{ + // Keccak-256 twice from different dynamic location but with same content + AssemblyItems input{ + Instruction::DUP1, + u256(0x80), + Instruction::MSTORE, // m[128] = DUP1 + u256(0x20), + u256(0x80), + Instruction::KECCAK256, // keccak256(m[128..(128+32)]) + Instruction::DUP2, + u256(12), + Instruction::MSTORE, // m[12] = DUP1 + u256(0x20), + u256(12), + Instruction::KECCAK256 // keccak256(m[12..(12+32)]) + }; + checkCSE(input, { + u256(0x80), + Instruction::DUP2, + Instruction::DUP2, + Instruction::MSTORE, + u256(0x20), + Instruction::SWAP1, + Instruction::KECCAK256, + u256(12), + Instruction::DUP3, + Instruction::SWAP1, + Instruction::MSTORE, + Instruction::DUP1 + }); +} + +BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_dynamic_store_in_between) +{ + // Keccak-256 twice from different dynamic location but with same content, + // dynamic mstore in between, which forces us to re-calculate the hash + AssemblyItems input{ + u256(0x80), + Instruction::DUP2, + Instruction::DUP2, + Instruction::MSTORE, // m[128] = DUP1 + u256(0x20), + Instruction::DUP1, + Instruction::DUP3, + Instruction::KECCAK256, // keccak256(m[128..(128+32)]) + u256(12), + Instruction::DUP5, + Instruction::DUP2, + Instruction::MSTORE, // m[12] = DUP1 + Instruction::DUP12, + Instruction::DUP14, + Instruction::MSTORE, // destroys memory knowledge + Instruction::SWAP2, + Instruction::SWAP1, + Instruction::SWAP2, + Instruction::KECCAK256 // keccak256(m[12..(12+32)]) + }; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_noninterfering_store_in_between) +{ + // Keccak-256 twice from different dynamic location but with same content, + // dynamic mstore in between, but does not force us to re-calculate the hash + AssemblyItems input{ + u256(0x80), + Instruction::DUP2, + Instruction::DUP2, + Instruction::MSTORE, // m[128] = DUP1 + u256(0x20), + Instruction::DUP1, + Instruction::DUP3, + Instruction::KECCAK256, // keccak256(m[128..(128+32)]) + u256(12), + Instruction::DUP5, + Instruction::DUP2, + Instruction::MSTORE, // m[12] = DUP1 + Instruction::DUP12, + u256(12 + 32), + Instruction::MSTORE, // does not destoy memory knowledge + Instruction::DUP13, + u256(128 - 32), + Instruction::MSTORE, // does not destoy memory knowledge + u256(0x20), + u256(12), + Instruction::KECCAK256 // keccak256(m[12..(12+32)]) + }; + // if this changes too often, only count the number of SHA3 and MSTORE instructions + AssemblyItems output = CSE(input); + BOOST_CHECK_EQUAL(4, count(output.begin(), output.end(), AssemblyItem(Instruction::MSTORE))); + BOOST_CHECK_EQUAL(1, count(output.begin(), output.end(), AssemblyItem(Instruction::KECCAK256))); +} + +BOOST_AUTO_TEST_CASE(cse_with_initially_known_stack) +{ + eth::KnownState state = createInitialState(AssemblyItems{ + u256(0x12), + u256(0x20), + Instruction::ADD + }); + AssemblyItems input{ + u256(0x12 + 0x20) + }; + checkCSE(input, AssemblyItems{Instruction::DUP1}, state); +} + +BOOST_AUTO_TEST_CASE(cse_equality_on_initially_known_stack) +{ + eth::KnownState state = createInitialState(AssemblyItems{Instruction::DUP1}); + AssemblyItems input{ + Instruction::EQ + }; + AssemblyItems output = CSE(input, state); + // check that it directly pushes 1 (true) + BOOST_CHECK(find(output.begin(), output.end(), AssemblyItem(u256(1))) != output.end()); +} + +BOOST_AUTO_TEST_CASE(cse_access_previous_sequence) +{ + // Tests that the code generator detects whether it tries to access SLOAD instructions + // from a sequenced expression which is not in its scope. + eth::KnownState state = createInitialState(AssemblyItems{ + u256(0), + Instruction::SLOAD, + u256(1), + Instruction::ADD, + u256(0), + Instruction::SSTORE + }); + // now stored: val_1 + 1 (value at sequence 1) + // if in the following instructions, the SLOAD cresolves to "val_1 + 1", + // this cannot be generated because we cannot load from sequence 1 anymore. + AssemblyItems input{ + u256(0), + Instruction::SLOAD, + }; + BOOST_CHECK_THROW(CSE(input, state), StackTooDeepException); + // @todo for now, this throws an exception, but it should recover to the following + // (or an even better version) at some point: + // 0, SLOAD, 1, ADD, SSTORE, 0 SLOAD +} + +BOOST_AUTO_TEST_CASE(cse_optimise_return) +{ + checkCSE( + AssemblyItems{u256(0), u256(7), Instruction::RETURN}, + AssemblyItems{Instruction::STOP} + ); +} + +BOOST_AUTO_TEST_CASE(control_flow_graph_remove_unused) +{ + // remove parts of the code that are unused + AssemblyItems input{ + AssemblyItem(PushTag, 1), + Instruction::JUMP, + u256(7), + AssemblyItem(Tag, 1), + }; + checkCFG(input, {}); +} + +BOOST_AUTO_TEST_CASE(control_flow_graph_remove_unused_loop) +{ + AssemblyItems input{ + AssemblyItem(PushTag, 3), + Instruction::JUMP, + AssemblyItem(Tag, 1), + u256(7), + AssemblyItem(PushTag, 2), + Instruction::JUMP, + AssemblyItem(Tag, 2), + u256(8), + AssemblyItem(PushTag, 1), + Instruction::JUMP, + AssemblyItem(Tag, 3), + u256(11) + }; + checkCFG(input, {u256(11)}); +} + +BOOST_AUTO_TEST_CASE(control_flow_graph_reconnect_single_jump_source) +{ + // move code that has only one unconditional jump source + AssemblyItems input{ + u256(1), + AssemblyItem(PushTag, 1), + Instruction::JUMP, + AssemblyItem(Tag, 2), + u256(2), + AssemblyItem(PushTag, 3), + Instruction::JUMP, + AssemblyItem(Tag, 1), + u256(3), + AssemblyItem(PushTag, 2), + Instruction::JUMP, + AssemblyItem(Tag, 3), + u256(4), + }; + checkCFG(input, {u256(1), u256(3), u256(2), u256(4)}); +} + +BOOST_AUTO_TEST_CASE(control_flow_graph_do_not_remove_returned_to) +{ + // do not remove parts that are "returned to" + AssemblyItems input{ + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 2), + Instruction::JUMP, + AssemblyItem(Tag, 2), + Instruction::JUMP, + AssemblyItem(Tag, 1), + u256(2) + }; + checkCFG(input, {u256(2)}); +} + +BOOST_AUTO_TEST_CASE(block_deduplicator) +{ + AssemblyItems input{ + AssemblyItem(PushTag, 2), + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 3), + u256(6), + Instruction::SWAP3, + Instruction::JUMP, + AssemblyItem(Tag, 1), + u256(6), + Instruction::SWAP3, + Instruction::JUMP, + AssemblyItem(Tag, 2), + u256(6), + Instruction::SWAP3, + Instruction::JUMP, + AssemblyItem(Tag, 3) + }; + BlockDeduplicator dedup(input); + dedup.deduplicate(); + + set<u256> pushTags; + for (AssemblyItem const& item: input) + if (item.type() == PushTag) + pushTags.insert(item.data()); + BOOST_CHECK_EQUAL(pushTags.size(), 2); +} + +BOOST_AUTO_TEST_CASE(block_deduplicator_loops) +{ + AssemblyItems input{ + u256(0), + Instruction::SLOAD, + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 2), + Instruction::JUMPI, + Instruction::JUMP, + AssemblyItem(Tag, 1), + u256(5), + u256(6), + Instruction::SSTORE, + AssemblyItem(PushTag, 1), + Instruction::JUMP, + AssemblyItem(Tag, 2), + u256(5), + u256(6), + Instruction::SSTORE, + AssemblyItem(PushTag, 2), + Instruction::JUMP, + }; + BlockDeduplicator dedup(input); + dedup.deduplicate(); + + set<u256> pushTags; + for (AssemblyItem const& item: input) + if (item.type() == PushTag) + pushTags.insert(item.data()); + BOOST_CHECK_EQUAL(pushTags.size(), 1); +} + +BOOST_AUTO_TEST_CASE(clear_unreachable_code) +{ + AssemblyItems items{ + AssemblyItem(PushTag, 1), + Instruction::JUMP, + u256(0), + Instruction::SLOAD, + AssemblyItem(Tag, 2), + u256(5), + u256(6), + Instruction::SSTORE, + AssemblyItem(PushTag, 1), + Instruction::JUMP, + u256(5), + u256(6) + }; + AssemblyItems expectation{ + AssemblyItem(PushTag, 1), + Instruction::JUMP, + AssemblyItem(Tag, 2), + u256(5), + u256(6), + Instruction::SSTORE, + AssemblyItem(PushTag, 1), + Instruction::JUMP + }; + PeepholeOptimiser peepOpt(items); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + +BOOST_AUTO_TEST_CASE(peephole_double_push) +{ + AssemblyItems items{ + u256(0), + u256(0), + u256(5), + u256(5), + u256(4), + u256(5) + }; + AssemblyItems expectation{ + u256(0), + Instruction::DUP1, + u256(5), + Instruction::DUP1, + u256(4), + u256(5) + }; + PeepholeOptimiser peepOpt(items); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + +BOOST_AUTO_TEST_CASE(cse_sub_zero) +{ + checkCSE({ + u256(0), + Instruction::DUP2, + Instruction::SUB + }, { + Instruction::DUP1 + }); + + checkCSE({ + Instruction::DUP1, + u256(0), + Instruction::SUB + }, { + u256(0), + Instruction::DUP2, + Instruction::SWAP1, + Instruction::SUB + }); +} + + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp new file mode 100644 index 00000000..44c673c5 --- /dev/null +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -0,0 +1,259 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for Solidity's ABI encoder. + */ + +#include <functional> +#include <string> +#include <tuple> +#include <boost/test/unit_test.hpp> +#include <libsolidity/interface/Exceptions.h> +#include <test/libsolidity/SolidityExecutionFramework.h> + +using namespace std; +using namespace std::placeholders; +using namespace dev::test; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +#define REQUIRE_LOG_DATA(DATA) do { \ + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); \ + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); \ + BOOST_CHECK_EQUAL(toHex(m_logs[0].data), toHex(DATA)); \ +} while (false) + +BOOST_FIXTURE_TEST_SUITE(ABIEncoderTest, SolidityExecutionFramework) + +BOOST_AUTO_TEST_CASE(value_types) +{ + char const* sourceCode = R"( + contract C { + event E(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool, C); + function f() { + bytes6 x = hex"1bababababa2"; + bool b; + assembly { b := 7 } + C c; + assembly { c := sub(0, 5) } + E(10, uint16(uint256(-2)), uint24(0x12121212), int24(int256(-1)), bytes3(x), b, c); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f()"); + REQUIRE_LOG_DATA(encodeArgs( + 10, u256(65534), u256(0x121212), u256(-1), string("\x1b\xab\xab"), true, u160(u256(-5)) + )); +} + +BOOST_AUTO_TEST_CASE(string_literal) +{ + char const* sourceCode = R"( + contract C { + event E(string, bytes20, string); + function f() { + E("abcdef", "abcde", "abcdefabcdefgehabcabcasdfjklabcdefabcedefghabcabcasdfjklabcdefabcdefghabcabcasdfjklabcdeefabcdefghabcabcasdefjklabcdefabcdefghabcabcasdfjkl"); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f()"); + REQUIRE_LOG_DATA(encodeArgs( + 0x60, string("abcde"), 0xa0, + 6, string("abcdef"), + 0x8b, string("abcdefabcdefgehabcabcasdfjklabcdefabcedefghabcabcasdfjklabcdefabcdefghabcabcasdfjklabcdeefabcdefghabcabcasdefjklabcdefabcdefghabcabcasdfjkl") + )); +} + + +BOOST_AUTO_TEST_CASE(enum_type_cleanup) +{ + char const* sourceCode = R"( + contract C { + enum E { A, B } + function f(uint x) returns (E en) { + assembly { en := x } + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(uint256)", 0) == encodeArgs(0)); + BOOST_CHECK(callContractFunction("f(uint256)", 1) == encodeArgs(1)); + BOOST_CHECK(callContractFunction("f(uint256)", 2) == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(conversion) +{ + char const* sourceCode = R"( + contract C { + event E(bytes4, bytes4, uint16, uint8, int16, int8); + function f() { + bytes2 x; assembly { x := 0xf1f2f3f400000000000000000000000000000000000000000000000000000000 } + uint8 a; + uint16 b = 0x1ff; + int8 c; + int16 d; + assembly { a := sub(0, 1) c := 0x0101ff d := 0xff01 } + E(10, x, a, uint8(b), c, int8(d)); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f()"); + REQUIRE_LOG_DATA(encodeArgs( + string(3, 0) + string("\x0a"), string("\xf1\xf2"), + 0xff, 0xff, u256(-1), u256(1) + )); +} + +BOOST_AUTO_TEST_CASE(storage_byte_array) +{ + char const* sourceCode = R"( + contract C { + bytes short; + bytes long; + event E(bytes s, bytes l); + function f() { + short = "123456789012345678901234567890a"; + long = "ffff123456789012345678901234567890afffffffff123456789012345678901234567890a"; + E(short, long); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f()"); + REQUIRE_LOG_DATA(encodeArgs( + 0x40, 0x80, + 31, string("123456789012345678901234567890a"), + 75, string("ffff123456789012345678901234567890afffffffff123456789012345678901234567890a") + )); +} + +BOOST_AUTO_TEST_CASE(storage_array) +{ + char const* sourceCode = R"( + contract C { + address[3] addr; + event E(address[3] a); + function f() { + assembly { + sstore(0, sub(0, 1)) + sstore(1, sub(0, 2)) + sstore(2, sub(0, 3)) + } + E(addr); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f()"); + REQUIRE_LOG_DATA(encodeArgs(u160(-1), u160(-2), u160(-3))); +} + +BOOST_AUTO_TEST_CASE(storage_array_dyn) +{ + char const* sourceCode = R"( + contract C { + address[] addr; + event E(address[] a); + function f() { + addr.push(1); + addr.push(2); + addr.push(3); + E(addr); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f()"); + REQUIRE_LOG_DATA(encodeArgs(0x20, 3, u160(1), u160(2), u160(3))); +} + +BOOST_AUTO_TEST_CASE(storage_array_compact) +{ + char const* sourceCode = R"( + contract C { + int72[] x; + event E(int72[]); + function f() { + x.push(-1); + x.push(2); + x.push(-3); + x.push(4); + x.push(-5); + x.push(6); + x.push(-7); + x.push(8); + E(x); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f()"); + REQUIRE_LOG_DATA(encodeArgs( + 0x20, 8, u256(-1), 2, u256(-3), 4, u256(-5), 6, u256(-7), 8 + )); +} + +BOOST_AUTO_TEST_CASE(external_function) +{ + char const* sourceCode = R"( + contract C { + event E(function(uint) external returns (uint), function(uint) external returns (uint)); + function(uint) external returns (uint) g; + function f(uint) returns (uint) { + g = this.f; + E(this.f, g); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f(uint256)"); + string functionIdF = asString(m_contractAddress.ref()) + asString(FixedHash<4>(dev::keccak256("f(uint256)")).ref()); + REQUIRE_LOG_DATA(encodeArgs(functionIdF, functionIdF)); +} + +BOOST_AUTO_TEST_CASE(external_function_cleanup) +{ + char const* sourceCode = R"( + contract C { + event E(function(uint) external returns (uint), function(uint) external returns (uint)); + // This test relies on the fact that g is stored in slot zero. + function(uint) external returns (uint) g; + function f(uint) returns (uint) { + function(uint) external returns (uint)[1] memory h; + assembly { sstore(0, sub(0, 1)) mstore(h, sub(0, 1)) } + E(h[0], g); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("f(uint256)"); + REQUIRE_LOG_DATA(encodeArgs(string(24, char(-1)), string(24, char(-1)))); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 8b41e1db..df9afaae 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -49,7 +49,8 @@ public: { m_compiler.reset(false); m_compiler.addSource("", "pragma solidity >=0.0;\n" + _sourceCode); - ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(dev::test::Options::get().optimize), "Compiling contract failed"); + m_compiler.setOptimiserSettings(dev::test::Options::get().optimize); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); AssemblyItems const* items = m_compiler.runtimeAssemblyItems(""); ASTNode const& sourceUnit = m_compiler.ast(); diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 6aa96fb8..03287b28 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -20,11 +20,15 @@ * Tests for high level features like import. */ -#include <string> -#include <boost/test/unit_test.hpp> +#include <test/libsolidity/ErrorCheck.h> + #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/CompilerStack.h> +#include <boost/test/unit_test.hpp> + +#include <string> + using namespace std; namespace dev @@ -202,6 +206,68 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent) BOOST_CHECK(d.compile()); } +BOOST_AUTO_TEST_CASE(shadowing_via_import) +{ + CompilerStack c; + c.addSource("a", "library A {} pragma solidity >=0.0;"); + c.addSource("b", "library A {} pragma solidity >=0.0;"); + c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";"); + BOOST_CHECK(!c.compile()); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_imports) +{ + CompilerStack c; + c.addSource("B.sol", "contract X {} pragma solidity >=0.0;"); + c.addSource("b", R"( + pragma solidity >=0.0; + import * as msg from "B.sol"; + contract C { + } + )"); + BOOST_CHECK(c.compile()); + size_t errorCount = 0; + for (auto const& e: c.errors()) + { + string const* msg = e->comment(); + BOOST_REQUIRE(msg); + if (msg->find("pre-release") != string::npos) + continue; + BOOST_CHECK( + msg->find("shadows a builtin symbol") != string::npos + ); + errorCount++; + } + BOOST_CHECK_EQUAL(errorCount, 1); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_multiple_imports) +{ + CompilerStack c; + c.addSource("B.sol", "contract msg {} contract block{} pragma solidity >=0.0;"); + c.addSource("b", R"( + pragma solidity >=0.0; + import {msg, block} from "B.sol"; + contract C { + } + )"); + BOOST_CHECK(c.compile()); + auto numErrors = c.errors().size(); + // Sometimes we get the prerelease warning, sometimes not. + BOOST_CHECK(4 <= numErrors && numErrors <= 5); + for (auto const& e: c.errors()) + { + string const* msg = e->comment(); + BOOST_REQUIRE(msg); + BOOST_CHECK( + msg->find("pre-release") != string::npos || + msg->find("shadows a builtin symbol") != string::npos + ); + } +} + + + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp index a6a7bc5b..536ba730 100644 --- a/test/libsolidity/JSONCompiler.cpp +++ b/test/libsolidity/JSONCompiler.cpp @@ -35,7 +35,10 @@ extern "C" { extern char const* version(); extern char const* license(); +extern char const* compileJSON(char const* _input, bool _optimize); extern char const* compileJSONMulti(char const* _input, bool _optimize); +extern char const* compileJSONCallback(char const* _input, bool _optimize, void* _readCallback); +extern char const* compileStandard(char const* _input, void* _readCallback); } namespace dev @@ -48,9 +51,29 @@ namespace test namespace { +Json::Value compileSingle(string const& _input) +{ + string output(compileJSON(_input.c_str(), dev::test::Options::get().optimize)); + Json::Value ret; + BOOST_REQUIRE(Json::Reader().parse(output, ret, false)); + return ret; +} + +Json::Value compileMulti(string const& _input, bool _callback) +{ + string output( + _callback ? + compileJSONCallback(_input.c_str(), dev::test::Options::get().optimize, NULL) : + compileJSONMulti(_input.c_str(), dev::test::Options::get().optimize) + ); + Json::Value ret; + BOOST_REQUIRE(Json::Reader().parse(output, ret, false)); + return ret; +} + Json::Value compile(string const& _input) { - string output(compileJSONMulti(_input.c_str(), dev::test::Options::get().optimize)); + string output(compileStandard(_input.c_str(), NULL)); Json::Value ret; BOOST_REQUIRE(Json::Reader().parse(output, ret, false)); return ret; @@ -81,8 +104,15 @@ BOOST_AUTO_TEST_CASE(basic_compilation) } } )"; - Json::Value result = compile(input); + Json::Value result = compileMulti(input, false); BOOST_CHECK(result.isObject()); + + // Compare with compileJSONCallback + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(result), + dev::jsonCompactPrint(compileMulti(input, true)) + ); + BOOST_CHECK(result["contracts"].isObject()); BOOST_CHECK(result["contracts"]["fileA:A"].isObject()); Json::Value contract = result["contracts"]["fileA:A"]; @@ -119,6 +149,69 @@ BOOST_AUTO_TEST_CASE(basic_compilation) "\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}" ); } + +BOOST_AUTO_TEST_CASE(single_compilation) +{ + Json::Value result = compileSingle("contract A { }"); + BOOST_CHECK(result.isObject()); + + BOOST_CHECK(result["contracts"].isObject()); + BOOST_CHECK(result["contracts"][":A"].isObject()); + Json::Value contract = result["contracts"][":A"]; + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["interface"].isString()); + BOOST_CHECK_EQUAL(contract["interface"].asString(), "[]"); + BOOST_CHECK(contract["bytecode"].isString()); + BOOST_CHECK_EQUAL( + dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), + "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00" + ); + BOOST_CHECK(contract["runtimeBytecode"].isString()); + BOOST_CHECK_EQUAL( + dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()), + "60606040525b600080fd00" + ); + BOOST_CHECK(contract["functionHashes"].isObject()); + BOOST_CHECK(contract["gasEstimates"].isObject()); + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(contract["gasEstimates"]), + "{\"creation\":[62,10800],\"external\":{},\"internal\":{}}" + ); + BOOST_CHECK(contract["metadata"].isString()); + BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); + BOOST_CHECK(result["sources"].isObject()); + BOOST_CHECK(result["sources"][""].isObject()); + BOOST_CHECK(result["sources"][""]["AST"].isObject()); + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(result["sources"][""]["AST"]), + "{\"attributes\":{\"absolutePath\":\"\",\"exportedSymbols\":{\"A\":[1]}}," + "\"children\":[{\"attributes\":{\"baseContracts\":[null],\"contractDependencies\":[null]," + "\"contractKind\":\"contract\",\"documentation\":null,\"fullyImplemented\":true,\"linearizedBaseContracts\":[1]," + "\"name\":\"A\",\"nodes\":[null],\"scope\":2},\"id\":1,\"name\":\"ContractDefinition\"," + "\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}" + ); +} + +BOOST_AUTO_TEST_CASE(standard_compilation) +{ + char const* input = R"( + { + "language": "Solidity", + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(result.isObject()); + + // Only tests some assumptions. The StandardCompiler is tested properly in another suite. + BOOST_CHECK(result.isMember("sources")); + BOOST_CHECK(result.isMember("contracts")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index ce1a7b18..0d3caddd 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -44,7 +44,8 @@ BOOST_AUTO_TEST_CASE(metadata_stamp) )"; CompilerStack compilerStack; compilerStack.addSource("", std::string(sourceCode)); - ETH_TEST_REQUIRE_NO_THROW(compilerStack.compile(dev::test::Options::get().optimize), "Compiling contract failed"); + compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); + ETH_TEST_REQUIRE_NO_THROW(compilerStack.compile(), "Compiling contract failed"); bytes const& bytecode = compilerStack.runtimeObject("test").bytecode; std::string const& metadata = compilerStack.metadata("test"); BOOST_CHECK(dev::test::isValidMetadata(metadata)); @@ -57,6 +58,75 @@ BOOST_AUTO_TEST_CASE(metadata_stamp) BOOST_CHECK(std::equal(expectation.begin(), expectation.end(), bytecode.end() - metadataCBORSize - 2)); } +BOOST_AUTO_TEST_CASE(metadata_relevant_sources) +{ + CompilerStack compilerStack; + char const* sourceCode = R"( + pragma solidity >=0.0; + contract A { + function g(function(uint) external returns (uint) x) {} + } + )"; + compilerStack.addSource("A", std::string(sourceCode)); + sourceCode = R"( + pragma solidity >=0.0; + contract B { + function g(function(uint) external returns (uint) x) {} + } + )"; + compilerStack.addSource("B", std::string(sourceCode)); + compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); + ETH_TEST_REQUIRE_NO_THROW(compilerStack.compile(), "Compiling contract failed"); + + std::string const& serialisedMetadata = compilerStack.metadata("A"); + BOOST_CHECK(dev::test::isValidMetadata(serialisedMetadata)); + Json::Value metadata; + BOOST_REQUIRE(Json::Reader().parse(serialisedMetadata, metadata, false)); + + BOOST_CHECK_EQUAL(metadata["sources"].size(), 1); + BOOST_CHECK(metadata["sources"].isMember("A")); +} + +BOOST_AUTO_TEST_CASE(metadata_relevant_sources_imports) +{ + CompilerStack compilerStack; + char const* sourceCode = R"( + pragma solidity >=0.0; + contract A { + function g(function(uint) external returns (uint) x) {} + } + )"; + compilerStack.addSource("A", std::string(sourceCode)); + sourceCode = R"( + pragma solidity >=0.0; + import "./A"; + contract B is A { + function g(function(uint) external returns (uint) x) {} + } + )"; + compilerStack.addSource("B", std::string(sourceCode)); + sourceCode = R"( + pragma solidity >=0.0; + import "./B"; + contract C is B { + function g(function(uint) external returns (uint) x) {} + } + )"; + compilerStack.addSource("C", std::string(sourceCode)); + compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); + ETH_TEST_REQUIRE_NO_THROW(compilerStack.compile(), "Compiling contract failed"); + + std::string const& serialisedMetadata = compilerStack.metadata("C"); + BOOST_CHECK(dev::test::isValidMetadata(serialisedMetadata)); + Json::Value metadata; + BOOST_REQUIRE(Json::Reader().parse(serialisedMetadata, metadata, false)); + + BOOST_CHECK_EQUAL(metadata["sources"].size(), 3); + BOOST_CHECK(metadata["sources"].isMember("A")); + BOOST_CHECK(metadata["sources"].isMember("B")); + BOOST_CHECK(metadata["sources"].isMember("C")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 94d3e168..166c4660 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1950,6 +1950,87 @@ BOOST_AUTO_TEST_CASE(ripemd) testContractAgainstCpp("a(bytes32)", f, u256(-1)); } +BOOST_AUTO_TEST_CASE(packed_keccak256) +{ + char const* sourceCode = R"( + contract test { + function a(bytes32 input) returns (bytes32 hash) { + var b = 65536; + uint c = 256; + return keccak256(8, input, b, input, c); + } + } + )"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _x) -> u256 + { + return dev::keccak256( + toCompactBigEndian(unsigned(8)) + + toBigEndian(_x) + + toCompactBigEndian(unsigned(65536)) + + toBigEndian(_x) + + toBigEndian(u256(256)) + ); + }; + testContractAgainstCpp("a(bytes32)", f, u256(4)); + testContractAgainstCpp("a(bytes32)", f, u256(5)); + testContractAgainstCpp("a(bytes32)", f, u256(-1)); +} + +BOOST_AUTO_TEST_CASE(packed_sha256) +{ + char const* sourceCode = R"( + contract test { + function a(bytes32 input) returns (bytes32 hash) { + var b = 65536; + uint c = 256; + return sha256(8, input, b, input, c); + } + } + )"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _x) -> bytes + { + if (_x == u256(4)) + return fromHex("804e0d7003cfd70fc925dc103174d9f898ebb142ecc2a286da1abd22ac2ce3ac"); + if (_x == u256(5)) + return fromHex("e94921945f9068726c529a290a954f412bcac53184bb41224208a31edbf63cf0"); + if (_x == u256(-1)) + return fromHex("f14def4d07cd185ddd8b10a81b2238326196a38867e6e6adbcc956dc913488c7"); + return fromHex(""); + }; + testContractAgainstCpp("a(bytes32)", f, u256(4)); + testContractAgainstCpp("a(bytes32)", f, u256(5)); + testContractAgainstCpp("a(bytes32)", f, u256(-1)); +} + +BOOST_AUTO_TEST_CASE(packed_ripemd160) +{ + char const* sourceCode = R"( + contract test { + function a(bytes32 input) returns (bytes32 hash) { + var b = 65536; + uint c = 256; + return ripemd160(8, input, b, input, c); + } + } + )"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _x) -> bytes + { + if (_x == u256(4)) + return fromHex("f93175303eba2a7b372174fc9330237f5ad202fc000000000000000000000000"); + if (_x == u256(5)) + return fromHex("04f4fc112e2bfbe0d38f896a46629e08e2fcfad5000000000000000000000000"); + if (_x == u256(-1)) + return fromHex("c0a2e4b1f3ff766a9a0089e7a410391730872495000000000000000000000000"); + return fromHex(""); + }; + testContractAgainstCpp("a(bytes32)", f, u256(4)); + testContractAgainstCpp("a(bytes32)", f, u256(5)); + testContractAgainstCpp("a(bytes32)", f, u256(-1)); +} + BOOST_AUTO_TEST_CASE(ecrecover) { char const* sourceCode = R"( @@ -2318,21 +2399,6 @@ BOOST_AUTO_TEST_CASE(gas_and_value_basic) BOOST_REQUIRE(callContractFunction("checkState()") == encodeArgs(false, 20 - 5)); } -BOOST_AUTO_TEST_CASE(gas_for_builtin) -{ - char const* sourceCode = R"( - contract Contract { - function test(uint g) returns (bytes32 data, bool flag) { - data = ripemd160.gas(g)("abc"); - flag = true; - } - } - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test(uint256)", 500) == bytes()); - BOOST_CHECK(callContractFunction("test(uint256)", 800) == encodeArgs(u256("0x8eb208f7e05d987a9b044a8e98c6b087f15a0bfc000000000000000000000000"), true)); -} - BOOST_AUTO_TEST_CASE(value_complex) { char const* sourceCode = R"( @@ -2696,6 +2762,34 @@ BOOST_AUTO_TEST_CASE(function_modifier_for_constructor) BOOST_CHECK(callContractFunction("getData()") == encodeArgs(4 | 2)); } +BOOST_AUTO_TEST_CASE(function_modifier_multiple_times) +{ + char const* sourceCode = R"( + contract C { + uint public a; + modifier mod(uint x) { a += x; _; } + function f(uint x) mod(2) mod(5) mod(x) returns(uint) { return a; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(2 + 5 + 3)); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(2 + 5 + 3)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_multiple_times_local_vars) +{ + char const* sourceCode = R"( + contract C { + uint public a; + modifier mod(uint x) { uint b = x; a += b; _; a -= b; assert(b == x); } + function f(uint x) mod(2) mod(5) mod(x) returns(uint) { return a; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(2 + 5 + 3)); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(0)); +} + BOOST_AUTO_TEST_CASE(crazy_elementary_typenames_on_stack) { char const* sourceCode = R"( @@ -3063,6 +3157,31 @@ BOOST_AUTO_TEST_CASE(event_really_lots_of_data_from_storage) BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(uint256,bytes,uint256)"))); } +BOOST_AUTO_TEST_CASE(event_really_really_lots_of_data_from_storage) +{ + char const* sourceCode = R"( + contract ClientReceipt { + bytes x; + event Deposit(uint fixeda, bytes dynx, uint fixedb); + function deposit() { + x.length = 31; + x[0] = "A"; + x[1] = "B"; + x[2] = "C"; + x[30] = "Z"; + Deposit(10, x, 15); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("deposit()"); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 31, string("ABC") + string(27, 0) + "Z")); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(uint256,bytes,uint256)"))); +} + BOOST_AUTO_TEST_CASE(event_indexed_string) { char const* sourceCode = R"( @@ -4308,6 +4427,75 @@ BOOST_AUTO_TEST_CASE(array_copy_storage_storage_struct) BOOST_CHECK(storageEmpty(m_contractAddress)); } +BOOST_AUTO_TEST_CASE(array_copy_storage_abi) +{ + // NOTE: This does not really test copying from storage to ABI directly, + // because it will always copy to memory first. + char const* sourceCode = R"( + contract c { + uint8[] x; + uint16[] y; + uint24[] z; + function test1() returns (uint8[]) { + for (uint i = 0; i < 101; ++i) + x.push(uint8(i)); + return x; + } + function test2() returns (uint16[]) { + for (uint i = 0; i < 101; ++i) + y.push(uint16(i)); + return y; + } + function test3() returns (uint24[]) { + for (uint i = 0; i < 101; ++i) + z.push(uint24(i)); + return z; + } + } + )"; + compileAndRun(sourceCode); + bytes valueSequence; + for (size_t i = 0; i < 101; ++i) + valueSequence += toBigEndian(u256(i)); + BOOST_CHECK(callContractFunction("test1()") == encodeArgs(0x20, 101) + valueSequence); + BOOST_CHECK(callContractFunction("test2()") == encodeArgs(0x20, 101) + valueSequence); + BOOST_CHECK(callContractFunction("test3()") == encodeArgs(0x20, 101) + valueSequence); +} + +BOOST_AUTO_TEST_CASE(array_copy_storage_abi_signed) +{ + // NOTE: This does not really test copying from storage to ABI directly, + // because it will always copy to memory first. + char const* sourceCode = R"( + contract c { + int16[] x; + function test() returns (int16[]) { + x.push(int16(-1)); + x.push(int16(-1)); + x.push(int16(8)); + x.push(int16(-16)); + x.push(int16(-2)); + x.push(int16(6)); + x.push(int16(8)); + x.push(int16(-1)); + return x; + } + } + )"; + compileAndRun(sourceCode); + bytes valueSequence; + BOOST_CHECK(callContractFunction("test()") == encodeArgs(0x20, 8, + u256(-1), + u256(-1), + u256(8), + u256(-16), + u256(-2), + u256(6), + u256(8), + u256(-1) + )); +} + BOOST_AUTO_TEST_CASE(array_push) { char const* sourceCode = R"( @@ -8223,6 +8411,53 @@ BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0))); } +BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input_proper) +{ + char const* sourceCode = R"( + contract C { + function f() returns (address) { + return recover( + 0x77e5189111eb6557e8a637b27ef8fbb15bc61d61c2f00cc48878f3a296e5e0ca, + 0, // invalid v value + 0x6944c77849b18048f6abe0db8084b0d0d0689cdddb53d2671c36967b58691ad4, + 0xef4f06ba4f78319baafd0424365777241af4dfd3da840471b4b4b087b7750d0d, + 0xca35b7d915458ef540ade6068dfe2f44e8fa733c, + 0xca35b7d915458ef540ade6068dfe2f44e8fa733c + ); + } + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s, uint blockExpired, bytes32 salt) + returns (address) + { + require(hash == keccak256(blockExpired, salt)); + return ecrecover(hash, v, r, s); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input_asm) +{ + char const* sourceCode = R"( + contract C { + function f() returns (address) { + assembly { + mstore(mload(0x40), 0xca35b7d915458ef540ade6068dfe2f44e8fa733c) + } + return ecrecover( + 0x77e5189111eb6557e8a637b27ef8fbb15bc61d61c2f00cc48878f3a296e5e0ca, + 0, // invalid v value + 0x6944c77849b18048f6abe0db8084b0d0d0689cdddb53d2671c36967b58691ad4, + 0xef4f06ba4f78319baafd0424365777241af4dfd3da840471b4b4b087b7750d0d + ); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0))); +} + BOOST_AUTO_TEST_CASE(calling_nonexisting_contract_throws) { char const* sourceCode = R"( @@ -9741,6 +9976,64 @@ BOOST_AUTO_TEST_CASE(inlineasm_empty_let) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0), u256(0))); } +BOOST_AUTO_TEST_CASE(bare_call_invalid_address) +{ + char const* sourceCode = R"( + contract C { + /// Calling into non-existant account is successful (creates the account) + function f() external constant returns (bool) { + return address(0x4242).call(); + } + function g() external constant returns (bool) { + return address(0x4242).callcode(); + } + function h() external constant returns (bool) { + return address(0x4242).delegatecall(); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("g()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("h()") == encodeArgs(u256(1))); +} + +BOOST_AUTO_TEST_CASE(delegatecall_return_value) +{ + char const* sourceCode = R"DELIMITER( + contract C { + uint value; + function set(uint _value) external { + value = _value; + } + function get() external constant returns (uint) { + return value; + } + function get_delegated() external constant returns (bool) { + return this.delegatecall(bytes4(sha3("get()"))); + } + function assert0() external constant { + assert(value == 0); + } + function assert0_delegated() external constant returns (bool) { + return this.delegatecall(bytes4(sha3("assert0()"))); + } + } + )DELIMITER"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("assert0_delegated()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("set(uint256)", u256(1)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("assert0_delegated()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("set(uint256)", u256(42)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(42))); + BOOST_CHECK(callContractFunction("assert0_delegated()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 03e3a881..a8e58c25 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -56,7 +56,9 @@ public: std::string sourceCode = "pragma solidity >=0.0;\n" + _sourceCode; m_compiler.reset(false); m_compiler.addSource("", sourceCode); - if (!m_compiler.compile(m_optimize, m_optimizeRuns, _libraryAddresses)) + m_compiler.setLibraries(_libraryAddresses); + m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); + if (!m_compiler.compile()) { for (auto const& error: m_compiler.errors()) SourceReferenceFormatter::printExceptionInformation( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 4b29243a..55ce5f7b 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -624,7 +624,7 @@ BOOST_AUTO_TEST_CASE(function_no_implementation) std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); ContractDefinition* contract = dynamic_cast<ContractDefinition*>(nodes[1].get()); BOOST_REQUIRE(contract); - BOOST_CHECK(!contract->annotation().isFullyImplemented); + BOOST_CHECK(!contract->annotation().unimplementedFunctions.empty()); BOOST_CHECK(!contract->definedFunctions()[0]->isImplemented()); } @@ -640,10 +640,10 @@ BOOST_AUTO_TEST_CASE(abstract_contract) ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get()); ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().isFullyImplemented); + BOOST_CHECK(!base->annotation().unimplementedFunctions.empty()); BOOST_CHECK(!base->definedFunctions()[0]->isImplemented()); BOOST_REQUIRE(derived); - BOOST_CHECK(derived->annotation().isFullyImplemented); + BOOST_CHECK(derived->annotation().unimplementedFunctions.empty()); BOOST_CHECK(derived->definedFunctions()[0]->isImplemented()); } @@ -659,9 +659,9 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get()); ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().isFullyImplemented); + BOOST_CHECK(!base->annotation().unimplementedFunctions.empty()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); + BOOST_CHECK(!derived->annotation().unimplementedFunctions.empty()); } BOOST_AUTO_TEST_CASE(create_abstract_contract) @@ -677,44 +677,6 @@ BOOST_AUTO_TEST_CASE(create_abstract_contract) CHECK_ERROR(text, TypeError, ""); } -BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_optional) -{ - ASTPointer<SourceUnit> sourceUnit; - char const* text = R"( - contract BaseBase { function BaseBase(uint j); } - contract base is BaseBase { function foo(); } - contract derived is base { - function derived(uint i) BaseBase(i){} - function foo() {} - } - )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); - std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); - BOOST_CHECK_EQUAL(nodes.size(), 4); - ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[3].get()); - BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); -} - -BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_not_provided) -{ - ASTPointer<SourceUnit> sourceUnit; - char const* text = R"( - contract BaseBase { function BaseBase(uint); } - contract base is BaseBase { function foo(); } - contract derived is base { - function derived(uint) {} - function foo() {} - } - )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); - std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); - BOOST_CHECK_EQUAL(nodes.size(), 4); - ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[3].get()); - BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); -} - BOOST_AUTO_TEST_CASE(redeclare_implemented_abstract_function_as_abstract) { ASTPointer<SourceUnit> sourceUnit; @@ -738,7 +700,7 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) BOOST_CHECK_EQUAL(nodes.size(), 3); ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().isFullyImplemented); + BOOST_CHECK(!derived->annotation().unimplementedFunctions.empty()); } BOOST_AUTO_TEST_CASE(function_canonical_signature) @@ -1115,7 +1077,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_double_invocation) modifier mod(uint a) { if (a > 0) _; } } )"; - CHECK_ERROR(text, DeclarationError, "Modifier already used for this function"); + success(text); } BOOST_AUTO_TEST_CASE(base_constructor_double_invocation) @@ -5498,22 +5460,6 @@ BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_library) CHECK_SUCCESS_NO_WARNINGS(text); } -BOOST_AUTO_TEST_CASE(does_not_warn_non_magic_msg_value) -{ - char const* text = R"( - contract C { - struct msg { - uint256 value; - } - - function f() { - msg.value; - } - } - )"; - CHECK_SUCCESS_NO_WARNINGS(text); -} - BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_modifier_following_non_payable_public_function) { char const* text = R"( @@ -5730,7 +5676,7 @@ BOOST_AUTO_TEST_CASE(interface_constructor) function I(); } )"; - CHECK_ERROR(text, TypeError, "Constructor cannot be defined in interfaces"); + CHECK_ERROR_ALLOW_MULTI(text, TypeError, "Constructor cannot be defined in interfaces"); } BOOST_AUTO_TEST_CASE(interface_functions) @@ -6127,6 +6073,126 @@ BOOST_AUTO_TEST_CASE(no_unused_inline_asm) CHECK_SUCCESS_NO_WARNINGS(text); } +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_functions) +{ + char const* text = R"( + contract C { + function keccak256() {} + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_variables) +{ + char const* text = R"( + contract C { + function f() { + uint msg; + msg; + } + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_storage_variables) +{ + char const* text = R"( + contract C { + uint msg; + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtin_at_global_scope) +{ + char const* text = R"( + contract msg { + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_parameters) +{ + char const* text = R"( + contract C { + function f(uint require) { + require = 2; + } + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_return_parameters) +{ + char const* text = R"( + contract C { + function f() returns (uint require) { + require = 2; + } + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_with_events) +{ + char const* text = R"( + contract C { + event keccak256(); + } + )"; + CHECK_WARNING(text, "shadows a builtin symbol"); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_ignores_struct) +{ + char const* text = R"( + contract C { + struct a { + uint msg; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(shadowing_builtins_ignores_constructor) +{ + char const* text = R"( + contract C { + function C() {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(function_overload_is_not_shadowing) +{ + char const* text = R"( + contract C { + function f() {} + function f(uint) {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(function_override_is_not_shadowing) +{ + char const* text = R"( + contract D { function f() {} } + contract C is D { + function f(uint) {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(callable_crash) { char const* text = R"( @@ -6245,14 +6311,6 @@ BOOST_AUTO_TEST_CASE(create2_as_variable) CHECK_WARNING_ALLOW_MULTI(text, "Variable is shadowed in inline assembly by an instruction of the same name"); } -BOOST_AUTO_TEST_CASE(shadowing_warning_can_be_removed) -{ - char const* text = R"( - contract C {function f() {assembly {}}} - )"; - CHECK_SUCCESS_NO_WARNINGS(text); -} - BOOST_AUTO_TEST_CASE(warn_unspecified_storage) { char const* text = R"( @@ -6382,6 +6440,161 @@ BOOST_AUTO_TEST_CASE(using_this_in_constructor) CHECK_WARNING(text, "\"this\" used in constructor"); } +BOOST_AUTO_TEST_CASE(do_not_crash_on_not_lvalue) +{ + // This checks for a bug that caused a crash because of continued analysis. + char const* text = R"( + contract C { + mapping (uint => uint) m; + function f() { + m(1) = 2; + } + } + )"; + CHECK_ERROR_ALLOW_MULTI(text, TypeError, "is not callable"); +} + +BOOST_AUTO_TEST_CASE(builtin_reject_gas) +{ + char const* text = R"( + contract C { + function f() { + keccak256.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + sha256.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ripemd160.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ecrecover.gas(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); +} + +BOOST_AUTO_TEST_CASE(builtin_reject_value) +{ + char const* text = R"( + contract C { + function f() { + keccak256.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + sha256.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ripemd160.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); + text = R"( + contract C { + function f() { + ecrecover.value(); + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); +} + +BOOST_AUTO_TEST_CASE(constructor_without_implementation) +{ + char const* text = R"( + contract C { + function C(); + } + )"; + CHECK_ERROR(text, TypeError, "Constructor must be implemented if declared."); +} + +BOOST_AUTO_TEST_CASE(library_function_without_implementation) +{ + char const* text = R"( + library L { + function f(); + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + library L { + function f() internal; + } + )"; + CHECK_ERROR(text, TypeError, "Internal library function must be implemented if declared."); + text = R"( + library L { + function f() private; + } + )"; + CHECK_ERROR(text, TypeError, "Internal library function must be implemented if declared."); +} + +BOOST_AUTO_TEST_CASE(experimental_pragma) +{ + char const* text = R"( + pragma experimental; + )"; + CHECK_ERROR(text, SyntaxError, "Experimental feature name is missing."); + text = R"( + pragma experimental 123; + )"; + CHECK_ERROR(text, SyntaxError, "Unsupported experimental feature name."); + text = R"( + pragma experimental unsupportedName; + )"; + CHECK_ERROR(text, SyntaxError, "Unsupported experimental feature name."); + text = R"( + pragma experimental "unsupportedName"; + )"; + CHECK_ERROR(text, SyntaxError, "Unsupported experimental feature name."); + text = R"( + pragma experimental ""; + )"; + CHECK_ERROR(text, SyntaxError, "Empty experimental feature name is invalid."); + text = R"( + pragma experimental unsupportedName unsupportedName; + )"; + CHECK_ERROR(text, SyntaxError, "Stray arguments."); +// text = R"( +// pragma experimental supportedName; +// )"; +// CHECK_WARNING(text, "Experimental features are turned on. Do not use experimental features on live deployments."); +// text = R"( +// pragma experimental supportedName; +// pragma experimental supportedName; +// )"; +// CHECK_ERROR(text, SyntaxError, "Duplicate experimental feature name."); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index aa343561..be20a9f2 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -65,7 +65,9 @@ public: void expectNatspecError(std::string const& _code) { - BOOST_CHECK(!m_compilerStack.parseAndAnalyze(_code)); + m_compilerStack.reset(false); + m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + BOOST_CHECK(!m_compilerStack.parseAndAnalyze()); BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError)); } diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index a4d80c99..bd635c33 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -22,11 +22,7 @@ #include <test/libsolidity/SolidityExecutionFramework.h> -#include <libevmasm/CommonSubexpressionEliminator.h> -#include <libevmasm/PeepholeOptimiser.h> -#include <libevmasm/ControlFlowGraph.h> -#include <libevmasm/Assembly.h> -#include <libevmasm/BlockDeduplicator.h> +#include <libevmasm/Instruction.h> #include <boost/test/unit_test.hpp> #include <boost/lexical_cast.hpp> @@ -106,71 +102,6 @@ public: "\nOptimized: " + toHex(optimizedOutput)); } - AssemblyItems addDummyLocations(AssemblyItems const& _input) - { - // add dummy locations to each item so that we can check that they are not deleted - AssemblyItems input = _input; - for (AssemblyItem& item: input) - item.setLocation(SourceLocation(1, 3, make_shared<string>(""))); - return input; - } - - eth::KnownState createInitialState(AssemblyItems const& _input) - { - eth::KnownState state; - for (auto const& item: addDummyLocations(_input)) - state.feedItem(item, true); - return state; - } - - AssemblyItems CSE(AssemblyItems const& _input, eth::KnownState const& _state = eth::KnownState()) - { - AssemblyItems input = addDummyLocations(_input); - - eth::CommonSubexpressionEliminator cse(_state); - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); - AssemblyItems output = cse.getOptimizedItems(); - - for (AssemblyItem const& item: output) - { - BOOST_CHECK(item == Instruction::POP || !item.location().isEmpty()); - } - return output; - } - - void checkCSE( - AssemblyItems const& _input, - AssemblyItems const& _expectation, - KnownState const& _state = eth::KnownState() - ) - { - AssemblyItems output = CSE(_input, _state); - BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); - } - - AssemblyItems CFG(AssemblyItems const& _input) - { - AssemblyItems output = _input; - // Running it four times should be enough for these tests. - for (unsigned i = 0; i < 4; ++i) - { - ControlFlowGraph cfg(output); - AssemblyItems optItems; - for (BasicBlock const& block: cfg.optimisedBlocks()) - copy(output.begin() + block.begin, output.begin() + block.end, - back_inserter(optItems)); - output = move(optItems); - } - return output; - } - - void checkCFG(AssemblyItems const& _input, AssemblyItems const& _expectation) - { - AssemblyItems output = CFG(_input); - BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); - } - -protected: /// @returns the number of intructions in the given bytecode, not taking the metadata hash /// into account. size_t numInstructions(bytes const& _bytecode) @@ -187,6 +118,7 @@ protected: return instructions; } +protected: Address m_optimizedContract; Address m_nonOptimizedContract; }; @@ -434,734 +366,6 @@ BOOST_AUTO_TEST_CASE(sequence_number_for_calls) compareVersions("f(string,string)", 0x40, 0x80, 3, "abc", 3, "def"); } -BOOST_AUTO_TEST_CASE(cse_intermediate_swap) -{ - eth::KnownState state; - eth::CommonSubexpressionEliminator cse(state); - AssemblyItems input{ - Instruction::SWAP1, Instruction::POP, Instruction::ADD, u256(0), Instruction::SWAP1, - Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1, - Instruction::DIV, u256(0xff), Instruction::AND - }; - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); - AssemblyItems output = cse.getOptimizedItems(); - BOOST_CHECK(!output.empty()); -} - -BOOST_AUTO_TEST_CASE(cse_negative_stack_access) -{ - AssemblyItems input{Instruction::DUP2, u256(0)}; - checkCSE(input, input); -} - -BOOST_AUTO_TEST_CASE(cse_negative_stack_end) -{ - AssemblyItems input{Instruction::ADD}; - checkCSE(input, input); -} - -BOOST_AUTO_TEST_CASE(cse_intermediate_negative_stack) -{ - AssemblyItems input{Instruction::ADD, u256(1), Instruction::DUP1}; - checkCSE(input, input); -} - -BOOST_AUTO_TEST_CASE(cse_pop) -{ - checkCSE({Instruction::POP}, {Instruction::POP}); -} - -BOOST_AUTO_TEST_CASE(cse_unneeded_items) -{ - AssemblyItems input{ - Instruction::ADD, - Instruction::SWAP1, - Instruction::POP, - u256(7), - u256(8), - }; - checkCSE(input, input); -} - -BOOST_AUTO_TEST_CASE(cse_constant_addition) -{ - AssemblyItems input{u256(7), u256(8), Instruction::ADD}; - checkCSE(input, {u256(7 + 8)}); -} - -BOOST_AUTO_TEST_CASE(cse_invariants) -{ - AssemblyItems input{ - Instruction::DUP1, - Instruction::DUP1, - u256(0), - Instruction::OR, - Instruction::OR - }; - checkCSE(input, {Instruction::DUP1}); -} - -BOOST_AUTO_TEST_CASE(cse_subself) -{ - checkCSE({Instruction::DUP1, Instruction::SUB}, {Instruction::POP, u256(0)}); -} - -BOOST_AUTO_TEST_CASE(cse_subother) -{ - checkCSE({Instruction::SUB}, {Instruction::SUB}); -} - -BOOST_AUTO_TEST_CASE(cse_double_negation) -{ - checkCSE({Instruction::DUP5, Instruction::NOT, Instruction::NOT}, {Instruction::DUP5}); -} - -BOOST_AUTO_TEST_CASE(cse_double_iszero) -{ - checkCSE({Instruction::GT, Instruction::ISZERO, Instruction::ISZERO}, {Instruction::GT}); - checkCSE({Instruction::GT, Instruction::ISZERO}, {Instruction::GT, Instruction::ISZERO}); - checkCSE( - {Instruction::ISZERO, Instruction::ISZERO, Instruction::ISZERO}, - {Instruction::ISZERO} - ); -} - -BOOST_AUTO_TEST_CASE(cse_associativity) -{ - AssemblyItems input{ - Instruction::DUP1, - Instruction::DUP1, - u256(0), - Instruction::OR, - Instruction::OR - }; - checkCSE(input, {Instruction::DUP1}); -} - -BOOST_AUTO_TEST_CASE(cse_associativity2) -{ - AssemblyItems input{ - u256(0), - Instruction::DUP2, - u256(2), - u256(1), - Instruction::DUP6, - Instruction::ADD, - u256(2), - Instruction::ADD, - Instruction::ADD, - Instruction::ADD, - Instruction::ADD - }; - checkCSE(input, {Instruction::DUP2, Instruction::DUP2, Instruction::ADD, u256(5), Instruction::ADD}); -} - -BOOST_AUTO_TEST_CASE(cse_storage) -{ - AssemblyItems input{ - u256(0), - Instruction::SLOAD, - u256(0), - Instruction::SLOAD, - Instruction::ADD, - u256(0), - Instruction::SSTORE - }; - checkCSE(input, { - u256(0), - Instruction::DUP1, - Instruction::SLOAD, - Instruction::DUP1, - Instruction::ADD, - Instruction::SWAP1, - Instruction::SSTORE - }); -} - -BOOST_AUTO_TEST_CASE(cse_noninterleaved_storage) -{ - // two stores to the same location should be replaced by only one store, even if we - // read in the meantime - AssemblyItems input{ - u256(7), - Instruction::DUP2, - Instruction::SSTORE, - Instruction::DUP1, - Instruction::SLOAD, - u256(8), - Instruction::DUP3, - Instruction::SSTORE - }; - checkCSE(input, { - u256(8), - Instruction::DUP2, - Instruction::SSTORE, - u256(7) - }); -} - -BOOST_AUTO_TEST_CASE(cse_interleaved_storage) -{ - // stores and reads to/from two unknown locations, should not optimize away the first store - AssemblyItems input{ - u256(7), - Instruction::DUP2, - Instruction::SSTORE, // store to "DUP1" - Instruction::DUP2, - Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1" - u256(0), - Instruction::DUP3, - Instruction::SSTORE // store different value to "DUP1" - }; - checkCSE(input, input); -} - -BOOST_AUTO_TEST_CASE(cse_interleaved_storage_same_value) -{ - // stores and reads to/from two unknown locations, should not optimize away the first store - // but it should optimize away the second, since we already know the value will be the same - AssemblyItems input{ - u256(7), - Instruction::DUP2, - Instruction::SSTORE, // store to "DUP1" - Instruction::DUP2, - Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1" - u256(6), - u256(1), - Instruction::ADD, - Instruction::DUP3, - Instruction::SSTORE // store same value to "DUP1" - }; - checkCSE(input, { - u256(7), - Instruction::DUP2, - Instruction::SSTORE, - Instruction::DUP2, - Instruction::SLOAD - }); -} - -BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location) -{ - // stores and reads to/from two known locations, should optimize away the first store, - // because we know that the location is different - AssemblyItems input{ - u256(0x70), - u256(1), - Instruction::SSTORE, // store to 1 - u256(2), - Instruction::SLOAD, // read from 2, is different from 1 - u256(0x90), - u256(1), - Instruction::SSTORE // store different value at 1 - }; - checkCSE(input, { - u256(2), - Instruction::SLOAD, - u256(0x90), - u256(1), - Instruction::SSTORE - }); -} - -BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location_offset) -{ - // stores and reads to/from two locations which are known to be different, - // should optimize away the first store, because we know that the location is different - AssemblyItems input{ - u256(0x70), - Instruction::DUP2, - u256(1), - Instruction::ADD, - Instruction::SSTORE, // store to "DUP1"+1 - Instruction::DUP1, - u256(2), - Instruction::ADD, - Instruction::SLOAD, // read from "DUP1"+2, is different from "DUP1"+1 - u256(0x90), - Instruction::DUP3, - u256(1), - Instruction::ADD, - Instruction::SSTORE // store different value at "DUP1"+1 - }; - checkCSE(input, { - u256(2), - Instruction::DUP2, - Instruction::ADD, - Instruction::SLOAD, - u256(0x90), - u256(1), - Instruction::DUP4, - Instruction::ADD, - Instruction::SSTORE - }); -} - -BOOST_AUTO_TEST_CASE(cse_deep_stack) -{ - AssemblyItems input{ - Instruction::ADD, - Instruction::SWAP1, - Instruction::POP, - Instruction::SWAP8, - Instruction::POP, - Instruction::SWAP8, - Instruction::POP, - Instruction::SWAP8, - Instruction::SWAP5, - Instruction::POP, - Instruction::POP, - Instruction::POP, - Instruction::POP, - Instruction::POP, - }; - checkCSE(input, { - Instruction::SWAP4, - Instruction::SWAP12, - Instruction::SWAP3, - Instruction::SWAP11, - Instruction::POP, - Instruction::SWAP1, - Instruction::SWAP3, - Instruction::ADD, - Instruction::SWAP8, - Instruction::POP, - Instruction::SWAP6, - Instruction::POP, - Instruction::POP, - Instruction::POP, - Instruction::POP, - Instruction::POP, - Instruction::POP, - }); -} - -BOOST_AUTO_TEST_CASE(cse_jumpi_no_jump) -{ - AssemblyItems input{ - u256(0), - u256(1), - Instruction::DUP2, - AssemblyItem(PushTag, 1), - Instruction::JUMPI - }; - checkCSE(input, { - u256(0), - u256(1) - }); -} - -BOOST_AUTO_TEST_CASE(cse_jumpi_jump) -{ - AssemblyItems input{ - u256(1), - u256(1), - Instruction::DUP2, - AssemblyItem(PushTag, 1), - Instruction::JUMPI - }; - checkCSE(input, { - u256(1), - Instruction::DUP1, - AssemblyItem(PushTag, 1), - Instruction::JUMP - }); -} - -BOOST_AUTO_TEST_CASE(cse_empty_keccak256) -{ - AssemblyItems input{ - u256(0), - Instruction::DUP2, - Instruction::KECCAK256 - }; - checkCSE(input, { - u256(dev::keccak256(bytesConstRef())) - }); -} - -BOOST_AUTO_TEST_CASE(cse_partial_keccak256) -{ - AssemblyItems input{ - u256(0xabcd) << (256 - 16), - u256(0), - Instruction::MSTORE, - u256(2), - u256(0), - Instruction::KECCAK256 - }; - checkCSE(input, { - u256(0xabcd) << (256 - 16), - u256(0), - Instruction::MSTORE, - u256(dev::keccak256(bytes{0xab, 0xcd})) - }); -} - -BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_location) -{ - // Keccak-256 twice from same dynamic location - AssemblyItems input{ - Instruction::DUP2, - Instruction::DUP1, - Instruction::MSTORE, - u256(64), - Instruction::DUP2, - Instruction::KECCAK256, - u256(64), - Instruction::DUP3, - Instruction::KECCAK256 - }; - checkCSE(input, { - Instruction::DUP2, - Instruction::DUP1, - Instruction::MSTORE, - u256(64), - Instruction::DUP2, - Instruction::KECCAK256, - Instruction::DUP1 - }); -} - -BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content) -{ - // Keccak-256 twice from different dynamic location but with same content - AssemblyItems input{ - Instruction::DUP1, - u256(0x80), - Instruction::MSTORE, // m[128] = DUP1 - u256(0x20), - u256(0x80), - Instruction::KECCAK256, // keccak256(m[128..(128+32)]) - Instruction::DUP2, - u256(12), - Instruction::MSTORE, // m[12] = DUP1 - u256(0x20), - u256(12), - Instruction::KECCAK256 // keccak256(m[12..(12+32)]) - }; - checkCSE(input, { - u256(0x80), - Instruction::DUP2, - Instruction::DUP2, - Instruction::MSTORE, - u256(0x20), - Instruction::SWAP1, - Instruction::KECCAK256, - u256(12), - Instruction::DUP3, - Instruction::SWAP1, - Instruction::MSTORE, - Instruction::DUP1 - }); -} - -BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_dynamic_store_in_between) -{ - // Keccak-256 twice from different dynamic location but with same content, - // dynamic mstore in between, which forces us to re-calculate the hash - AssemblyItems input{ - u256(0x80), - Instruction::DUP2, - Instruction::DUP2, - Instruction::MSTORE, // m[128] = DUP1 - u256(0x20), - Instruction::DUP1, - Instruction::DUP3, - Instruction::KECCAK256, // keccak256(m[128..(128+32)]) - u256(12), - Instruction::DUP5, - Instruction::DUP2, - Instruction::MSTORE, // m[12] = DUP1 - Instruction::DUP12, - Instruction::DUP14, - Instruction::MSTORE, // destroys memory knowledge - Instruction::SWAP2, - Instruction::SWAP1, - Instruction::SWAP2, - Instruction::KECCAK256 // keccak256(m[12..(12+32)]) - }; - checkCSE(input, input); -} - -BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_noninterfering_store_in_between) -{ - // Keccak-256 twice from different dynamic location but with same content, - // dynamic mstore in between, but does not force us to re-calculate the hash - AssemblyItems input{ - u256(0x80), - Instruction::DUP2, - Instruction::DUP2, - Instruction::MSTORE, // m[128] = DUP1 - u256(0x20), - Instruction::DUP1, - Instruction::DUP3, - Instruction::KECCAK256, // keccak256(m[128..(128+32)]) - u256(12), - Instruction::DUP5, - Instruction::DUP2, - Instruction::MSTORE, // m[12] = DUP1 - Instruction::DUP12, - u256(12 + 32), - Instruction::MSTORE, // does not destoy memory knowledge - Instruction::DUP13, - u256(128 - 32), - Instruction::MSTORE, // does not destoy memory knowledge - u256(0x20), - u256(12), - Instruction::KECCAK256 // keccak256(m[12..(12+32)]) - }; - // if this changes too often, only count the number of SHA3 and MSTORE instructions - AssemblyItems output = CSE(input); - BOOST_CHECK_EQUAL(4, count(output.begin(), output.end(), AssemblyItem(Instruction::MSTORE))); - BOOST_CHECK_EQUAL(1, count(output.begin(), output.end(), AssemblyItem(Instruction::KECCAK256))); -} - -BOOST_AUTO_TEST_CASE(cse_with_initially_known_stack) -{ - eth::KnownState state = createInitialState(AssemblyItems{ - u256(0x12), - u256(0x20), - Instruction::ADD - }); - AssemblyItems input{ - u256(0x12 + 0x20) - }; - checkCSE(input, AssemblyItems{Instruction::DUP1}, state); -} - -BOOST_AUTO_TEST_CASE(cse_equality_on_initially_known_stack) -{ - eth::KnownState state = createInitialState(AssemblyItems{Instruction::DUP1}); - AssemblyItems input{ - Instruction::EQ - }; - AssemblyItems output = CSE(input, state); - // check that it directly pushes 1 (true) - BOOST_CHECK(find(output.begin(), output.end(), AssemblyItem(u256(1))) != output.end()); -} - -BOOST_AUTO_TEST_CASE(cse_access_previous_sequence) -{ - // Tests that the code generator detects whether it tries to access SLOAD instructions - // from a sequenced expression which is not in its scope. - eth::KnownState state = createInitialState(AssemblyItems{ - u256(0), - Instruction::SLOAD, - u256(1), - Instruction::ADD, - u256(0), - Instruction::SSTORE - }); - // now stored: val_1 + 1 (value at sequence 1) - // if in the following instructions, the SLOAD cresolves to "val_1 + 1", - // this cannot be generated because we cannot load from sequence 1 anymore. - AssemblyItems input{ - u256(0), - Instruction::SLOAD, - }; - BOOST_CHECK_THROW(CSE(input, state), StackTooDeepException); - // @todo for now, this throws an exception, but it should recover to the following - // (or an even better version) at some point: - // 0, SLOAD, 1, ADD, SSTORE, 0 SLOAD -} - -BOOST_AUTO_TEST_CASE(cse_optimise_return) -{ - checkCSE( - AssemblyItems{u256(0), u256(7), Instruction::RETURN}, - AssemblyItems{Instruction::STOP} - ); -} - -BOOST_AUTO_TEST_CASE(control_flow_graph_remove_unused) -{ - // remove parts of the code that are unused - AssemblyItems input{ - AssemblyItem(PushTag, 1), - Instruction::JUMP, - u256(7), - AssemblyItem(Tag, 1), - }; - checkCFG(input, {}); -} - -BOOST_AUTO_TEST_CASE(control_flow_graph_remove_unused_loop) -{ - AssemblyItems input{ - AssemblyItem(PushTag, 3), - Instruction::JUMP, - AssemblyItem(Tag, 1), - u256(7), - AssemblyItem(PushTag, 2), - Instruction::JUMP, - AssemblyItem(Tag, 2), - u256(8), - AssemblyItem(PushTag, 1), - Instruction::JUMP, - AssemblyItem(Tag, 3), - u256(11) - }; - checkCFG(input, {u256(11)}); -} - -BOOST_AUTO_TEST_CASE(control_flow_graph_reconnect_single_jump_source) -{ - // move code that has only one unconditional jump source - AssemblyItems input{ - u256(1), - AssemblyItem(PushTag, 1), - Instruction::JUMP, - AssemblyItem(Tag, 2), - u256(2), - AssemblyItem(PushTag, 3), - Instruction::JUMP, - AssemblyItem(Tag, 1), - u256(3), - AssemblyItem(PushTag, 2), - Instruction::JUMP, - AssemblyItem(Tag, 3), - u256(4), - }; - checkCFG(input, {u256(1), u256(3), u256(2), u256(4)}); -} - -BOOST_AUTO_TEST_CASE(control_flow_graph_do_not_remove_returned_to) -{ - // do not remove parts that are "returned to" - AssemblyItems input{ - AssemblyItem(PushTag, 1), - AssemblyItem(PushTag, 2), - Instruction::JUMP, - AssemblyItem(Tag, 2), - Instruction::JUMP, - AssemblyItem(Tag, 1), - u256(2) - }; - checkCFG(input, {u256(2)}); -} - -BOOST_AUTO_TEST_CASE(block_deduplicator) -{ - AssemblyItems input{ - AssemblyItem(PushTag, 2), - AssemblyItem(PushTag, 1), - AssemblyItem(PushTag, 3), - u256(6), - Instruction::SWAP3, - Instruction::JUMP, - AssemblyItem(Tag, 1), - u256(6), - Instruction::SWAP3, - Instruction::JUMP, - AssemblyItem(Tag, 2), - u256(6), - Instruction::SWAP3, - Instruction::JUMP, - AssemblyItem(Tag, 3) - }; - BlockDeduplicator dedup(input); - dedup.deduplicate(); - - set<u256> pushTags; - for (AssemblyItem const& item: input) - if (item.type() == PushTag) - pushTags.insert(item.data()); - BOOST_CHECK_EQUAL(pushTags.size(), 2); -} - -BOOST_AUTO_TEST_CASE(block_deduplicator_loops) -{ - AssemblyItems input{ - u256(0), - Instruction::SLOAD, - AssemblyItem(PushTag, 1), - AssemblyItem(PushTag, 2), - Instruction::JUMPI, - Instruction::JUMP, - AssemblyItem(Tag, 1), - u256(5), - u256(6), - Instruction::SSTORE, - AssemblyItem(PushTag, 1), - Instruction::JUMP, - AssemblyItem(Tag, 2), - u256(5), - u256(6), - Instruction::SSTORE, - AssemblyItem(PushTag, 2), - Instruction::JUMP, - }; - BlockDeduplicator dedup(input); - dedup.deduplicate(); - - set<u256> pushTags; - for (AssemblyItem const& item: input) - if (item.type() == PushTag) - pushTags.insert(item.data()); - BOOST_CHECK_EQUAL(pushTags.size(), 1); -} - -BOOST_AUTO_TEST_CASE(clear_unreachable_code) -{ - AssemblyItems items{ - AssemblyItem(PushTag, 1), - Instruction::JUMP, - u256(0), - Instruction::SLOAD, - AssemblyItem(Tag, 2), - u256(5), - u256(6), - Instruction::SSTORE, - AssemblyItem(PushTag, 1), - Instruction::JUMP, - u256(5), - u256(6) - }; - AssemblyItems expectation{ - AssemblyItem(PushTag, 1), - Instruction::JUMP, - AssemblyItem(Tag, 2), - u256(5), - u256(6), - Instruction::SSTORE, - AssemblyItem(PushTag, 1), - Instruction::JUMP - }; - PeepholeOptimiser peepOpt(items); - BOOST_REQUIRE(peepOpt.optimise()); - BOOST_CHECK_EQUAL_COLLECTIONS( - items.begin(), items.end(), - expectation.begin(), expectation.end() - ); -} - -BOOST_AUTO_TEST_CASE(peephole_double_push) -{ - AssemblyItems items{ - u256(0), - u256(0), - u256(5), - u256(5), - u256(4), - u256(5) - }; - AssemblyItems expectation{ - u256(0), - Instruction::DUP1, - u256(5), - Instruction::DUP1, - u256(4), - u256(5) - }; - PeepholeOptimiser peepOpt(items); - BOOST_REQUIRE(peepOpt.optimise()); - BOOST_CHECK_EQUAL_COLLECTIONS( - items.begin(), items.end(), - expectation.begin(), expectation.end() - ); -} - BOOST_AUTO_TEST_CASE(computing_constants) { char const* sourceCode = R"( @@ -1377,29 +581,6 @@ BOOST_AUTO_TEST_CASE(invalid_state_at_control_flow_join) compareVersions("test()"); } -BOOST_AUTO_TEST_CASE(cse_sub_zero) -{ - checkCSE({ - u256(0), - Instruction::DUP2, - Instruction::SUB - }, { - Instruction::DUP1 - }); - - checkCSE({ - Instruction::DUP1, - u256(0), - Instruction::SUB - }, { - u256(0), - Instruction::DUP2, - Instruction::SWAP1, - Instruction::SUB - }); -} - - BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 78edd4d1..2be824d6 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -898,7 +898,12 @@ BOOST_AUTO_TEST_CASE(multiple_visibility_specifiers) contract c { uint private internal a; })"; - CHECK_PARSE_ERROR(text, "Visibility already specified"); + CHECK_PARSE_ERROR(text, "Visibility already specified as \"private\"."); + text = R"( + contract c { + function f() private external {} + })"; + CHECK_PARSE_ERROR(text, "Visibility already specified as \"private\"."); } BOOST_AUTO_TEST_CASE(multiple_payable_specifiers) |