diff --git a/CMakeLists.txt b/CMakeLists.txt index ebb4a615a4e9..1b96859fe780 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.5.5") +set(PROJECT_VERSION "0.5.6") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) option(LLL "Build LLL" OFF) diff --git a/Changelog.md b/Changelog.md index b7ef05e25a2d..afa1c37623eb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,35 @@ +### 0.5.6 (2019-03-13) + +Important Bugfixes: + * Yul Optimizer: Fix visitation order bug for the structural simplifier. + * Optimizer: Fix overflow in optimization rule that simplifies double shift by constant. + +Language Features: + * Allow calldata arrays with dynamically encoded base types with ABIEncoderV2. + * Allow dynamically encoded calldata structs with ABIEncoderV2. + + +Compiler Features: + * Optimizer: Add rules for ``lt``-comparisons with constants. + * Peephole Optimizer: Remove double ``iszero`` before ``jumpi``. + * SMTChecker: Support enums without typecast. + * SMTChecker: Support one-dimensional arrays. + * Type Checker: Provide better error messages for some literal conversions. + * Yul Optimizer: Add rule to remove empty default switch cases. + * Yul Optimizer: Add rule to remove empty cases if no default exists. + * Yul Optimizer: Add rule to replace a switch with no cases with ``pop(expression)``. + + +Bugfixes: + * JSON ABI: Json description of library ABIs no longer contains functions with internal types like storage structs. + * SMTChecker: Fix internal compiler error when contract contains too large rational number. + * Type system: Detect if a contract's base uses types that require the experimental abi encoder while the contract still uses the old encoder. + + +Build System: + * Soltest: Add support for arrays in function signatures. + * Soltest: Add support for struct arrays in function signatures. + ### 0.5.5 (2019-03-05) Language Features: @@ -7,10 +39,12 @@ Language Features: Compiler Features: * Support ``petersburg`` as ``evmVersion`` and set as default. + * Commandline Interface: Option to activate the experimental yul optimizer using ``-optimize-yul``. * Inline Assembly: Consider ``extcodehash`` as part of Constantinople. * Inline Assembly: Instructions unavailable to the currently configured EVM are errors now. * SMTChecker: Do not report underflow/overflow if they always revert. This removes false positives when using ``SafeMath``. * Standard JSON Interface: Allow retrieving metadata without triggering bytecode generation. + * Standard JSON Interface: Provide fine-grained control over the optimizer via the settings. * Static Analyzer: Warn about expressions with custom types when they have no effect. * Optimizer: Add new rules with constants including ``LT``, ``GT``, ``AND`` and ``BYTE``. * Optimizer: Add rule for shifts with constants for Constantinople. @@ -36,6 +70,7 @@ Bugfixes: Build System: * Soltest: Add support for left-aligned, padded hex literals. + * Soltest: Add support for left-aligned, unpadded hex string literals. * Soltest: Add support for right-aligned, padded boolean literals. ### 0.5.4 (2019-02-12) diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md index 39dc30ad7088..6aa0ad79c4f5 100644 --- a/ReleaseChecklist.md +++ b/ReleaseChecklist.md @@ -38,14 +38,15 @@ - [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb ### Documentation - - [ ] Update the default version on readthedocs. + - [ ] Build the new version on https://readthedocs.org/projects/solidity/ (select `latest` on the bottom of the page and click `BUILD`) + - [ ] In the admin panel, select `Versions` in the menu and set the default version to the released one. ### Release solc-js - [ ] Increment the version number, create a pull request for that, merge it after tests succeeded. - [ ] Run ``npm publish`` in the updated ``solc-js`` repository. - - [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry. - - [ ] Merge ``release`` back into ``develop``. ### Post-release + - [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry. + - [ ] Merge ``release`` back into ``develop``. - [ ] Announce on Twitter and Reddit. - [ ] Lean back, wait for bug reports and repeat from step 1 :) diff --git a/docs/050-breaking-changes.rst b/docs/050-breaking-changes.rst index 01d21c8c468a..46ddc9abe054 100644 --- a/docs/050-breaking-changes.rst +++ b/docs/050-breaking-changes.rst @@ -308,7 +308,7 @@ This will no longer compile with Solidity v0.5.0. However, you can define a comp :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; interface OldContract { function someOldFunction(uint8 a) external; function anotherOldFunction() external returns (bool); @@ -325,7 +325,7 @@ Given the interface defined above, you can now easily use the already deployed p :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; interface OldContract { function someOldFunction(uint8 a) external; @@ -345,7 +345,7 @@ commandline compiler for linking): :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; library OldLibrary { function someFunction(uint8 a) public returns(bool); @@ -430,7 +430,7 @@ New version: :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract OtherContract { uint x; diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index fff087b0f38a..86e48ae90ccd 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -212,7 +212,7 @@ Given the contract: :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract Foo { function bar(bytes3[2] memory) public pure {} @@ -483,7 +483,7 @@ For example, :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract Test { constructor() public { b = hex"12345678901234567890123456789012"; } @@ -530,7 +530,7 @@ As an example, the code :: - pragma solidity >=0.4.19 <0.6.0; + pragma solidity >=0.4.19 <0.7.0; pragma experimental ABIEncoderV2; contract Test { diff --git a/docs/assembly.rst b/docs/assembly.rst index 953ebf48de9c..835ae11ac249 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -76,7 +76,7 @@ idea is that assembly libraries will be used to enhance the Solidity language. .. code:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; library GetCode { function at(address _addr) public view returns (bytes memory o_code) { @@ -101,7 +101,7 @@ efficient code, for example: .. code:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; library VectorSum { // This function is less efficient because the optimizer currently fails to @@ -394,7 +394,7 @@ Local Solidity variables are available for assignments, for example: .. code:: - pragma solidity >=0.4.11 <0.6.0; + pragma solidity >=0.4.11 <0.7.0; contract C { uint b; @@ -433,7 +433,7 @@ be just ``0``, but it can also be a complex functional-style expression. .. code:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { function f(uint x) public view returns (uint b) { @@ -690,7 +690,7 @@ Example: We will follow an example compilation from Solidity to assembly. We consider the runtime bytecode of the following Solidity program:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { function f(uint x) public pure returns (uint y) { diff --git a/docs/bugs.json b/docs/bugs.json index 41ebce7bb621..327e54a183aa 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,16 @@ [ + { + "name": "DoubleShiftSizeOverflow", + "summary": "Double bitwise shifts by large constants whose sum overflows 256 bits can result in unexpected values.", + "description": "Nested logical shift operations whose total shift size is 2**256 or more are incorrectly optimized. This only applies to shifts by numbers of bits that are compile-time constant expressions.", + "introduced": "0.5.5", + "fixed": "0.5.6", + "severity": "low", + "conditions": { + "optimizer": true, + "evmVersion": ">=constantinople" + } + }, { "name": "ExpExponentCleanup", "summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.", diff --git a/docs/bugs.rst b/docs/bugs.rst index f7522183aa17..11680abcd410 100644 --- a/docs/bugs.rst +++ b/docs/bugs.rst @@ -52,9 +52,15 @@ severity discoverability in contract tests, likelihood of occurrence and potential damage by exploits. conditions - Conditions that have to be met to trigger the bug. Currently, this - is an object that can contain a boolean value ``optimizer``, which + Conditions that have to be met to trigger the bug. The following + keys can be used: + ``optimizer``, Boolean value which means that the optimizer has to be switched on to enable the bug. + ``evmVersion``, a string that indicates which EVM version compiler + settings trigger the bug. The string can contain comparison + operators. For example, ``">=constantinople"`` means that the bug + is present when the EVM version is set to ``constantinople`` or + later. If no conditions are given, assume that the bug is present. check This field contains different checks that report whether the smart contract diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index da2b1dc1edda..dd779e0ed19b 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -630,7 +630,13 @@ "released": "2019-02-12" }, "0.5.5": { - "bugs": [], + "bugs": [ + "DoubleShiftSizeOverflow" + ], "released": "2019-03-05" + }, + "0.5.6": { + "bugs": [], + "released": "2019-03-13" } } \ No newline at end of file diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index 65bf7f44f9b0..5f15e38933e2 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -28,7 +28,7 @@ become the new richest. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract WithdrawalContract { address public richest; @@ -65,7 +65,7 @@ This is as opposed to the more intuitive sending pattern: :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract SendContract { address payable public richest; @@ -130,7 +130,7 @@ restrictions highly readable. :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract AccessRestriction { // These will be assigned at the construction @@ -282,7 +282,7 @@ function finishes. :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract StateMachine { enum Stages { diff --git a/docs/contracts/abstract-contracts.rst b/docs/contracts/abstract-contracts.rst index 87340733d8cd..924805467ccf 100644 --- a/docs/contracts/abstract-contracts.rst +++ b/docs/contracts/abstract-contracts.rst @@ -8,7 +8,7 @@ Abstract Contracts Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``):: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract Feline { function utterance() public returns (bytes32); @@ -16,7 +16,7 @@ Contracts are marked as abstract when at least one of their functions lacks an i Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract Feline { function utterance() public returns (bytes32); diff --git a/docs/contracts/constant-state-variables.rst b/docs/contracts/constant-state-variables.rst index 3e615ed01730..2b4b7ff5b462 100644 --- a/docs/contracts/constant-state-variables.rst +++ b/docs/contracts/constant-state-variables.rst @@ -26,7 +26,7 @@ value types and strings. :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract C { uint constant x = 32**22 + 8; diff --git a/docs/contracts/creating-contracts.rst b/docs/contracts/creating-contracts.rst index 981243b12563..8b7784c36bb4 100644 --- a/docs/contracts/creating-contracts.rst +++ b/docs/contracts/creating-contracts.rst @@ -33,7 +33,7 @@ This means that cyclic creation dependencies are impossible. :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract OwnedToken { // `TokenCreator` is a contract type that is defined below. diff --git a/docs/contracts/events.rst b/docs/contracts/events.rst index ecb0a87f8a11..d97f2dd2558e 100644 --- a/docs/contracts/events.rst +++ b/docs/contracts/events.rst @@ -63,7 +63,7 @@ not possible to filter for specific anonymous events by name. :: - pragma solidity >=0.4.21 <0.6.0; + pragma solidity >=0.4.21 <0.7.0; contract ClientReceipt { event Deposit( @@ -136,7 +136,7 @@ as topics. The event call above can be performed in the same way as :: - pragma solidity >=0.4.10 <0.6.0; + pragma solidity >=0.4.10 <0.7.0; contract C { function f() public payable { diff --git a/docs/contracts/function-modifiers.rst b/docs/contracts/function-modifiers.rst index 376cd9fa1fd9..fd24a8cb5b00 100644 --- a/docs/contracts/function-modifiers.rst +++ b/docs/contracts/function-modifiers.rst @@ -12,7 +12,7 @@ inheritable properties of contracts and may be overridden by derived contracts. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract owned { constructor() public { owner = msg.sender; } diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index 522ce5c47c31..4e5a52c0f6bd 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -23,7 +23,7 @@ unused parameters can be omitted. For example, if you want your contract to accept one kind of external call with two integers, you would use something like:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract Simple { uint sum; @@ -55,7 +55,7 @@ Function return variables are declared with the same syntax after the For example, suppose you want to return two results: the sum and the product of two integers passed as function parameters, then you use something like:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract Simple { function arithmetic(uint _a, uint _b) @@ -78,7 +78,7 @@ or you can provide return values (either a single or :ref:`multiple ones`) directly with the ``return`` statement:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract Simple { function arithmetic(uint _a, uint _b) @@ -140,7 +140,7 @@ The following statements are considered modifying the state: :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract C { function f(uint a, uint b) public view returns (uint) { @@ -185,7 +185,7 @@ In addition to the list of state modifying statements explained above, the follo :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract C { function f(uint a, uint b) public pure returns (uint) { @@ -279,7 +279,7 @@ Like any function, the fallback function can execute complex operations as long :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract Test { // This function is called for all messages sent to @@ -330,7 +330,7 @@ The following example shows overloading of the function :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract A { function f(uint _in) public pure returns (uint out) { @@ -348,7 +348,7 @@ externally visible functions differ by their Solidity types but not by their ext :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; // This will not compile contract A { @@ -381,7 +381,7 @@ candidate, resolution fails. :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract A { function f(uint8 _in) public pure returns (uint8 out) { diff --git a/docs/contracts/inheritance.rst b/docs/contracts/inheritance.rst index 2e94c2f9d2ae..0474a2b5ce0b 100644 --- a/docs/contracts/inheritance.rst +++ b/docs/contracts/inheritance.rst @@ -23,7 +23,7 @@ Details are given in the following example. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract owned { constructor() public { owner = msg.sender; } @@ -95,7 +95,7 @@ Note that above, we call ``mortal.kill()`` to "forward" the destruction request. The way this is done is problematic, as seen in the following example:: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract owned { constructor() public { owner = msg.sender; } @@ -124,7 +124,7 @@ derived override, but this function will bypass ``Base1.kill``, basically because it does not even know about ``Base1``. The way around this is to use ``super``:: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract owned { constructor() public { owner = msg.sender; } @@ -188,7 +188,7 @@ equivalent to ``constructor() public {}``. For example: :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract A { uint public a; @@ -218,7 +218,7 @@ The constructors of all the base contracts will be called following the linearization rules explained below. If the base constructors have arguments, derived contracts need to specify all of them. This can be done in two ways:: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract Base { uint x; @@ -277,7 +277,7 @@ error "Linearization of inheritance graph impossible". :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract X {} contract A is X {} diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst index b551b51843a4..43c8637ee6d9 100644 --- a/docs/contracts/interfaces.rst +++ b/docs/contracts/interfaces.rst @@ -22,7 +22,7 @@ Interfaces are denoted by their own keyword: :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; interface Token { enum TokenType { Fungible, NonFungible } diff --git a/docs/contracts/libraries.rst b/docs/contracts/libraries.rst index 0cabe18ac167..eaa69100c656 100644 --- a/docs/contracts/libraries.rst +++ b/docs/contracts/libraries.rst @@ -47,7 +47,7 @@ more advanced example to implement a set). :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; library Set { // We define a new struct datatype that will be used to @@ -121,7 +121,7 @@ custom types without the overhead of external function calls: :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; library BigInt { struct bigint { diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst index ef456ff42f98..12fc976d54a9 100644 --- a/docs/contracts/using-for.rst +++ b/docs/contracts/using-for.rst @@ -31,7 +31,7 @@ available without having to add further code. Let us rewrite the set example from the :ref:`libraries` in this way:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; // This is the same code as before, just without comments library Set { @@ -81,7 +81,7 @@ Let us rewrite the set example from the It is also possible to extend elementary types in that way:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; library Search { function indexOf(uint[] storage self, uint value) diff --git a/docs/contracts/visibility-and-getters.rst b/docs/contracts/visibility-and-getters.rst index e78c967414dd..e2a10122ced8 100644 --- a/docs/contracts/visibility-and-getters.rst +++ b/docs/contracts/visibility-and-getters.rst @@ -53,7 +53,7 @@ return parameter list for functions. :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { function f(uint a) private pure returns (uint b) { return a + 1; } @@ -67,7 +67,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract C { uint private data; @@ -111,7 +111,7 @@ when they are declared. :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract C { uint public data = 42; @@ -131,7 +131,7 @@ it evaluates to a state variable. If it is accessed externally :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract C { uint public data; @@ -150,7 +150,7 @@ to write a function, for example: :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract arrayExample { // public state variable @@ -176,7 +176,7 @@ The next example is more complex: :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract Complex { struct Data { diff --git a/docs/contributing.rst b/docs/contributing.rst index 5e9308b50c9b..c0ac358b7a92 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -404,4 +404,15 @@ Common Terms Code Examples ------------- -* Ensure that all code examples begin with a ``pragma`` version that spans the largest where the contract code is valid. For example ``pragma solidity >=0.4.0 <0.6.0;``. \ No newline at end of file +A CI process tests all code block formatted code examples that begin with ``pragma solidity``, ``contract``, ``library`` +or ``interface`` using the ``./test/cmdlineTests.sh`` script when you create a PR. If you are adding new code examples, +ensure they work and pass tests before creating the PR. + +Ensure that all code examples begin with a ``pragma`` version that spans the largest where the contract code is valid. +For example ``pragma solidity >=0.4.0 <0.7.0;``. + +Running Documentation Tests +--------------------------- + +Make sure your contributions pass our documentation tests by running ``./scripts/docs.sh`` that installs dependencies +needed for documentation and checks for any problems such as broken links or syntax issues. \ No newline at end of file diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 6e3c64d50963..fa59a4adf127 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -37,7 +37,7 @@ Internal Function Calls Functions of the current contract can be called directly ("internally"), also recursively, as seen in this nonsensical example:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { function g(uint a) public pure returns (uint ret) { return a + f(); } @@ -75,7 +75,7 @@ When calling functions of other contracts, you can specify the amount of Wei or :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract InfoFeed { function info() public payable returns (uint ret) { return 42; } @@ -122,7 +122,7 @@ parameters from the function declaration, but can be in arbitrary order. :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract C { mapping(uint => uint) data; @@ -145,7 +145,7 @@ Those parameters will still be present on the stack, but they are inaccessible. :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { // omitted name for parameter @@ -168,7 +168,7 @@ is compiled so recursive creation-dependencies are not possible. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract D { uint public x; @@ -225,7 +225,7 @@ groupings of expressions. :: - pragma solidity >0.4.23 <0.6.0; + pragma solidity >0.4.23 <0.7.0; contract C { uint[] data; @@ -270,7 +270,7 @@ because only a reference and not a copy is passed. :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { uint[20] x; @@ -316,7 +316,7 @@ the two variables have the same name but disjoint scopes. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract C { function minimalScoping() pure public { { @@ -337,7 +337,7 @@ In any case, you will get a warning about the outer variable being shadowed. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; // This will report a warning contract C { function f() pure public returns (uint) { @@ -357,7 +357,7 @@ In any case, you will get a warning about the outer variable being shadowed. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; // This will not compile contract C { function f() pure public returns (uint) { @@ -404,7 +404,7 @@ a message string for ``require``, but not for ``assert``. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract Sharer { function sendHalf(address payable addr) public payable returns (uint balance) { @@ -450,7 +450,7 @@ The following example shows how an error string can be used together with revert :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract VendingMachine { function buy(uint amount) public payable { diff --git a/docs/examples/blind-auction.rst b/docs/examples/blind-auction.rst index 70d95ad6d6ca..0b13621ec9a5 100644 --- a/docs/examples/blind-auction.rst +++ b/docs/examples/blind-auction.rst @@ -30,7 +30,7 @@ activate themselves. :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract SimpleAuction { // Parameters of the auction. Times are either @@ -194,7 +194,7 @@ high or low invalid bids. :: - pragma solidity >0.4.23 <0.6.0; + pragma solidity >0.4.23 <0.7.0; contract BlindAuction { struct Bid { diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst index e06ea4a4ff74..c7f4a99252ee 100644 --- a/docs/examples/micropayment.rst +++ b/docs/examples/micropayment.rst @@ -112,7 +112,7 @@ The full contract :: - pragma solidity >=0.4.24 <0.6.0; + pragma solidity >=0.4.24 <0.7.0; contract ReceiverPays { address owner = msg.sender; @@ -286,7 +286,7 @@ The full contract :: - pragma solidity >=0.4.24 <0.6.0; + pragma solidity >=0.4.24 <0.7.0; contract SimplePaymentChannel { address payable public sender; // The account sending payments. diff --git a/docs/examples/modular.rst b/docs/examples/modular.rst index b9bd92d9c605..ebde4059204c 100644 --- a/docs/examples/modular.rst +++ b/docs/examples/modular.rst @@ -11,7 +11,7 @@ addresses match what you expect. :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; library Balances { function move(mapping(address => uint256) storage balances, address from, address to, uint amount) internal { diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst index cfc63a240d1f..765bdfc350c6 100644 --- a/docs/examples/safe-remote.rst +++ b/docs/examples/safe-remote.rst @@ -6,7 +6,7 @@ Safe Remote Purchase :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract Purchase { uint public value; diff --git a/docs/examples/voting.rst b/docs/examples/voting.rst index 73ace87d92c3..e4b6da200809 100644 --- a/docs/examples/voting.rst +++ b/docs/examples/voting.rst @@ -32,7 +32,7 @@ of votes. :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; /// @title Voting with delegation. contract Ballot { diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 0cce690b3f17..c221a72b21ca 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -17,7 +17,7 @@ Storage :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract SimpleStorage { uint storedData; @@ -35,7 +35,7 @@ The first line simply tells that the source code is written for Solidity version 0.4.0 or anything newer that does not break functionality (up to, but not including, version 0.6.0). This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently. -So-called pragmas are common instructions for compilers about how to treat the +:ref:`Pragmas` are common instructions for compilers about how to treat the source code (e.g. `pragma once `_). A contract in the sense of Solidity is a collection of code (its *functions*) and @@ -81,7 +81,7 @@ registering with username and password — all you need is an Ethereum keypair. :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; contract Coin { // The keyword "public" makes those variables diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index 235f4dd4f49e..5195a131c55f 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -13,11 +13,11 @@ and :ref:`pragma directives`. Pragmas ======= -The ``pragma`` keyword can be used to enable certain compiler features +The ``pragma`` keyword is used to enable certain compiler features or checks. A pragma directive is always local to a source file, so you have to add the pragma to all your files if you want enable it in all of your project. If you :ref:`import` another file, the pragma -from that file will not automatically apply to the importing file. +from that file does not automatically apply to the importing file. .. index:: ! pragma, version @@ -26,34 +26,34 @@ from that file will not automatically apply to the importing file. Version Pragma -------------- -Source files can (and should) be annotated with a so-called version pragma to reject -being compiled with future compiler versions that might introduce incompatible -changes. We try to keep such changes to an absolute minimum and especially -introduce changes in a way that changes in semantics will also require changes -in the syntax, but this is of course not always possible. Because of that, it is always +Source files can (and should) be annotated with a version pragma to reject +compilation with future compiler versions that might introduce incompatible +changes. We try to keep these to an absolute minimum and +introduce them in a way that changes in semantics also require changes +in the syntax, but this is not always possible. Because of this, it is always a good idea to read through the changelog at least for releases that contain -breaking changes, those releases will always have versions of the form +breaking changes. These releases always have versions of the form ``0.x.0`` or ``x.0.0``. The version pragma is used as follows:: pragma solidity ^0.5.2; -Such a source file will not compile with a compiler earlier than version 0.5.2 -and it will also not work on a compiler starting from version 0.6.0 (this -second condition is added by using ``^``). The idea behind this is that -there will be no breaking changes until version ``0.6.0``, so we can always -be sure that our code will compile the way we intended it to. We do not fix -the exact version of the compiler, so that bugfix releases are still possible. +A source file with the line above does not compile with a compiler earlier than version 0.5.2, +and it also does not work on a compiler starting from version 0.6.0 (this +second condition is added by using ``^``). This is because +there will be no breaking changes until version ``0.6.0``, so you can always +be sure that your code compiles the way you intended. The exact version of the +compiler is not fixed, so that bugfix releases are still possible. -It is possible to specify much more complex rules for the compiler version, -the expression follows those used by `npm `_. +It is possible to specify more complex rules for the compiler version, +these follow the same syntax used by `npm `_. .. note:: - Using the version pragma will *not* change the version of the compiler. - It will also *not* enable or disable features of the compiler. It will just - instruct the compiler to check whether its version matches the one - required by the pragma. If it does not match, the compiler will issue + Using the version pragma *does not* change the version of the compiler. + It also *does not* enable or disable features of the compiler. It just + instructs the compiler to check whether its version matches the one + required by the pragma. If it does not match, the compiler issues an error. .. index:: ! pragma, experimental @@ -96,7 +96,7 @@ The component does not yet support all features of the Solidity language and likely outputs many warnings. In case it reports unsupported features, the analysis may not be fully sound. -.. index:: source file, ! import +.. index:: source file, ! import, module .. _import: @@ -106,8 +106,8 @@ Importing other Source Files Syntax and Semantics -------------------- -Solidity supports import statements that are very similar to those available in JavaScript -(from ES6 on), although Solidity does not know the concept of a "default export". +Solidity supports import statements to help modularise your code that are similar to those available in JavaScript +(from ES6 on). However, Solidity does not support the concept of a `default export `_. At a global level, you can use import statements of the following form: @@ -117,29 +117,21 @@ At a global level, you can use import statements of the following form: This statement imports all global symbols from "filename" (and symbols imported there) into the current global scope (different than in ES6 but backwards-compatible for Solidity). -This simple form is not recommended for use, because it pollutes the namespace in an -unpredictable way: If you add new top-level items inside "filename", they will automatically +This form is not recommended for use, because it unpredictably pollutes the namespace. +If you add new top-level items inside "filename", they automatically appear in all files that import like this from "filename". It is better to import specific symbols explicitly. The following example creates a new global symbol ``symbolName`` whose members are all -the global symbols from ``"filename"``. +the global symbols from ``"filename"``: :: import * as symbolName from "filename"; -If there is a naming collision, you can also rename symbols while importing. -This code -creates new global symbols ``alias`` and ``symbol2`` which reference ``symbol1`` and ``symbol2`` from inside ``"filename"``, respectively. +which results in all global symbols being available in the format ``symbolName.symbol``. -:: - - import {symbol1 as alias, symbol2} from "filename"; - - - -Another syntax is not part of ES6, but probably convenient: +A variant of this syntax that is not part of ES6, but possibly useful is: :: @@ -147,31 +139,37 @@ Another syntax is not part of ES6, but probably convenient: which is equivalent to ``import * as symbolName from "filename";``. -.. note:: - If you use `import "filename.sol" as moduleName;`, you access a contract called `C` - from inside `"filename.sol"` as `moduleName.C` and not by using `C` directly. +If there is a naming collision, you can rename symbols while importing. For example, +the code below creates new global symbols ``alias`` and ``symbol2`` which reference +``symbol1`` and ``symbol2`` from inside ``"filename"``, respectively. + +:: + + import {symbol1 as alias, symbol2} from "filename"; Paths ----- In the above, ``filename`` is always treated as a path with ``/`` as directory separator, -``.`` as the current and ``..`` as the parent directory. When ``.`` or ``..`` is followed by a character except ``/``, +and ``.`` as the current and ``..`` as the parent directory. When ``.`` or ``..`` is followed by a character except ``/``, it is not considered as the current or the parent directory. All path names are treated as absolute paths unless they start with the current ``.`` or the parent directory ``..``. -To import a file ``x`` from the same directory as the current file, use ``import "./x" as x;``. -If you use ``import "x" as x;`` instead, a different file could be referenced +To import a file ``filename`` from the same directory as the current file, use ``import "./filename" as symbolName;``. +If you use ``import "filename" as symbolName;`` instead, a different file could be referenced (in a global "include directory"). -It depends on the compiler (see below) how to actually resolve the paths. +It depends on the compiler (see :ref:`import-compiler`) how to actually resolve the paths. In general, the directory hierarchy does not need to strictly map onto your local -filesystem, it can also map to resources discovered via e.g. ipfs, http or git. +filesystem, and the path can also map to resources such as ipfs, http or git. .. note:: Always use relative imports like ``import "./filename.sol";`` and avoid using ``..`` in path specifiers. In the latter case, it is probably better to use global paths and set up remappings as explained below. +.. _import-compiler: + Use in Actual Compilers ----------------------- @@ -280,7 +278,7 @@ for the two function parameters and two return variables. :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; /** @title Shape calculator. */ contract ShapeCalculator { diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index a57aa2884cd9..048283e8d7b7 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -62,7 +62,7 @@ non-elementary type, the positions are found by adding an offset of ``keccak256( So for the following contract snippet:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract C { struct s { uint a; uint b; } diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index ebc39ad0c86b..c3b04f08fe47 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -55,7 +55,7 @@ complete contract): :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; // THIS CONTRACT CONTAINS A BUG - DO NOT USE contract Fund { @@ -78,7 +78,7 @@ as it uses ``call`` which forwards all remaining gas by default: :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; // THIS CONTRACT CONTAINS A BUG - DO NOT USE contract Fund { @@ -97,7 +97,7 @@ outlined further below: :: - pragma solidity >=0.4.11 <0.6.0; + pragma solidity >=0.4.11 <0.7.0; contract Fund { /// Mapping of ether shares of the contract. @@ -183,7 +183,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; // THIS CONTRACT CONTAINS A BUG - DO NOT USE contract TxUserWallet { @@ -203,7 +203,7 @@ Now someone tricks you into sending ether to the address of this attack wallet: :: - pragma solidity ^0.5.0; + pragma solidity >=0.5.0 <0.7.0; interface TxUserWallet { function transferTo(address payable dest, uint amount) external; diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 569639e0b02a..01ed23233145 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -26,7 +26,7 @@ storage. :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract SimpleStorage { uint storedData; // State variable @@ -46,7 +46,7 @@ Functions are the executable units of code within a contract. :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract SimpleAuction { function bid() public payable { // Function @@ -69,7 +69,7 @@ Function modifiers can be used to amend the semantics of functions in a declarat :: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract Purchase { address public seller; @@ -96,7 +96,7 @@ Events are convenience interfaces with the EVM logging facilities. :: - pragma solidity >=0.4.21 <0.6.0; + pragma solidity >=0.4.21 <0.7.0; contract SimpleAuction { event HighestBidIncreased(address bidder, uint amount); // Event @@ -120,7 +120,7 @@ Structs are custom defined types that can group several variables (see :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract Ballot { struct Voter { // Struct @@ -141,7 +141,7 @@ Enums can be used to create custom types with a finite set of 'constant values' :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract Purchase { enum State { Created, Locked, Inactive } // Enum diff --git a/docs/style-guide.rst b/docs/style-guide.rst index dcbfc486ab3b..d754f1ce7503 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -52,7 +52,7 @@ Surround top level declarations in solidity source with two blank lines. Yes:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract A { // ... @@ -70,7 +70,7 @@ Yes:: No:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract A { // ... @@ -89,7 +89,7 @@ Blank lines may be omitted between groups of related one-liners (such as stub fu Yes:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract A { function spam() public pure; @@ -109,7 +109,7 @@ Yes:: No:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract A { function spam() public pure { @@ -237,7 +237,7 @@ Import statements should always be placed at the top of the file. Yes:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; import "./Owned.sol"; @@ -251,7 +251,7 @@ Yes:: No:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract A { // ... @@ -283,7 +283,7 @@ Within a grouping, place the ``view`` and ``pure`` functions last. Yes:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract A { constructor() public { @@ -315,7 +315,7 @@ Yes:: No:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract A { @@ -411,7 +411,7 @@ should: Yes:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract Coin { struct Bank { @@ -422,7 +422,7 @@ Yes:: No:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract Coin { @@ -723,7 +723,7 @@ manner as modifiers if the function declaration is long or hard to read. Yes:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; // Base contracts just to make this compile contract B { @@ -755,7 +755,7 @@ Yes:: No:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; // Base contracts just to make this compile contract B { @@ -971,7 +971,7 @@ As shown in the example below, if the contract name is `Congress` and the librar Yes:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; // Owned.sol contract Owned { @@ -1000,7 +1000,7 @@ Yes:: No:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; // owned.sol contract owned { @@ -1104,7 +1104,7 @@ multiline comment starting with `/**` and ending with `*/`. For example, the contract from `a simple smart contract `_ with the comments added looks like the one below:: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; /// @author The Solidity Team /// @title A simple storage example diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index 935ed6b4fd97..2414687c5345 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -34,7 +34,7 @@ each ``_KeyType``, recursively. For example with a mapping: :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract MappingExample { mapping(address => uint) public balances; diff --git a/docs/types/operators.rst b/docs/types/operators.rst index 9851f2145c07..f0915e2dfe1a 100644 --- a/docs/types/operators.rst +++ b/docs/types/operators.rst @@ -27,7 +27,7 @@ value it referred to previously. :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract DeleteExample { uint data; diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst index c640ca22d777..6a4f6e64c81b 100644 --- a/docs/types/reference-types.rst +++ b/docs/types/reference-types.rst @@ -49,7 +49,7 @@ Data locations are not only relevant for persistency of data, but also for the s :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; contract C { uint[] x; // the data location of x is storage @@ -146,7 +146,7 @@ or create a new memory array and copy every element. :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { function f(uint len) public pure { @@ -175,7 +175,7 @@ In the example below, the type of ``[1, 2, 3]`` is :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract C { function f() public pure { @@ -190,7 +190,7 @@ Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, :: - pragma solidity >=0.4.0 <0.6.0; + pragma solidity >=0.4.0 <0.7.0; // This will not compile. contract C { @@ -248,7 +248,7 @@ Array Members :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract ArrayContract { uint[2**20] m_aLotOfIntegers; @@ -347,7 +347,7 @@ shown in the following example: :: - pragma solidity >=0.4.11 <0.6.0; + pragma solidity >=0.4.11 <0.7.0; contract CrowdFunding { // Defines a new type with two fields. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 039e605e2125..9c082f9b450e 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -59,15 +59,16 @@ This means that, for example ``~int256(0) == int256(-1)``. Shifts ^^^^^^ -The result of a shift operation has the type of the left operand. The -expression ``x << y`` is equivalent to ``x * 2**y``, and, for positive integers, -``x >> y`` is equivalent to ``x / 2**y``. For negative ``x``, ``x >> y`` -is equivalent to dividing by a power of ``2`` while rounding down (towards negative infinity). -Shifting by a negative amount throws a runtime exception. +The result of a shift operation has the type of the left operand, truncating the result to match the type. + +- For positive and negative ``x`` values, ``x << y`` is equivalent to ``x * 2**y``. +- For positive ``x`` values, ``x >> y`` is equivalent to ``x / 2**y``. +- For negative ``x`` values, ``x >> y`` is equivalent to ``(x + 1) / 2**y - 1`` (which is the same as dividing ``x`` by ``2**y`` while rounding down towards negative infinity). +- In all cases, shifting by a negative ``y`` throws a runtime exception. .. warning:: Before version ``0.5.0`` a right shift ``x >> y`` for negative ``x`` was equivalent to ``x / 2**y``, - i.e. right shifts used rounding towards zero instead of rounding towards negative infinity. + i.e., right shifts used rounding up (towards zero) instead of rounding down (towards negative infinity). Addition, Subtraction and Multiplication ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -517,7 +518,7 @@ subsequent unsigned integer values starting from ``0``. :: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } @@ -621,7 +622,7 @@ Public (or external) functions have the following members: Example that shows how to use the members:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; contract Example { function f() public payable returns (bytes4) { @@ -634,7 +635,7 @@ Example that shows how to use the members:: Example that shows how to use internal function types:: - pragma solidity >=0.4.16 <0.6.0; + pragma solidity >=0.4.16 <0.7.0; library ArrayUtils { // internal functions can be used in internal library functions because @@ -685,7 +686,7 @@ Example that shows how to use internal function types:: Another example that uses external function types:: - pragma solidity >=0.4.22 <0.6.0; + pragma solidity >=0.4.22 <0.7.0; contract Oracle { struct Request { diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 6c80fb0dfb62..eb7e38d512bc 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -163,6 +163,10 @@ Mathematical and Cryptographic Functions ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input +.. note:: + + There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0. + ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input @@ -182,12 +186,15 @@ Mathematical and Cryptographic Functions For further details, read `example usage `_. -.. note:: +.. warning:: - When running ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*, you might encounter Out-of-Gas. This is because these functions are implemented as "precompiled contracts" and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net. + If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without requiring + knowledge of the corresponding private key. This is usually not a problem unless you require signatures to be unique or + use them to identify items. OpenZeppelin have a `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. .. note:: - There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0. + + When running ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*, you might encounter Out-of-Gas. This is because these functions are implemented as "precompiled contracts" and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net. .. index:: balance, send, transfer, call, callcode, delegatecall, staticcall diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index e211026b2f64..2279f24a1c01 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -45,6 +45,14 @@ struct ApplyRule { }; template +struct ApplyRule +{ + static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + return Method::applySimple(_in[0], _in[1], _in[2], _in[3], _out); + } +}; +template struct ApplyRule { static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) @@ -197,6 +205,32 @@ struct SwapComparison: SimplePeepholeOptimizerMethod } }; +struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod +{ + static size_t applySimple( + AssemblyItem const& _iszero1, + AssemblyItem const& _iszero2, + AssemblyItem const& _pushTag, + AssemblyItem const& _jumpi, + std::back_insert_iterator _out + ) + { + if ( + _iszero1 == Instruction::ISZERO && + _iszero2 == Instruction::ISZERO && + _pushTag.type() == PushTag && + _jumpi == Instruction::JUMPI + ) + { + *_out = _pushTag; + *_out = _jumpi; + return true; + } + else + return false; + } +}; + struct JumpToNext: SimplePeepholeOptimizerMethod { static size_t applySimple( @@ -320,7 +354,12 @@ bool PeepholeOptimiser::optimise() { OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; while (state.i < m_items.size()) - applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), JumpToNext(), UnreachableCode(), TagConjunctions(), TruthyAnd(), Identity()); + applyMethods( + state, + PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), + IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(), + TagConjunctions(), TruthyAnd(), Identity() + ); if (m_optimisedItems.size() < m_items.size() || ( m_optimisedItems.size() == m_items.size() && ( eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3) || diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 046633d17e02..5f5bb63524b3 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -146,10 +146,12 @@ std::vector> simplificationRuleListPart2( {{Instruction::SHR, {0, X}}, [=]{ return X; }, false}, {{Instruction::SHL, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::SHR, {X, 0}}, [=]{ return u256(0); }, true}, - {{Instruction::LT, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::GT, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}; }, false}, + {{Instruction::LT, {0, X}}, [=]() -> Pattern { return {Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}; }, false}, {{Instruction::GT, {X, ~u256(0)}}, [=]{ return u256(0); }, true}, + {{Instruction::LT, {~u256(0), X}}, [=]{ return u256(0); }, true}, {{Instruction::GT, {0, X}}, [=]{ return u256(0); }, true}, + {{Instruction::LT, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::AND, {{Instruction::BYTE, {X, Y}}, {u256(0xff)}}}, [=]() -> Pattern { return {Instruction::BYTE, {X, Y}}; }, false}, {{Instruction::BYTE, {X, 31}}, [=]() -> Pattern { return {Instruction::AND, {X, u256(0xff)}}; }, false} }; @@ -349,14 +351,26 @@ std::vector> simplificationRuleListPart7( rules.push_back({ // SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X) {Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}}, - [=]() -> Pattern { return {Instruction::SHL, {std::min(A.d() + B.d(), u256(256)), X}}; }, + [=]() -> Pattern { + bigint sum = bigint(A.d()) + B.d(); + if (sum >= 256) + return {Instruction::AND, {X, u256(0)}}; + else + return {Instruction::SHL, {u256(sum), X}}; + }, false }); rules.push_back({ // SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X) {Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}}, - [=]() -> Pattern { return {Instruction::SHR, {std::min(A.d() + B.d(), u256(256)), X}}; }, + [=]() -> Pattern { + bigint sum = bigint(A.d()) + B.d(); + if (sum >= 256) + return {Instruction::AND, {X, u256(0)}}; + else + return {Instruction::SHR, {u256(sum), X}}; + }, false }); diff --git a/liblangutil/ErrorReporter.cpp b/liblangutil/ErrorReporter.cpp index fb01847d5b8d..0bf3a4167162 100644 --- a/liblangutil/ErrorReporter.cpp +++ b/liblangutil/ErrorReporter.cpp @@ -118,6 +118,12 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) return false; } +void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +{ + error(_type, _location, _secondaryLocation, _description); + BOOST_THROW_EXCEPTION(FatalError()); +} + void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description) { error(_type, _location, _description); @@ -207,6 +213,15 @@ void ErrorReporter::typeError(SourceLocation const& _location, string const& _de ); } +void ErrorReporter::fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +{ + fatalError( + Error::Type::TypeError, + _location, + _secondaryLocation, + _description + ); +} void ErrorReporter::fatalTypeError(SourceLocation const& _location, string const& _description) { diff --git a/liblangutil/ErrorReporter.h b/liblangutil/ErrorReporter.h index d7cea4cdaa12..30b494a0fcba 100644 --- a/liblangutil/ErrorReporter.h +++ b/liblangutil/ErrorReporter.h @@ -98,12 +98,13 @@ class ErrorReporter auto filterEmpty = boost::adaptors::filtered([](std::string const& _s) { return !_s.empty(); }); - std::string errorStr = dev::joinHumanReadable(descs | filterEmpty); + std::string errorStr = dev::joinHumanReadable(descs | filterEmpty, " "); error(Error::Type::TypeError, _location, errorStr); } void fatalTypeError(SourceLocation const& _location, std::string const& _description); + void fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description); void docstringParsingError(std::string const& _description); @@ -118,12 +119,20 @@ class ErrorReporter } private: - void error(Error::Type _type, + void error( + Error::Type _type, + SourceLocation const& _location, + SecondarySourceLocation const& _secondaryLocation, + std::string const& _description = std::string()); + + void fatalError( + Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, std::string const& _description = std::string()); - void fatalError(Error::Type _type, + void fatalError( + Error::Type _type, SourceLocation const& _location = SourceLocation(), std::string const& _description = std::string()); diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 36125c2ef965..39ea693502ce 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -63,6 +63,10 @@ set(sources codegen/ExpressionCompiler.h codegen/LValue.cpp codegen/LValue.h + codegen/MultiUseYulFunctionCollector.h + codegen/MultiUseYulFunctionCollector.cpp + codegen/YulUtilFunctions.h + codegen/YulUtilFunctions.cpp formal/SMTChecker.cpp formal/SMTChecker.h formal/SMTLib2Interface.cpp diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index 96b9611e5560..1c6383b8bfda 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -44,6 +45,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract) checkExternalTypeClashes(_contract); checkHashCollisions(_contract); checkLibraryRequirements(_contract); + checkBaseABICompatibility(_contract); return Error::containsOnlyWarnings(m_errorReporter.errors()); } @@ -460,3 +462,50 @@ void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _c if (!var->isConstant()) m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); } + +void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _contract) +{ + if (_contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)) + return; + + if (_contract.isLibrary()) + { + solAssert( + _contract.baseContracts().empty() || m_errorReporter.hasErrors(), + "Library is not allowed to inherit" + ); + return; + } + + SecondarySourceLocation errors; + + // interfaceFunctionList contains all inherited functions as well + for (auto const& func: _contract.interfaceFunctionList()) + { + solAssert(func.second->hasDeclaration(), "Function has no declaration?!"); + + if (!func.second->declaration().sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)) + continue; + + auto const& currentLoc = func.second->declaration().location(); + + for (TypePointer const& paramType: func.second->parameterTypes() + func.second->parameterTypes()) + if (!TypeChecker::typeSupportedByOldABIEncoder(*paramType, false)) + { + errors.append("Type only supported by the new experimental ABI encoder", currentLoc); + break; + } + } + + if (!errors.infos.empty()) + m_errorReporter.fatalTypeError( + _contract.location(), + errors, + std::string("Contract \"") + + _contract.name() + + "\" does not use the new experimental ABI encoder but wants to inherit from a contract " + + "which uses types that require it. " + + "Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature." + ); + +} diff --git a/libsolidity/analysis/ContractLevelChecker.h b/libsolidity/analysis/ContractLevelChecker.h index d754687aeb60..f2cd9887f89b 100644 --- a/libsolidity/analysis/ContractLevelChecker.h +++ b/libsolidity/analysis/ContractLevelChecker.h @@ -78,6 +78,8 @@ class ContractLevelChecker void checkHashCollisions(ContractDefinition const& _contract); /// Checks that all requirements for a library are fulfilled if this is a library. void checkLibraryRequirements(ContractDefinition const& _contract); + /// Checks base contracts for ABI compatibility + void checkBaseABICompatibility(ContractDefinition const& _contract); langutil::ErrorReporter& m_errorReporter; }; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 5378f15fbd04..4620d82b0c5c 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -45,10 +45,7 @@ using namespace dev; using namespace langutil; using namespace dev::solidity; -namespace -{ - -bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall) +bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall) { if (_isLibraryCall && _type.dataStoredIn(DataLocation::Storage)) return true; @@ -64,9 +61,6 @@ bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall) return true; } -} - - bool TypeChecker::checkTypeRequirements(ASTNode const& _contract) { _contract.accept(*this); @@ -94,6 +88,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract) for (auto const& n: _contract.subNodes()) n->accept(*this); + return false; } @@ -376,28 +371,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function) }; for (ASTPointer const& var: _function.parameters()) { - TypePointer baseType = type(*var); - if (auto const* arrayType = dynamic_cast(baseType.get())) - { - baseType = arrayType->baseType(); - if ( - !m_scope->isInterface() && - baseType->dataStoredIn(DataLocation::CallData) && - baseType->isDynamicallyEncoded() - ) - m_errorReporter.typeError(var->location(), "Calldata arrays with dynamically encoded base types are not yet supported."); - } - while (auto const* arrayType = dynamic_cast(baseType.get())) - baseType = arrayType->baseType(); - - if ( - !m_scope->isInterface() && - baseType->dataStoredIn(DataLocation::CallData) - ) - if (auto const* structType = dynamic_cast(baseType.get())) - if (structType->isDynamicallyEncoded()) - m_errorReporter.typeError(var->location(), "Dynamically encoded calldata structs are not yet supported."); - checkArgumentAndReturnParameter(*var); var->accept(*this); } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 09879322e789..8f532a89cfd4 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -63,6 +63,8 @@ class TypeChecker: private ASTConstVisitor /// (this can happen for variables with non-explicit types before their types are resolved) TypePointer const& type(VariableDeclaration const& _variable) const; + static bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall); + private: bool visit(ContractDefinition const& _contract) override; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 88ca727ce745..0ece09f16c65 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -94,6 +94,12 @@ class AssemblyViewPureChecker: public boost::static_visitor (*this)(_for.body); (*this)(_for.post); } + void operator()(yul::Break const&) + { + } + void operator()(yul::Continue const&) + { + } void operator()(yul::Block const& _block) { for (auto const& s: _block.statements) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 60cbc7dce618..62170284c0ca 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -429,7 +429,6 @@ class ContractDefinition: public Declaration, public Documented std::vector> m_subNodes; ContractKind m_contractKind; - std::vector m_linearizedBaseContracts; mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr> m_interfaceEvents; mutable std::unique_ptr> m_inheritableMembers; @@ -589,6 +588,7 @@ class CallableDeclaration: public Declaration, public VariableScope } std::vector> const& parameters() const { return m_parameters->parameters(); } + std::vector> const& returnParameters() const { return m_returnParameters->parameters(); } ParameterList const& parameterList() const { return *m_parameters; } ASTPointer const& returnParameterList() const { return m_returnParameters; } @@ -629,7 +629,6 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public bool isFallback() const { return !m_isConstructor && name().empty(); } bool isPayable() const { return m_stateMutability == StateMutability::Payable; } std::vector> const& modifiers() const { return m_functionModifiers; } - std::vector> const& returnParameters() const { return m_returnParameters->parameters(); } Block const& body() const { solAssert(m_body, ""); return *m_body; } bool isVisibleInContract() const override { diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 4748b8217534..bcf9b53ff635 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -126,9 +126,15 @@ bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2) } /// Checks whether _value fits into IntegerType _type. -bool fitsIntegerType(bigint const& _value, IntegerType const& _type) +BoolResult fitsIntegerType(bigint const& _value, IntegerType const& _type) { - return (_type.minValue() <= _value) && (_value <= _type.maxValue()); + if (_value < 0 && !_type.isSigned()) + return BoolResult{std::string("Cannot implicitly convert signed literal to unsigned type.")}; + + if (_type.minValue() > _value || _value > _type.maxValue()) + return BoolResult{"Literal is too large to fit in " + _type.toString(false) + "."}; + + return true; } /// Checks whether _value fits into _bits bits when having 1 bit as the sign bit @@ -722,7 +728,9 @@ BoolResult FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) con if (_convertTo.category() == category()) { FixedPointType const& convertTo = dynamic_cast(_convertTo); - if (convertTo.numBits() < m_totalBits || convertTo.fractionalDigits() < m_fractionalDigits) + if (convertTo.fractionalDigits() < m_fractionalDigits) + return BoolResult{std::string("Too many fractional digits.")}; + if (convertTo.numBits() < m_totalBits) return false; else return convertTo.maxIntegerValue() >= maxIntegerValue() && convertTo.minIntegerValue() <= minIntegerValue(); diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index f4c743174fa1..d78c6e7fb173 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -22,7 +22,6 @@ #include -#include #include #include @@ -54,8 +53,6 @@ string ABIFunctions::tupleEncoder( functionName += options.toFunctionNameSuffix(); return createExternallyUsedFunction(functionName, [&]() { - solAssert(!_givenTypes.empty(), ""); - // Note that the values are in reverse due to the difference in calling semantics. Whiskers templ(R"( function (headStart ) -> tail { @@ -183,15 +180,13 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) if (_fromMemory) functionName += "_fromMemory"; - solAssert(!_types.empty(), ""); - return createExternallyUsedFunction(functionName, [&]() { TypePointers decodingTypes; for (auto const& t: _types) decodingTypes.emplace_back(t->decodingType()); Whiskers templ(R"( - function (headStart, dataEnd) -> { + function (headStart, dataEnd) { if slt(sub(dataEnd, headStart), ) { revert(0, 0) } } @@ -242,6 +237,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize(); } templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); + templ("arrow", valueReturnParams.empty() ? "" : "->"); templ("decodeElements", decodeElements); return templ.render(); @@ -250,11 +246,9 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) pair> ABIFunctions::requestedFunctions() { - string result; - for (auto const& f: m_requestedFunctions) - result += f.second; - m_requestedFunctions.clear(); - return make_pair(result, std::move(m_externallyUsedFunctions)); + std::set empty; + swap(empty, m_externallyUsedFunctions); + return make_pair(m_functionCollector->requestedFunctions(), std::move(empty)); } string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const @@ -271,6 +265,7 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const return suffix; } + string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) { string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); @@ -401,7 +396,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) FixedBytesType const& toBytesType = dynamic_cast(_to); body = Whiskers("converted := ((value))") - ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) + ("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8)) ("clean", cleanupFunction(_from)) .render(); } @@ -477,7 +472,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) if (toCategory == Type::Category::Integer) body = Whiskers("converted := ((value))") - ("shift", shiftRightFunction(256 - from.numBytes() * 8)) + ("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8)) ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) .render(); else if (toCategory == Type::Category::Address) @@ -541,40 +536,6 @@ string ABIFunctions::cleanupCombinedExternalFunctionIdFunction() }); } -string ABIFunctions::combineExternalFunctionIdFunction() -{ - string functionName = "combine_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (addr, selector) -> combined { - combined := (or((addr), and(selector, 0xffffffff))) - } - )") - ("functionName", functionName) - ("shl32", shiftLeftFunction(32)) - ("shl64", shiftLeftFunction(64)) - .render(); - }); -} - -string ABIFunctions::splitExternalFunctionIdFunction() -{ - string functionName = "split_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (combined) -> addr, selector { - combined := (combined) - selector := and(combined, 0xffffffff) - addr := (combined) - } - )") - ("functionName", functionName) - ("shr32", shiftRightFunction(32)) - ("shr64", shiftRightFunction(64)) - .render(); - }); -} - string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, @@ -655,7 +616,7 @@ string ABIFunctions::abiEncodingFunction( else cleanupConvert = conversionFunction(_from, to) + "(value)"; if (!_options.padded) - cleanupConvert = leftAlignFunction(to) + "(" + cleanupConvert + ")"; + cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; templ("cleanupConvert", cleanupConvert); } return templ.render(); @@ -745,8 +706,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArray( templ("functionName", functionName); templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); - templ("copyFun", copyToMemoryFunction(true)); - templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length"); + templ("copyFun", m_utils.copyToMemoryFunction(true)); + templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); return templ.render(); }); } @@ -816,16 +777,16 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", arrayLengthFunction(_from)); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("dataAreaFun", arrayDataAreaFunction(_from)); + templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from)); EncodingOptions subOptions(_options); subOptions.encodeFunctionFromStack = false; subOptions.padded = true; templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); - templ("nextArrayElement", nextArrayElementFunction(_from)); + templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); return templ.render(); }); } @@ -859,10 +820,10 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray( } )"); templ("functionName", functionName); - templ("lengthFun", arrayLengthFunction(_from)); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("copyFun", copyToMemoryFunction(false)); - templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length"); + templ("copyFun", m_utils.copyToMemoryFunction(false)); + templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); return templ.render(); }); } @@ -920,7 +881,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); templ("lengthPaddedShort", _options.padded ? "0x20" : "length"); templ("lengthPaddedLong", _options.padded ? "i" : "length"); - templ("arrayDataSlot", arrayDataAreaFunction(_from)); + templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from)); return templ.render(); } else @@ -961,9 +922,9 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", arrayLengthFunction(_from)); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("dataArea", arrayDataAreaFunction(_from)); + templ("dataArea", m_utils.arrayDataAreaFunction(_from)); templ("itemsPerSlot", to_string(itemsPerSlot)); // We use padded size because array elements are always padded. string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); @@ -980,7 +941,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("encodeToMemoryFun", encodeToMemoryFun); std::vector> items(itemsPerSlot); for (size_t i = 0; i < itemsPerSlot; ++i) - items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8); + items[i]["shiftRightFun"] = m_utils.shiftRightFunction(i * storageBytes * 8); templ("items", items); return templ.render(); } @@ -1066,7 +1027,7 @@ string ABIFunctions::abiEncodingFunctionStruct( members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; previousSlotOffset = storageSlotOffset; } - members.back()["retrieveValue"] = shiftRightFunction(intraSlotOffset * 8) + "(slotValue)"; + members.back()["retrieveValue"] = m_utils.shiftRightFunction(intraSlotOffset * 8) + "(slotValue)"; } else { @@ -1209,7 +1170,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType( } )") ("functionName", functionName) - ("combineExtFun", combineExternalFunctionIdFunction()) + ("combineExtFun", m_utils.combineExternalFunctionIdFunction()) .render(); }); else @@ -1251,7 +1212,6 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo if (structType->dataStoredIn(DataLocation::CallData)) { solAssert(!_fromMemory, ""); - solUnimplementedAssert(!structType->isDynamicallyEncoded(), "Dynamically encoded calldata structs are not yet implemented."); return abiDecodingFunctionCalldataStruct(*structType); } else @@ -1331,8 +1291,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); - templ("allocate", allocationFunction()); - templ("allocationSize", arrayAllocationSizeFunction(_type)); + templ("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); if (_type.isDynamicallySized()) templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); else @@ -1357,13 +1317,10 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) { - // This does not work with arrays of complex types - the array access - // is not yet implemented in Solidity. solAssert(_type.dataStoredIn(DataLocation::CallData), ""); if (!_type.isDynamicallySized()) solAssert(_type.length() < u256("0xffffffffffffffff"), ""); - if (_type.baseType()->isDynamicallyEncoded()) - solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity."); + solAssert(_type.baseType()->calldataEncodedSize() > 0, ""); solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); string functionName = @@ -1379,7 +1336,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) length := calldataload(offset) if gt(length, 0xffffffffffffffff) { revert(0, 0) } arrayPos := add(offset, 0x20) - if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(length, )), end) { revert(0, 0) } } )"; else @@ -1394,7 +1351,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) w("functionName", functionName); w("readableTypeName", _type.toString(true)); w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); - w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length())); + if (!_type.isDynamicallySized()) + w("length", toCompactHexWithPrefix(_type.length())); return w.render(); }); } @@ -1426,9 +1384,9 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ ); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); - templ("allocate", allocationFunction()); - templ("allocationSize", arrayAllocationSizeFunction(_type)); - templ("copyToMemFun", copyToMemoryFunction(!_fromMemory)); + templ("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); + templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory)); return templ.render(); }); } @@ -1480,7 +1438,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr )"); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); - templ("allocate", allocationFunction()); + templ("allocate", m_utils.allocationFunction()); solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); size_t headPos = 0; @@ -1540,7 +1498,7 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, )") ("functionName", functionName) ("load", _fromMemory ? "mload" : "calldataload") - ("splitExtFun", splitExternalFunctionIdFunction()) + ("splitExtFun", m_utils.splitExternalFunctionIdFunction()) .render(); } else @@ -1558,357 +1516,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, }); } -string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) -{ - string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; - return createFunction(functionName, [&]() { - if (_fromCalldata) - { - return Whiskers(R"( - function (src, dst, length) { - calldatacopy(dst, src, length) - // clear end - mstore(add(dst, length), 0) - } - )") - ("functionName", functionName) - .render(); - } - else - { - return Whiskers(R"( - function (src, dst, length) { - let i := 0 - for { } lt(i, length) { i := add(i, 32) } - { - mstore(add(dst, i), mload(add(src, i))) - } - if gt(i, length) - { - // clear end - mstore(add(dst, length), 0) - } - } - )") - ("functionName", functionName) - .render(); - } - }); -} - -string ABIFunctions::leftAlignFunction(Type const& _type) -{ - string functionName = string("leftAlign_") + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> aligned { - - } - )"); - templ("functionName", functionName); - switch (_type.category()) - { - case Type::Category::Address: - templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)"); - break; - case Type::Category::Integer: - { - IntegerType const& type = dynamic_cast(_type); - if (type.numBits() == 256) - templ("body", "aligned := value"); - else - templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)"); - break; - } - case Type::Category::RationalNumber: - solAssert(false, "Left align requested for rational number."); - break; - case Type::Category::Bool: - templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); - break; - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; - case Type::Category::Array: - case Type::Category::Struct: - solAssert(false, "Left align requested for non-value type."); - break; - case Type::Category::FixedBytes: - templ("body", "aligned := value"); - break; - case Type::Category::Contract: - templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)"); - break; - case Type::Category::Enum: - { - unsigned storageBytes = dynamic_cast(_type).storageBytes(); - templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)"); - break; - } - case Type::Category::InaccessibleDynamic: - solAssert(false, "Left align requested for inaccessible dynamic type."); - break; - default: - solAssert(false, "Left align of type " + _type.identifier() + " requested."); - } - - return templ.render(); - }); -} - -string ABIFunctions::shiftLeftFunction(size_t _numBits) -{ - solAssert(_numBits < 256, ""); - - string functionName = "shift_left_" + to_string(_numBits); - if (m_evmVersion.hasBitwiseShifting()) - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shl(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := mul(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } -} - -string ABIFunctions::shiftRightFunction(size_t _numBits) -{ - solAssert(_numBits < 256, ""); - - // Note that if this is extended with signed shifts, - // the opcodes SAR and SDIV behave differently with regards to rounding! - - string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; - if (m_evmVersion.hasBitwiseShifting()) - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shr(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := div(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } -} - -string ABIFunctions::roundUpFunction() -{ - string functionName = "round_up_to_mul_of_32"; - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> result { - result := and(add(value, 31), not(31)) - } - )") - ("functionName", functionName) - .render(); - }); -} - -string ABIFunctions::arrayLengthFunction(ArrayType const& _type) -{ - string functionName = "array_length_" + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers w(R"( - function (value) -> length { - - } - )"); - w("functionName", functionName); - string body; - if (!_type.isDynamicallySized()) - body = "length := " + toCompactHexWithPrefix(_type.length()); - else - { - switch (_type.location()) - { - case DataLocation::CallData: - solAssert(false, "called regular array length function on calldata array"); - break; - case DataLocation::Memory: - body = "length := mload(value)"; - break; - case DataLocation::Storage: - if (_type.isByteArray()) - { - // Retrieve length both for in-place strings and off-place strings: - // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 - // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it - // computes (x & (-1)) / 2, which is equivalent to just x / 2. - body = R"( - length := sload(value) - let mask := sub(mul(0x100, iszero(and(length, 1))), 1) - length := div(and(length, mask), 2) - )"; - } - else - body = "length := sload(value)"; - break; - } - } - solAssert(!body.empty(), ""); - w("body", body); - return w.render(); - }); -} - -string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) -{ - solAssert(_type.dataStoredIn(DataLocation::Memory), ""); - string functionName = "array_allocation_size_" + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers w(R"( - function (length) -> size { - // Make sure we can allocate memory without overflow - if gt(length, 0xffffffffffffffff) { revert(0, 0) } - size := - - } - )"); - w("functionName", functionName); - if (_type.isByteArray()) - // Round up - w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); - else - w("allocationSize", "mul(length, 0x20)"); - if (_type.isDynamicallySized()) - w("addLengthSlot", "size := add(size, 0x20)"); - else - w("addLengthSlot", ""); - return w.render(); - }); -} - -string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) -{ - string functionName = "array_dataslot_" + _type.identifier(); - return createFunction(functionName, [&]() { - if (_type.dataStoredIn(DataLocation::Memory)) - { - if (_type.isDynamicallySized()) - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := add(memPtr, 0x20) - } - )") - ("functionName", functionName) - .render(); - else - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := memPtr - } - )") - ("functionName", functionName) - .render(); - } - else if (_type.dataStoredIn(DataLocation::Storage)) - { - if (_type.isDynamicallySized()) - { - Whiskers w(R"( - function (slot) -> dataSlot { - mstore(0, slot) - dataSlot := keccak256(0, 0x20) - } - )"); - w("functionName", functionName); - return w.render(); - } - else - { - Whiskers w(R"( - function (slot) -> dataSlot { - dataSlot := slot - } - )"); - w("functionName", functionName); - return w.render(); - } - } - else - { - // Not used for calldata - solAssert(false, ""); - } - }); -} - -string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) -{ - solAssert(!_type.isByteArray(), ""); - solAssert( - _type.location() == DataLocation::Memory || - _type.location() == DataLocation::Storage, - "" - ); - solAssert( - _type.location() == DataLocation::Memory || - _type.baseType()->storageBytes() > 16, - "" - ); - string functionName = "array_nextElement_" + _type.identifier(); - return createFunction(functionName, [&]() { - if (_type.location() == DataLocation::Memory) - return Whiskers(R"( - function (memPtr) -> nextPtr { - nextPtr := add(memPtr, 0x20) - } - )") - ("functionName", functionName) - .render(); - else if (_type.location() == DataLocation::Storage) - return Whiskers(R"( - function (slot) -> nextSlot { - nextSlot := add(slot, 1) - } - )") - ("functionName", functionName) - .render(); - else - solAssert(false, ""); - }); -} - string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) { string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); @@ -1933,34 +1540,9 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, }); } -string ABIFunctions::allocationFunction() -{ - string functionName = "allocateMemory"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (size) -> memPtr { - memPtr := mload() - let newFreePtr := add(memPtr, size) - // protect against overflow - if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } - mstore(, newFreePtr) - } - )") - ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) - ("functionName", functionName) - .render(); - }); -} - string ABIFunctions::createFunction(string const& _name, function const& _creator) { - if (!m_requestedFunctions.count(_name)) - { - auto fun = _creator(); - solAssert(!fun.empty(), ""); - m_requestedFunctions[_name] = fun; - } - return _name; + return m_functionCollector->createFunction(_name, _creator); } string ABIFunctions::createExternallyUsedFunction(string const& _name, function const& _creator) diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 654afc03427a..c1d88d68dc92 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -22,7 +22,9 @@ #pragma once -#include +#include +#include + #include #include @@ -30,8 +32,10 @@ #include #include -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ class Type; class ArrayType; @@ -40,17 +44,25 @@ class FunctionType; using TypePointer = std::shared_ptr; using TypePointers = std::vector; -/// -/// Class to generate encoding and decoding functions. Also maintains a collection -/// of "functions to be generated" in order to avoid generating the same function -/// multiple times. -/// -/// Make sure to include the result of ``requestedFunctions()`` to a block that -/// is visible from the code that was generated here, or use named labels. +/** + * Class to generate encoding and decoding functions. Also maintains a collection + * of "functions to be generated" in order to avoid generating the same function + * multiple times. + * + * Make sure to include the result of ``requestedFunctions()`` to a block that + * is visible from the code that was generated here, or use named labels. + */ class ABIFunctions { public: - explicit ABIFunctions(langutil::EVMVersion _evmVersion = langutil::EVMVersion{}) : m_evmVersion(_evmVersion) {} + explicit ABIFunctions( + langutil::EVMVersion _evmVersion, + std::shared_ptr _functionCollector = std::make_shared() + ): + m_evmVersion(_evmVersion), + m_functionCollector(std::move(_functionCollector)), + m_utils(_evmVersion, m_functionCollector) + {} /// @returns name of an assembly function to ABI-encode values of @a _givenTypes /// into memory, converting the types to @a _targetTypes on the fly. @@ -129,14 +141,6 @@ class ABIFunctions std::string cleanupCombinedExternalFunctionIdFunction(); - /// @returns a function that combines the address and selector to a single value - /// for use in the ABI. - std::string combineExternalFunctionIdFunction(); - - /// @returns a function that splits the address and selector from a single value - /// for use in the ABI. - std::string splitExternalFunctionIdFunction(); - /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. /// @param _fromStack if false, the input value was just loaded from storage @@ -229,34 +233,6 @@ class ABIFunctions /// Part of @a abiDecodingFunction for array types. std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); - /// @returns a function that copies raw bytes of dynamic length from calldata - /// or memory to memory. - /// Pads with zeros and might write more than exactly length. - std::string copyToMemoryFunction(bool _fromCalldata); - - /// @returns the name of a function that takes a (cleaned) value of the given value type and - /// left-aligns it, usually for use in non-padded encoding. - std::string leftAlignFunction(Type const& _type); - - std::string shiftLeftFunction(size_t _numBits); - std::string shiftRightFunction(size_t _numBits); - /// @returns the name of a function that rounds its input to the next multiple - /// of 32 or the input if it is a multiple of 32. - std::string roundUpFunction(); - - std::string arrayLengthFunction(ArrayType const& _type); - /// @returns the name of a function that computes the number of bytes required - /// to store an array in memory given its length (internally encoded, not ABI encoded). - /// The function reverts for too large lengths. - std::string arrayAllocationSizeFunction(ArrayType const& _type); - /// @returns the name of a function that converts a storage slot number - /// or a memory pointer to the slot number / memory pointer for the data position of an array - /// which is stored in that slot / memory area. - std::string arrayDataAreaFunction(ArrayType const& _type); - /// @returns the name of a function that advances an array data pointer to the next element. - /// Only works for memory arrays and storage arrays that store one item per slot. - std::string nextArrayElementFunction(ArrayType const& _type); - /// @returns the name of a function used during encoding that stores the length /// if the array is dynamically sized (and the options do not request in-place encoding). /// It returns the new encoding position. @@ -264,12 +240,6 @@ class ABIFunctions /// does nothing and just returns the position again. std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options); - /// @returns the name of a function that allocates memory. - /// Modifies the "free memory pointer" - /// Arguments: size - /// Return value: pointer - std::string allocationFunction(); - /// Helper function that uses @a _creator to create a function and add it to /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both /// cases. @@ -283,10 +253,10 @@ class ABIFunctions /// @returns the size of the static part of the encoding of the given types. static size_t headSize(TypePointers const& _targetTypes); - /// Map from function name to code for a multi-use function. - std::map m_requestedFunctions; - std::set m_externallyUsedFunctions; langutil::EVMVersion m_evmVersion; + std::shared_ptr m_functionCollector; + std::set m_externallyUsedFunctions; + YulUtilFunctions m_utils; }; } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 39189046ffa8..5c4a65323027 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -1030,7 +1030,7 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept } } -void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) const +void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const { /// Stack: reference [length] index DataLocation location = _arrayType.location(); @@ -1050,28 +1050,41 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c m_context << Instruction::SWAP1 << Instruction::POP; // stack: - m_context << Instruction::SWAP1; - // stack: switch (location) { case DataLocation::Memory: case DataLocation::CallData: - if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) - m_context << u256(32) << Instruction::ADD; - if (!_arrayType.isByteArray()) { - m_context << Instruction::SWAP1; if (location == DataLocation::CallData) - m_context << _arrayType.baseType()->calldataEncodedSize(); + { + if (_arrayType.baseType()->isDynamicallyEncoded()) + m_context << u256(0x20); + else + m_context << _arrayType.baseType()->calldataEncodedSize(); + } else m_context << u256(_arrayType.memoryHeadSize()); m_context << Instruction::MUL; } + // stack: + + if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) + m_context << u256(32) << Instruction::ADD; + + if (_keepReference) + m_context << Instruction::DUP2; + m_context << Instruction::ADD; break; case DataLocation::Storage: { + if (_keepReference) + m_context << Instruction::DUP2; + else + m_context << Instruction::SWAP1; + // stack: [] + eth::AssemblyItem endTag = m_context.newTag(); if (_arrayType.isByteArray()) { diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index daf50bf52944..af8e3e96615a 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -97,11 +97,13 @@ class ArrayUtils /// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef. /// If @a _arrayType is a byte array, takes tight coding into account. void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const; - /// Performs bounds checking and returns a reference on the stack. + /// Checks whether the index is out of range and returns the absolute offset of the element reference[index] + /// (i.e. reference + index * size_of_base_type). + /// If @a _keepReference is true, the base reference to the beginning of the array is kept on the stack. /// Stack pre: reference [length] index - /// Stack post (storage): storage_slot byte_offset - /// Stack post: memory/calldata_offset - void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; + /// Stack post (storage): [reference] storage_slot byte_offset + /// Stack post: [reference] memory/calldata_offset + void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const; private: /// Adds the given number of bytes to a storage byte offset counter and also increments diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index b32e744b1bbf..d007aaf7ce69 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -97,6 +97,53 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType) m_context << Instruction::REVERT; } +void CompilerUtils::accessCalldataTail(Type const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + + unsigned int baseEncodedSize = _type.calldataEncodedSize(); + solAssert(baseEncodedSize > 1, ""); + + // returns the absolute offset of the tail in "base_ref" + m_context.appendInlineAssembly(Whiskers(R"({ + let rel_offset_of_tail := calldataload(ptr_to_tail) + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { revert(0, 0) } + base_ref := add(base_ref, rel_offset_of_tail) + })")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"}); + // stack layout: + + if (!_type.isDynamicallySized()) + { + m_context << Instruction::POP; + // stack layout: + solAssert( + _type.category() == Type::Category::Struct || + _type.category() == Type::Category::Array, + "Invalid dynamically encoded base type on tail access." + ); + } + else + { + auto const* arrayType = dynamic_cast(&_type); + solAssert(!!arrayType, "Invalid dynamically sized type."); + unsigned int calldataStride = arrayType->calldataStride(); + solAssert(calldataStride > 0, ""); + + // returns the absolute offset of the tail in "base_ref" + // and the length of the tail in "length" + m_context.appendInlineAssembly( + Whiskers(R"({ + length := calldataload(base_ref) + base_ref := add(base_ref, 0x20) + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } + })")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(), + {"base_ref", "length"} + ); + // stack layout: + } +} + unsigned CompilerUtils::loadFromMemory( unsigned _offset, Type const& _type, diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 5e07e7bb4358..568d2c9a7a78 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -65,6 +65,13 @@ class CompilerUtils /// Stack post: void revertWithStringData(Type const& _argumentType); + /// Computes the absolute calldata offset of a tail given a base reference and the (absolute) + /// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also + /// returns the array length on the stack. + /// Stack pre: base_ref tail_ptr + /// Stack post: tail_ref [length] + void accessCalldataTail(Type const& _type); + /// Loads data from memory to the stack. /// @param _offset offset in memory (or calldata) /// @param _type data type to load diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a72b31dfe019..3ed4b702fcbb 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1424,20 +1424,28 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) } case DataLocation::CallData: { - solUnimplementedAssert(!type.isDynamicallyEncoded(), ""); - m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; - // For non-value types the calldata offset is returned directly. - if (_memberAccess.annotation().type->isValueType()) + if (_memberAccess.annotation().type->isDynamicallyEncoded()) { - solAssert(_memberAccess.annotation().type->calldataEncodedSize(false) > 0, ""); - CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false); + m_context << Instruction::DUP1; + m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; + CompilerUtils(m_context).accessCalldataTail(*_memberAccess.annotation().type); } else - solAssert( - _memberAccess.annotation().type->category() == Type::Category::Array || - _memberAccess.annotation().type->category() == Type::Category::Struct, - "" - ); + { + m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; + // For non-value types the calldata offset is returned directly. + if (_memberAccess.annotation().type->isValueType()) + { + solAssert(_memberAccess.annotation().type->calldataEncodedSize() > 0, ""); + CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false); + } + else + solAssert( + _memberAccess.annotation().type->category() == Type::Category::Array || + _memberAccess.annotation().type->category() == Type::Category::Struct, + "" + ); + } break; } default: @@ -1551,10 +1559,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) _indexAccess.indexExpression()->accept(*this); utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true); // stack layout: [] - ArrayUtils(m_context).accessIndex(arrayType); switch (arrayType.location()) { case DataLocation::Storage: + ArrayUtils(m_context).accessIndex(arrayType); if (arrayType.isByteArray()) { solAssert(!arrayType.isString(), "Index access to string is not allowed."); @@ -1564,18 +1572,36 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) setLValueToStorageItem(_indexAccess); break; case DataLocation::Memory: + ArrayUtils(m_context).accessIndex(arrayType); setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); break; case DataLocation::CallData: - //@todo if we implement this, the value in calldata has to be added to the base offset - solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); - if (arrayType.baseType()->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic( - *arrayType.baseType(), - true, - !arrayType.isByteArray(), - false - ); + if (arrayType.baseType()->isDynamicallyEncoded()) + { + // stack layout: + ArrayUtils(m_context).accessIndex(arrayType, true, true); + // stack layout: + + CompilerUtils(m_context).accessCalldataTail(*arrayType.baseType()); + // stack layout: [length] + } + else + { + ArrayUtils(m_context).accessIndex(arrayType, true); + if (arrayType.baseType()->isValueType()) + CompilerUtils(m_context).loadFromMemoryDynamic( + *arrayType.baseType(), + true, + !arrayType.isByteArray(), + false + ); + else + solAssert( + arrayType.baseType()->category() == Type::Category::Struct || + arrayType.baseType()->category() == Type::Category::Array, + "Invalid statically sized non-value base type on array access." + ); + } break; } } diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.cpp b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp new file mode 100644 index 000000000000..9dc66cea2174 --- /dev/null +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp @@ -0,0 +1,52 @@ +/* + 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 . +*/ +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ + +#include + +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +string MultiUseYulFunctionCollector::requestedFunctions() +{ + string result; + for (auto const& f: m_requestedFunctions) + result += f.second; + m_requestedFunctions.clear(); + return result; +} + +string MultiUseYulFunctionCollector::createFunction(string const& _name, function const& _creator) +{ + if (!m_requestedFunctions.count(_name)) + { + string fun = _creator(); + solAssert(!fun.empty(), ""); + solAssert(fun.find("function " + _name) != string::npos, "Function not properly named."); + m_requestedFunctions[_name] = std::move(fun); + } + return _name; +} diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.h b/libsolidity/codegen/MultiUseYulFunctionCollector.h new file mode 100644 index 000000000000..321474f90985 --- /dev/null +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.h @@ -0,0 +1,56 @@ +/* + 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 . +*/ +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ +class MultiUseYulFunctionCollector +{ +public: + /// Helper function that uses @a _creator to create a function and add it to + /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both + /// cases. + std::string createFunction(std::string const& _name, std::function const& _creator); + + /// @returns concatenation of all generated functions. + /// Clears the internal list, i.e. calling it again will result in an + /// empty return value. + std::string requestedFunctions(); + +private: + /// Map from function name to code for a multi-use function. + std::map m_requestedFunctions; +}; + +} +} diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp new file mode 100644 index 000000000000..489980f967af --- /dev/null +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -0,0 +1,438 @@ +/* + 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 . +*/ +/** + * Component that can generate various useful Yul functions. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +string YulUtilFunctions::combineExternalFunctionIdFunction() +{ + string functionName = "combine_external_function_id"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (addr, selector) -> combined { + combined := (or((addr), and(selector, 0xffffffff))) + } + )") + ("functionName", functionName) + ("shl32", shiftLeftFunction(32)) + ("shl64", shiftLeftFunction(64)) + .render(); + }); +} + +string YulUtilFunctions::splitExternalFunctionIdFunction() +{ + string functionName = "split_external_function_id"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (combined) -> addr, selector { + combined := (combined) + selector := and(combined, 0xffffffff) + addr := (combined) + } + )") + ("functionName", functionName) + ("shr32", shiftRightFunction(32)) + ("shr64", shiftRightFunction(64)) + .render(); + }); +} + +string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata) +{ + string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; + return m_functionCollector->createFunction(functionName, [&]() { + if (_fromCalldata) + { + return Whiskers(R"( + function (src, dst, length) { + calldatacopy(dst, src, length) + // clear end + mstore(add(dst, length), 0) + } + )") + ("functionName", functionName) + .render(); + } + else + { + return Whiskers(R"( + function (src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + if gt(i, length) + { + // clear end + mstore(add(dst, length), 0) + } + } + )") + ("functionName", functionName) + .render(); + } + }); +} + +string YulUtilFunctions::leftAlignFunction(Type const& _type) +{ + string functionName = string("leftAlign_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> aligned { + + } + )"); + templ("functionName", functionName); + switch (_type.category()) + { + case Type::Category::Address: + templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)"); + break; + case Type::Category::Integer: + { + IntegerType const& type = dynamic_cast(_type); + if (type.numBits() == 256) + templ("body", "aligned := value"); + else + templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)"); + break; + } + case Type::Category::RationalNumber: + solAssert(false, "Left align requested for rational number."); + break; + case Type::Category::Bool: + templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); + break; + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + case Type::Category::Struct: + solAssert(false, "Left align requested for non-value type."); + break; + case Type::Category::FixedBytes: + templ("body", "aligned := value"); + break; + case Type::Category::Contract: + templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)"); + break; + case Type::Category::Enum: + { + unsigned storageBytes = dynamic_cast(_type).storageBytes(); + templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)"); + break; + } + case Type::Category::InaccessibleDynamic: + solAssert(false, "Left align requested for inaccessible dynamic type."); + break; + default: + solAssert(false, "Left align of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string YulUtilFunctions::shiftLeftFunction(size_t _numBits) +{ + solAssert(_numBits < 256, ""); + + string functionName = "shift_left_" + to_string(_numBits); + if (m_evmVersion.hasBitwiseShifting()) + { + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := shl(, value) + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + .render(); + }); + } + else + { + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := mul(value, ) + } + )") + ("functionName", functionName) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); + } +} + +string YulUtilFunctions::shiftRightFunction(size_t _numBits) +{ + solAssert(_numBits < 256, ""); + + // Note that if this is extended with signed shifts, + // the opcodes SAR and SDIV behave differently with regards to rounding! + + string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; + if (m_evmVersion.hasBitwiseShifting()) + { + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := shr(, value) + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + .render(); + }); + } + else + { + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := div(value, ) + } + )") + ("functionName", functionName) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); + } +} + +string YulUtilFunctions::roundUpFunction() +{ + string functionName = "round_up_to_mul_of_32"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> result { + result := and(add(value, 31), not(31)) + } + )") + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) +{ + string functionName = "array_length_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers w(R"( + function (value) -> length { + + } + )"); + w("functionName", functionName); + string body; + if (!_type.isDynamicallySized()) + body = "length := " + toCompactHexWithPrefix(_type.length()); + else + { + switch (_type.location()) + { + case DataLocation::CallData: + solAssert(false, "called regular array length function on calldata array"); + break; + case DataLocation::Memory: + body = "length := mload(value)"; + break; + case DataLocation::Storage: + if (_type.isByteArray()) + { + // Retrieve length both for in-place strings and off-place strings: + // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 + // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it + // computes (x & (-1)) / 2, which is equivalent to just x / 2. + body = R"( + length := sload(value) + let mask := sub(mul(0x100, iszero(and(length, 1))), 1) + length := div(and(length, mask), 2) + )"; + } + else + body = "length := sload(value)"; + break; + } + } + solAssert(!body.empty(), ""); + w("body", body); + return w.render(); + }); +} + +string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + string functionName = "array_allocation_size_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers w(R"( + function (length) -> size { + // Make sure we can allocate memory without overflow + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + size := + + } + )"); + w("functionName", functionName); + if (_type.isByteArray()) + // Round up + w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); + else + w("allocationSize", "mul(length, 0x20)"); + if (_type.isDynamicallySized()) + w("addLengthSlot", "size := add(size, 0x20)"); + else + w("addLengthSlot", ""); + return w.render(); + }); +} + +string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) +{ + string functionName = "array_dataslot_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + if (_type.dataStoredIn(DataLocation::Memory)) + { + if (_type.isDynamicallySized()) + return Whiskers(R"( + function (memPtr) -> dataPtr { + dataPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + else + return Whiskers(R"( + function (memPtr) -> dataPtr { + dataPtr := memPtr + } + )") + ("functionName", functionName) + .render(); + } + else if (_type.dataStoredIn(DataLocation::Storage)) + { + if (_type.isDynamicallySized()) + { + Whiskers w(R"( + function (slot) -> dataSlot { + mstore(0, slot) + dataSlot := keccak256(0, 0x20) + } + )"); + w("functionName", functionName); + return w.render(); + } + else + { + Whiskers w(R"( + function (slot) -> dataSlot { + dataSlot := slot + } + )"); + w("functionName", functionName); + return w.render(); + } + } + else + { + // Not used for calldata + solAssert(false, ""); + } + }); +} + +string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) +{ + solAssert(!_type.isByteArray(), ""); + solAssert( + _type.location() == DataLocation::Memory || + _type.location() == DataLocation::Storage, + "" + ); + solAssert( + _type.location() == DataLocation::Memory || + _type.baseType()->storageBytes() > 16, + "" + ); + string functionName = "array_nextElement_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + if (_type.location() == DataLocation::Memory) + return Whiskers(R"( + function (memPtr) -> nextPtr { + nextPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + else if (_type.location() == DataLocation::Storage) + return Whiskers(R"( + function (slot) -> nextSlot { + nextSlot := add(slot, 1) + } + )") + ("functionName", functionName) + .render(); + else + solAssert(false, ""); + }); +} + +string YulUtilFunctions::allocationFunction() +{ + string functionName = "allocateMemory"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (size) -> memPtr { + memPtr := mload() + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(, newFreePtr) + } + )") + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("functionName", functionName) + .render(); + }); +} + diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h new file mode 100644 index 000000000000..1a21ece2b934 --- /dev/null +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -0,0 +1,100 @@ +/* + 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 . +*/ +/** + * Component that can generate various useful Yul functions. + */ + +#pragma once + +#include + +#include + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class Type; +class ArrayType; + +/** + * Component that can generate various useful Yul functions. + */ +class YulUtilFunctions +{ +public: + explicit YulUtilFunctions( + langutil::EVMVersion _evmVersion, + std::shared_ptr _functionCollector + ): + m_evmVersion(_evmVersion), + m_functionCollector(std::move(_functionCollector)) + {} + + /// @returns a function that combines the address and selector to a single value + /// for use in the ABI. + std::string combineExternalFunctionIdFunction(); + + /// @returns a function that splits the address and selector from a single value + /// for use in the ABI. + std::string splitExternalFunctionIdFunction(); + + /// @returns a function that copies raw bytes of dynamic length from calldata + /// or memory to memory. + /// Pads with zeros and might write more than exactly length. + std::string copyToMemoryFunction(bool _fromCalldata); + + /// @returns the name of a function that takes a (cleaned) value of the given value type and + /// left-aligns it, usually for use in non-padded encoding. + std::string leftAlignFunction(Type const& _type); + + std::string shiftLeftFunction(size_t _numBits); + std::string shiftRightFunction(size_t _numBits); + /// @returns the name of a function that rounds its input to the next multiple + /// of 32 or the input if it is a multiple of 32. + std::string roundUpFunction(); + + std::string arrayLengthFunction(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required + /// to store an array in memory given its length (internally encoded, not ABI encoded). + /// The function reverts for too large lengths. + std::string arrayAllocationSizeFunction(ArrayType const& _type); + /// @returns the name of a function that converts a storage slot number + /// or a memory pointer to the slot number / memory pointer for the data position of an array + /// which is stored in that slot / memory area. + std::string arrayDataAreaFunction(ArrayType const& _type); + /// @returns the name of a function that advances an array data pointer to the next element. + /// Only works for memory arrays and storage arrays that store one item per slot. + std::string nextArrayElementFunction(ArrayType const& _type); + + /// @returns the name of a function that allocates memory. + /// Modifies the "free memory pointer" + /// Arguments: size + /// Return value: pointer + std::string allocationFunction(); + +private: + langutil::EVMVersion m_evmVersion; + std::shared_ptr m_functionCollector; +}; + +} +} diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 1f6e853562f3..d9e2340214d5 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -374,6 +374,9 @@ void SMTChecker::checkOverflow(OverflowTarget& _target) void SMTChecker::endVisit(UnaryOperation const& _op) { + if (_op.annotation().type->category() == Type::Category::RationalNumber) + return; + switch (_op.getOperator()) { case Token::Not: // ! @@ -431,8 +434,21 @@ void SMTChecker::endVisit(UnaryOperation const& _op) } } +bool SMTChecker::visit(UnaryOperation const& _op) +{ + return !shortcutRationalNumber(_op); +} + +bool SMTChecker::visit(BinaryOperation const& _op) +{ + return !shortcutRationalNumber(_op); +} + void SMTChecker::endVisit(BinaryOperation const& _op) { + if (_op.annotation().type->category() == Type::Category::RationalNumber) + return; + if (TokenTraits::isArithmeticOp(_op.getOperator())) arithmeticOperation(_op); else if (TokenTraits::isCompareOp(_op.getOperator())) @@ -535,13 +551,6 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) m_interface->addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); } -void SMTChecker::eraseArrayKnowledge() -{ - for (auto const& var: m_variables) - if (var.first->annotation().type->category() == Type::Category::Mapping) - newValue(*var.first); -} - void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) { FunctionDefinition const* _funDef = nullptr; @@ -698,17 +707,26 @@ void SMTChecker::endVisit(Literal const& _literal) solAssert(_literal.annotation().type, "Expected type for AST node"); Type const& type = *_literal.annotation().type; if (isNumber(type.category())) - defineExpr(_literal, smt::Expression(type.literalValue(&_literal))); else if (isBool(type.category())) defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); else + { + if (type.category() == Type::Category::StringLiteral) + { + auto stringType = make_shared(DataLocation::Memory, true); + auto stringLit = dynamic_cast(_literal.annotation().type.get()); + solAssert(stringLit, ""); + auto result = newSymbolicVariable(*stringType, stringLit->richIdentifier(), *m_interface); + m_expressions.emplace(&_literal, result.second); + } m_errorReporter.warning( _literal.location(), "Assertion checker does not yet support the type of this literal (" + _literal.annotation().type->toString() + ")." ); + } } void SMTChecker::endVisit(Return const& _return) @@ -734,9 +752,9 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) auto const& exprType = _memberAccess.expression().annotation().type; solAssert(exprType, ""); + auto identifier = dynamic_cast(&_memberAccess.expression()); if (exprType->category() == Type::Category::Magic) { - auto identifier = dynamic_cast(&_memberAccess.expression()); string accessedName; if (identifier) accessedName = identifier->name(); @@ -748,12 +766,23 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) defineGlobalVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess); return false; } + else if (exprType->category() == Type::Category::TypeType) + { + if (identifier && dynamic_cast(identifier->annotation().referencedDeclaration)) + { + auto enumType = dynamic_cast(accessType.get()); + solAssert(enumType, ""); + defineExpr(_memberAccess, enumType->memberValue(_memberAccess.memberName())); + } + return false; + } else m_errorReporter.warning( _memberAccess.location(), "Assertion checker does not yet support this expression." ); + createExpr(_memberAccess); return true; } @@ -796,7 +825,6 @@ void SMTChecker::endVisit(IndexAccess const& _indexAccess) void SMTChecker::arrayAssignment() { m_arrayAssignmentHappened = true; - eraseArrayKnowledge(); } void SMTChecker::arrayIndexAssignment(Assignment const& _assignment) @@ -806,12 +834,48 @@ void SMTChecker::arrayIndexAssignment(Assignment const& _assignment) { auto const& varDecl = dynamic_cast(*id->annotation().referencedDeclaration); solAssert(knownVariable(varDecl), ""); + + if (varDecl.hasReferenceOrMappingType()) + resetVariables([&](VariableDeclaration const& _var) { + if (_var == varDecl) + return false; + TypePointer prefix = _var.type(); + TypePointer originalType = typeWithoutPointer(varDecl.type()); + while ( + prefix->category() == Type::Category::Mapping || + prefix->category() == Type::Category::Array + ) + { + if (*originalType == *typeWithoutPointer(prefix)) + return true; + if (prefix->category() == Type::Category::Mapping) + { + auto mapPrefix = dynamic_cast(prefix.get()); + solAssert(mapPrefix, ""); + prefix = mapPrefix->valueType(); + } + else + { + auto arrayPrefix = dynamic_cast(prefix.get()); + solAssert(arrayPrefix, ""); + prefix = arrayPrefix->baseType(); + } + } + return false; + }); + smt::Expression store = smt::Expression::store( m_variables[&varDecl]->currentValue(), expr(*indexAccess.indexExpression()), expr(_assignment.rightHandSide()) ); m_interface->addAssertion(newValue(varDecl) == store); + // Update the SMT select value after the assignment, + // necessary for sound models. + defineExpr(indexAccess, smt::Expression::select( + m_variables[&varDecl]->currentValue(), + expr(*indexAccess.indexExpression()) + )); } else if (dynamic_cast(&indexAccess.baseExpression())) m_errorReporter.warning( @@ -860,6 +924,21 @@ void SMTChecker::defineGlobalFunction(string const& _name, Expression const& _ex } } +bool SMTChecker::shortcutRationalNumber(Expression const& _expr) +{ + if (_expr.annotation().type->category() == Type::Category::RationalNumber) + { + auto rationalType = dynamic_cast(_expr.annotation().type.get()); + solAssert(rationalType, ""); + if (rationalType->isNegative()) + defineExpr(_expr, smt::Expression(u2s(rationalType->literalValue(nullptr)))); + else + defineExpr(_expr, smt::Expression(rationalType->literalValue(nullptr))); + return true; + } + return false; +} + void SMTChecker::arithmeticOperation(BinaryOperation const& _op) { switch (_op.getOperator()) @@ -1310,6 +1389,13 @@ void SMTChecker::resetVariables(function const }); } +TypePointer SMTChecker::typeWithoutPointer(TypePointer const& _type) +{ + if (auto refType = dynamic_cast(_type.get())) + return ReferenceType::copyForLocationIfReference(refType->location(), _type); + return _type; +} + void SMTChecker::mergeVariables(vector const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) { set uniqueVars(_variables.begin(), _variables.end()); diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index ccb7e1e4d89d..4039166d7e52 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -71,7 +71,9 @@ class SMTChecker: private ASTConstVisitor void endVisit(VariableDeclarationStatement const& _node) override; void endVisit(Assignment const& _node) override; void endVisit(TupleExpression const& _node) override; + bool visit(UnaryOperation const& _node) override; void endVisit(UnaryOperation const& _node) override; + bool visit(BinaryOperation const& _node) override; void endVisit(BinaryOperation const& _node) override; void endVisit(FunctionCall const& _node) override; void endVisit(Identifier const& _node) override; @@ -80,6 +82,9 @@ class SMTChecker: private ASTConstVisitor bool visit(MemberAccess const& _node) override; void endVisit(IndexAccess const& _node) override; + /// Do not visit subtree if node is a RationalNumber. + /// Symbolic _expr is the rational literal. + bool shortcutRationalNumber(Expression const& _expr); void arithmeticOperation(BinaryOperation const& _op); void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); @@ -103,8 +108,6 @@ class SMTChecker: private ASTConstVisitor void arrayAssignment(); /// Handles assignment to SMT array index. void arrayIndexAssignment(Assignment const& _assignment); - /// Erases information about SMT arrays. - void eraseArrayKnowledge(); /// Division expression in the given type. Requires special treatment because /// of rounding for signed division. @@ -177,6 +180,9 @@ class SMTChecker: private ASTConstVisitor void resetStorageReferences(); void resetVariables(std::vector _variables); void resetVariables(std::function const& _filter); + /// @returns the type without storage pointer information if it has it. + TypePointer typeWithoutPointer(TypePointer const& _type); + /// Given two different branches and the touched variables, /// merge the touched variables into after-branch ite variables /// using the branch condition as guard. diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 80b48e0f6749..042a16ab6a2a 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -118,6 +118,7 @@ class Expression explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {} Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {} Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {} + Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {} Expression(bigint const& _number): Expression(_number.str(), Kind::Int) {} Expression(Expression const&) = default; diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 269bff73854f..e72e22339c69 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -57,8 +57,13 @@ smt::SortPointer dev::solidity::smtSort(Type const& _type) solAssert(mapType, ""); return make_shared(smtSort(*mapType->keyType()), smtSort(*mapType->valueType())); } - // TODO Solidity array - return make_shared(smt::Kind::Int); + else + { + solAssert(isArray(_type.category()), ""); + auto arrayType = dynamic_cast(&_type); + solAssert(arrayType, ""); + return make_shared(make_shared(smt::Kind::Int), smtSort(*arrayType->baseType())); + } } default: // Abstract case. @@ -82,7 +87,7 @@ smt::Kind dev::solidity::smtKind(Type::Category _category) return smt::Kind::Bool; else if (isFunction(_category)) return smt::Kind::Function; - else if (isMapping(_category)) + else if (isMapping(_category) || isArray(_category)) return smt::Kind::Array; // Abstract case. return smt::Kind::Int; @@ -92,7 +97,8 @@ bool dev::solidity::isSupportedType(Type::Category _category) { return isNumber(_category) || isBool(_category) || - isMapping(_category); + isMapping(_category) || + isArray(_category); } bool dev::solidity::isSupportedTypeDeclaration(Type::Category _category) @@ -129,6 +135,8 @@ pair> dev::solidity::newSymbolicVariable( } else if (isAddress(_type.category())) var = make_shared(_uniqueName, _solver); + else if (isEnum(_type.category())) + var = make_shared(type, _uniqueName, _solver); else if (isRational(_type.category())) { auto rational = dynamic_cast(&_type); @@ -140,6 +148,8 @@ pair> dev::solidity::newSymbolicVariable( } else if (isMapping(_type.category())) var = make_shared(type, _uniqueName, _solver); + else if (isArray(_type.category())) + var = make_shared(type, _uniqueName, _solver); else solAssert(false, ""); return make_pair(abstract, var); @@ -175,12 +185,18 @@ bool dev::solidity::isAddress(Type::Category _category) return _category == Type::Category::Address; } +bool dev::solidity::isEnum(Type::Category _category) +{ + return _category == Type::Category::Enum; +} + bool dev::solidity::isNumber(Type::Category _category) { return isInteger(_category) || isRational(_category) || isFixedBytes(_category) || - isAddress(_category); + isAddress(_category) || + isEnum(_category); } bool dev::solidity::isBool(Type::Category _category) @@ -198,6 +214,11 @@ bool dev::solidity::isMapping(Type::Category _category) return _category == Type::Category::Mapping; } +bool dev::solidity::isArray(Type::Category _category) +{ + return _category == Type::Category::Array; +} + smt::Expression dev::solidity::minValue(IntegerType const& _type) { return smt::Expression(_type.minValue()); @@ -228,7 +249,14 @@ void dev::solidity::smt::setSymbolicUnknownValue(SymbolicVariable const& _variab void dev::solidity::smt::setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface) { - if (isInteger(_type->category())) + if (isEnum(_type->category())) + { + auto enumType = dynamic_cast(_type.get()); + solAssert(enumType, ""); + _interface.addAssertion(_expr >= 0); + _interface.addAssertion(_expr < enumType->numberOfMembers()); + } + else if (isInteger(_type->category())) { auto intType = dynamic_cast(_type.get()); solAssert(intType, ""); diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 35c7bb8dfa96..23a0e93d69cd 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -44,10 +44,12 @@ bool isInteger(Type::Category _category); bool isRational(Type::Category _category); bool isFixedBytes(Type::Category _category); bool isAddress(Type::Category _category); +bool isEnum(Type::Category _category); bool isNumber(Type::Category _category); bool isBool(Type::Category _category); bool isFunction(Type::Category _category); bool isMapping(Type::Category _category); +bool isArray(Type::Category _category); /// Returns a new symbolic variable, according to _type. /// Also returns whether the type is abstract or not, diff --git a/libsolidity/formal/SymbolicVariables.cpp b/libsolidity/formal/SymbolicVariables.cpp index 57921558cb5d..bdc341c43c1e 100644 --- a/libsolidity/formal/SymbolicVariables.cpp +++ b/libsolidity/formal/SymbolicVariables.cpp @@ -136,3 +136,23 @@ SymbolicMappingVariable::SymbolicMappingVariable( { solAssert(isMapping(m_type->category()), ""); } + +SymbolicArrayVariable::SymbolicArrayVariable( + TypePointer _type, + string const& _uniqueName, + smt::SolverInterface& _interface +): + SymbolicVariable(move(_type), _uniqueName, _interface) +{ + solAssert(isArray(m_type->category()), ""); +} + +SymbolicEnumVariable::SymbolicEnumVariable( + TypePointer _type, + string const& _uniqueName, + smt::SolverInterface& _interface +): + SymbolicVariable(move(_type), _uniqueName, _interface) +{ + solAssert(isEnum(m_type->category()), ""); +} diff --git a/libsolidity/formal/SymbolicVariables.h b/libsolidity/formal/SymbolicVariables.h index 86abf4f192bc..89df13f35f94 100644 --- a/libsolidity/formal/SymbolicVariables.h +++ b/libsolidity/formal/SymbolicVariables.h @@ -153,5 +153,31 @@ class SymbolicMappingVariable: public SymbolicVariable ); }; +/** + * Specialization of SymbolicVariable for Array + */ +class SymbolicArrayVariable: public SymbolicVariable +{ +public: + SymbolicArrayVariable( + TypePointer _type, + std::string const& _uniqueName, + smt::SolverInterface& _interface + ); +}; + +/** + * Specialization of SymbolicVariable for Enum + */ +class SymbolicEnumVariable: public SymbolicVariable +{ +public: + SymbolicEnumVariable( + TypePointer _type, + std::string const& _uniqueName, + smt::SolverInterface& _interface + ); +}; + } } diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 3c33eae8e697..f8a9c7ec8a6f 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -26,12 +26,31 @@ using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ +bool anyDataStoredInStorage(TypePointers const& _pointers) +{ + for (TypePointer const& pointer: _pointers) + if (pointer->dataStoredIn(DataLocation::Storage)) + return true; + + return false; +} +} + Json::Value ABI::generate(ContractDefinition const& _contractDef) { Json::Value abi(Json::arrayValue); for (auto it: _contractDef.interfaceFunctions()) { + if ( + _contractDef.isLibrary() && + (it.second->stateMutability() > StateMutability::View || + anyDataStoredInStorage(it.second->parameterTypes() + it.second->returnParameterTypes())) + ) + continue; + auto externalFunctionType = it.second->interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 88080a70bb1c..c17fdf39a24f 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -127,16 +127,6 @@ bool hashMatchesContent(string const& _hash, string const& _content) } } -StringMap createSourceList(Json::Value const& _input) -{ - StringMap sources; - Json::Value const& jsonSources = _input["sources"]; - if (jsonSources.isObject()) - for (auto const& sourceName: jsonSources.getMemberNames()) - sources[sourceName] = jsonSources[sourceName]["content"].asString(); - return sources; -} - bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact) { for (auto const& artifact: _outputSelection) @@ -365,9 +355,9 @@ boost::optional checkOutputSelection(Json::Value const& _outputSele return boost::none; } -} - -boost::optional StandardCompiler::parseOptimizerSettings(Json::Value const& _jsonInput) +/// Validates the optimizer settings and returns them in a parsed object. +/// On error returns the json-formatted error message. +boost::variant parseOptimizerSettings(Json::Value const& _jsonInput) { if (auto result = checkOptimizerKeys(_jsonInput)) return *result; @@ -417,14 +407,14 @@ boost::optional StandardCompiler::parseOptimizerSettings(Json::Valu return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet."); } } - m_compilerStack.setOptimiserSettings(std::move(settings)); - return {}; + return std::move(settings); } +} -Json::Value StandardCompiler::compileInternal(Json::Value const& _input) +boost::variant StandardCompiler::parseInput(Json::Value const& _input) { - m_compilerStack.reset(false); + InputsAndSettings ret; if (!_input.isObject()) return formatFatalError("JSONError", "Input is not a JSON object."); @@ -443,7 +433,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (sources.empty()) return formatFatalError("JSONError", "No input sources specified."); - Json::Value errors = Json::arrayValue; + ret.errors = Json::arrayValue; for (auto const& sourceName: sources.getMemberNames()) { @@ -459,14 +449,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { string content = sources[sourceName]["content"].asString(); if (!hash.empty() && !hashMatchesContent(hash, content)) - errors.append(formatError( + ret.errors.append(formatError( false, "IOError", "general", "Mismatch between content and supplied hash for \"" + sourceName + "\"" )); else - m_compilerStack.addSource(sourceName, content); + ret.sources[sourceName] = content; } else if (sources[sourceName]["urls"].isArray()) { @@ -484,7 +474,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (result.success) { if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) - errors.append(formatError( + ret.errors.append(formatError( false, "IOError", "general", @@ -492,7 +482,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) )); else { - m_compilerStack.addSource(sourceName, result.responseOrErrorMessage); + ret.sources[sourceName] = result.responseOrErrorMessage; found = true; break; } @@ -504,7 +494,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) for (auto const& failure: failures) { /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors. - errors.append(formatError( + ret.errors.append(formatError( found ? true : false, "IOError", "general", @@ -547,7 +537,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) "\"smtlib2Responses." + hashString + "\" must be a string." ); - m_compilerStack.addSMTLib2Response(hash, smtlib2Responses[hashString].asString()); + ret.smtLib2Responses[hash] = smtlib2Responses[hashString].asString(); } } } @@ -564,29 +554,31 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) boost::optional version = langutil::EVMVersion::fromString(settings["evmVersion"].asString()); if (!version) return formatFatalError("JSONError", "Invalid EVM version requested."); - m_compilerStack.setEVMVersion(*version); + ret.evmVersion = *version; } if (settings.isMember("remappings") && !settings["remappings"].isArray()) return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings."); - vector remappings; for (auto const& remapping: settings.get("remappings", Json::Value())) { if (!remapping.isString()) return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings"); if (auto r = CompilerStack::parseRemapping(remapping.asString())) - remappings.emplace_back(std::move(*r)); + ret.remappings.emplace_back(std::move(*r)); else return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\""); } - m_compilerStack.setRemappings(remappings); if (settings.isMember("optimizer")) - if (auto result = parseOptimizerSettings(settings["optimizer"])) - return *result; + { + auto optimiserSettings = parseOptimizerSettings(settings["optimizer"]); + if (optimiserSettings.type() == typeid(Json::Value)) + return boost::get(std::move(optimiserSettings)); // was an error + else + ret.optimiserSettings = boost::get(std::move(optimiserSettings)); + } - map libraries; Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); if (!jsonLibraries.isObject()) return formatFatalError("JSONError", "\"libraries\" is not a JSON object."); @@ -616,7 +608,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) try { // @TODO use libraries only for the given source - libraries[library] = h160(address); + ret.libraries[library] = h160(address); } catch (dev::BadHexCharacter const&) { @@ -627,32 +619,52 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) } } } - m_compilerStack.setLibraries(libraries); Json::Value metadataSettings = settings.get("metadata", Json::Value()); if (auto result = checkMetadataKeys(metadataSettings)) return *result; - m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool()); + ret.metadataLiteralSources = metadataSettings.get("useLiteralContent", Json::Value(false)).asBool(); Json::Value outputSelection = settings.get("outputSelection", Json::Value()); if (auto jsonError = checkOutputSelection(outputSelection)) return *jsonError; - m_compilerStack.setRequestedContractNames(requestedContractNames(outputSelection)); + ret.outputSelection = std::move(outputSelection); - bool const binariesRequested = isBinaryRequested(outputSelection); + return std::move(ret); +} + +Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings) +{ + CompilerStack compilerStack(m_readFile); + + StringMap sourceList = std::move(_inputsAndSettings.sources); + for (auto const& source: sourceList) + compilerStack.addSource(source.first, source.second); + for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses) + compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); + compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); + compilerStack.setRemappings(_inputsAndSettings.remappings); + compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); + compilerStack.setLibraries(_inputsAndSettings.libraries); + compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); + compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection)); + + Json::Value errors = std::move(_inputsAndSettings.errors); + + bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection); try { if (binariesRequested) - m_compilerStack.compile(); + compilerStack.compile(); else - m_compilerStack.parseAndAnalyze(); + compilerStack.parseAndAnalyze(); - for (auto const& error: m_compilerStack.errors()) + for (auto const& error: compilerStack.errors()) { Error const& err = dynamic_cast(*error); @@ -735,8 +747,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) )); } - bool const analysisSuccess = m_compilerStack.state() >= CompilerStack::State::AnalysisSuccessful; - bool const compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful; + bool const analysisSuccess = compilerStack.state() >= CompilerStack::State::AnalysisSuccessful; + bool const compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful; /// Inconsistent state - stop here to receive error reports from users if (((binariesRequested && !compilationSuccess) || !analysisSuccess) && errors.empty()) @@ -745,27 +757,27 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) Json::Value output = Json::objectValue; if (errors.size() > 0) - output["errors"] = errors; + output["errors"] = std::move(errors); - if (!m_compilerStack.unhandledSMTLib2Queries().empty()) - for (string const& query: m_compilerStack.unhandledSMTLib2Queries()) + if (!compilerStack.unhandledSMTLib2Queries().empty()) + for (string const& query: compilerStack.unhandledSMTLib2Queries()) output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query; output["sources"] = Json::objectValue; unsigned sourceIndex = 0; - for (string const& sourceName: analysisSuccess ? m_compilerStack.sourceNames() : vector()) + for (string const& sourceName: analysisSuccess ? compilerStack.sourceNames() : vector()) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - if (isArtifactRequested(outputSelection, sourceName, "", "ast")) - sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); - if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST")) - sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast")) + sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST")) + sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); output["sources"][sourceName] = sourceResult; } Json::Value contractsOutput = Json::objectValue; - for (string const& contractName: analysisSuccess ? m_compilerStack.contractNames() : vector()) + for (string const& contractName: analysisSuccess ? compilerStack.contractNames() : vector()) { size_t colon = contractName.rfind(':'); solAssert(colon != string::npos, ""); @@ -774,47 +786,47 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - if (isArtifactRequested(outputSelection, file, name, "abi")) - contractData["abi"] = m_compilerStack.contractABI(contractName); - if (isArtifactRequested(outputSelection, file, name, "metadata")) - contractData["metadata"] = m_compilerStack.metadata(contractName); - if (isArtifactRequested(outputSelection, file, name, "userdoc")) - contractData["userdoc"] = m_compilerStack.natspecUser(contractName); - if (isArtifactRequested(outputSelection, file, name, "devdoc")) - contractData["devdoc"] = m_compilerStack.natspecDev(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi")) + contractData["abi"] = compilerStack.contractABI(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata")) + contractData["metadata"] = compilerStack.metadata(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc")) + contractData["userdoc"] = compilerStack.natspecUser(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc")) + contractData["devdoc"] = compilerStack.natspecDev(contractName); // EVM Json::Value evmData(Json::objectValue); // @TODO: add ir - if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.assembly")) - evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); - if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly")) - evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); - if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers")) - evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); - if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.gasEstimates")) - evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly")) + evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly")) + evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers")) + evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates")) + evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); if (compilationSuccess && isArtifactRequested( - outputSelection, + _inputsAndSettings.outputSelection, file, name, { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } )) evmData["bytecode"] = collectEVMObject( - m_compilerStack.object(contractName), - m_compilerStack.sourceMapping(contractName) + compilerStack.object(contractName), + compilerStack.sourceMapping(contractName) ); if (compilationSuccess && isArtifactRequested( - outputSelection, + _inputsAndSettings.outputSelection, file, name, { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" } )) evmData["deployedBytecode"] = collectEVMObject( - m_compilerStack.runtimeObject(contractName), - m_compilerStack.runtimeSourceMapping(contractName) + compilerStack.runtimeObject(contractName), + compilerStack.runtimeSourceMapping(contractName) ); if (!evmData.empty()) @@ -837,7 +849,11 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept { try { - return compileInternal(_input); + auto parsed = parseInput(_input); + if (parsed.type() == typeid(InputsAndSettings)) + return compileSolidity(boost::get(std::move(parsed))); + else + return boost::get(std::move(parsed)); } catch (Json::LogicError const& _exception) { @@ -849,11 +865,11 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept } catch (Exception const& _exception) { - return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception)); + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::diagnostic_information(_exception)); } catch (...) { - return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal"); + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile"); } } diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index 7cc567317941..63852fbbd17c 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -25,6 +25,7 @@ #include #include +#include namespace dev { @@ -42,8 +43,8 @@ class StandardCompiler: boost::noncopyable /// Creates a new StandardCompiler. /// @param _readFile callback to used to read files for import statements. Must return /// and must not emit exceptions. - explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()) - : m_compilerStack(_readFile), m_readFile(_readFile) + explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): + m_readFile(_readFile) { } @@ -55,13 +56,25 @@ class StandardCompiler: boost::noncopyable std::string compile(std::string const& _input) noexcept; private: - /// Validaes and applies the optimizer settings. - /// On error returns the json-formatted error message. - boost::optional parseOptimizerSettings(Json::Value const& _settings); + struct InputsAndSettings + { + Json::Value errors; + std::map sources; + std::map smtLib2Responses; + langutil::EVMVersion evmVersion; + std::vector remappings; + OptimiserSettings optimiserSettings = OptimiserSettings::minimal(); + std::map libraries; + bool metadataLiteralSources = false; + Json::Value outputSelection; + }; + + /// Parses the input json (and potentially invokes the read callback) and either returns + /// it in condensed form or an error as a json object. + boost::variant parseInput(Json::Value const& _input); - Json::Value compileInternal(Json::Value const& _input); + Json::Value compileSolidity(InputsAndSettings _inputsAndSettings); - CompilerStack m_compilerStack; ReadCallback::Callback m_readFile; }; diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index abf2e3d1dd1f..77c98d6166d4 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -33,6 +34,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -472,7 +474,7 @@ bool AsmAnalyzer::operator()(ForLoop const& _for) { solAssert(_for.condition, ""); - Scope* originalScope = m_currentScope; + Scope* outerScope = m_currentScope; bool success = true; if (!(*this)(_for.pre)) @@ -485,18 +487,37 @@ bool AsmAnalyzer::operator()(ForLoop const& _for) if (!expectExpression(*_for.condition)) success = false; m_stackHeight--; + + // backup outer for-loop & create new state + auto outerForLoop = m_currentForLoop; + m_currentForLoop = &_for; + if (!(*this)(_for.body)) success = false; + if (!(*this)(_for.post)) success = false; m_stackHeight -= scope(&_for.pre).numberOfVariables(); m_info.stackHeightInfo[&_for] = m_stackHeight; - m_currentScope = originalScope; + m_currentScope = outerScope; + m_currentForLoop = outerForLoop; return success; } +bool AsmAnalyzer::operator()(Break const& _break) +{ + m_info.stackHeightInfo[&_break] = m_stackHeight; + return true; +} + +bool AsmAnalyzer::operator()(Continue const& _continue) +{ + m_info.stackHeightInfo[&_continue] = m_stackHeight; + return true; +} + bool AsmAnalyzer::operator()(Block const& _block) { bool success = true; diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 7d11391f9915..d25b3340a44e 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -34,6 +34,7 @@ #include #include +#include #include namespace langutil @@ -93,6 +94,8 @@ class AsmAnalyzer: public boost::static_visitor bool operator()(If const& _if); bool operator()(Switch const& _switch); bool operator()(ForLoop const& _forLoop); + bool operator()(Break const&); + bool operator()(Continue const&); bool operator()(Block const& _block); private: @@ -124,6 +127,7 @@ class AsmAnalyzer: public boost::static_visitor langutil::EVMVersion m_evmVersion; std::shared_ptr m_dialect; boost::optional m_errorTypeForLoose; + ForLoop const* m_currentForLoop = nullptr; }; } diff --git a/libyul/AsmData.h b/libyul/AsmData.h index fd8eab38b7d5..3b1eaaf8bf21 100644 --- a/libyul/AsmData.h +++ b/libyul/AsmData.h @@ -78,6 +78,10 @@ struct Case { langutil::SourceLocation location; std::unique_ptr value; /// Switch statement struct Switch { langutil::SourceLocation location; std::unique_ptr expression; std::vector cases; }; struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr condition; Block post; Block body; }; +/// Break statement (valid within for loop) +struct Break { langutil::SourceLocation location; }; +/// Continue statement (valid within for loop) +struct Continue { langutil::SourceLocation location; }; struct LocationExtractor: boost::static_visitor { diff --git a/libyul/AsmDataForward.h b/libyul/AsmDataForward.h index de5644252eea..d01b1dffad22 100644 --- a/libyul/AsmDataForward.h +++ b/libyul/AsmDataForward.h @@ -41,12 +41,14 @@ struct If; struct Switch; struct Case; struct ForLoop; +struct Break; +struct Continue; struct ExpressionStatement; struct Block; struct TypedName; using Expression = boost::variant; -using Statement = boost::variant; +using Statement = boost::variant; } diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 0adab10b6c62..320da666bec3 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -104,6 +105,32 @@ Statement Parser::parseStatement() } case Token::For: return parseForLoop(); + case Token::Break: + if (m_insideForLoopBody) + { + auto stmt = Statement{ createWithLocation() }; + m_scanner->next(); + return stmt; + } + else + { + m_errorReporter.syntaxError(location(), "Keyword break outside for-loop body is not allowed."); + m_scanner->next(); + return {}; + } + case Token::Continue: + if (m_insideForLoopBody) + { + auto stmt = Statement{ createWithLocation() }; + m_scanner->next(); + return stmt; + } + else + { + m_errorReporter.syntaxError(location(), "Keyword continue outside for-loop body is not allowed."); + m_scanner->next(); + return {}; + } case Token::Assign: { if (m_dialect->flavour != AsmFlavour::Loose) @@ -243,13 +270,19 @@ Case Parser::parseCase() ForLoop Parser::parseForLoop() { + bool outerForLoopBody = m_insideForLoopBody; + m_insideForLoopBody = false; + RecursionGuard recursionGuard(*this); ForLoop forLoop = createWithLocation(); expectToken(Token::For); forLoop.pre = parseBlock(); forLoop.condition = make_unique(parseExpression()); forLoop.post = parseBlock(); + + m_insideForLoopBody = true; forLoop.body = parseBlock(); + m_insideForLoopBody = outerForLoopBody; forLoop.location.end = forLoop.body.location.end; return forLoop; } @@ -455,6 +488,9 @@ VariableDeclaration Parser::parseVariableDeclaration() FunctionDefinition Parser::parseFunctionDefinition() { RecursionGuard recursionGuard(*this); + auto outerForLoopBody = m_insideForLoopBody; + m_insideForLoopBody = false; + ScopeGuard restoreInsideForLoopBody{[&]() { m_insideForLoopBody = outerForLoopBody; }}; FunctionDefinition funDef = createWithLocation(); expectToken(Token::Function); funDef.name = expectAsmIdentifier(); diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h index a02f0b4dbfce..098e237b91e5 100644 --- a/libyul/AsmParser.h +++ b/libyul/AsmParser.h @@ -39,7 +39,7 @@ class Parser: public langutil::ParserBase { public: explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr _dialect): - ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {} + ParserBase(_errorReporter), m_dialect(std::move(_dialect)), m_insideForLoopBody{false} {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @param _reuseScanner if true, do check for end of input after the `}`. @@ -87,6 +87,7 @@ class Parser: public langutil::ParserBase private: std::shared_ptr m_dialect; + bool m_insideForLoopBody; }; } diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index b7af47784944..fd360bc32ecf 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -224,6 +224,16 @@ string AsmPrinter::operator()(ForLoop const& _forLoop) const return out; } +string AsmPrinter::operator()(Break const&) const +{ + return "break"; +} + +string AsmPrinter::operator()(Continue const&) const +{ + return "continue"; +} + string AsmPrinter::operator()(Block const& _block) const { if (_block.statements.empty()) diff --git a/libyul/AsmPrinter.h b/libyul/AsmPrinter.h index a1b9d6cd0ff8..82fed24f2c5c 100644 --- a/libyul/AsmPrinter.h +++ b/libyul/AsmPrinter.h @@ -50,6 +50,8 @@ class AsmPrinter: public boost::static_visitor std::string operator()(If const& _if) const; std::string operator()(Switch const& _switch) const; std::string operator()(ForLoop const& _forLoop) const; + std::string operator()(Break const& _break) const; + std::string operator()(Continue const& _continue) const; std::string operator()(Block const& _block) const; private: diff --git a/libyul/AsmScopeFiller.h b/libyul/AsmScopeFiller.h index e8fb88d5f77f..7c8834637bfa 100644 --- a/libyul/AsmScopeFiller.h +++ b/libyul/AsmScopeFiller.h @@ -63,6 +63,8 @@ class ScopeFiller: public boost::static_visitor bool operator()(If const& _if); bool operator()(Switch const& _switch); bool operator()(ForLoop const& _forLoop); + bool operator()(Break const&) { return true; } + bool operator()(Continue const&) { return true; } bool operator()(Block const& _block); private: diff --git a/libyul/Utilities.cpp b/libyul/Utilities.cpp index e5f4e517c04a..2cf72f499ae9 100644 --- a/libyul/Utilities.cpp +++ b/libyul/Utilities.cpp @@ -24,6 +24,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -31,12 +32,48 @@ using namespace yul; u256 yul::valueOfNumberLiteral(Literal const& _literal) { - assertThrow(_literal.kind == LiteralKind::Number, OptimizerException, ""); + yulAssert(_literal.kind == LiteralKind::Number, "Expected number literal!"); + std::string const& literalString = _literal.value.str(); - assertThrow(isValidDecimal(literalString) || isValidHex(literalString), OptimizerException, ""); + yulAssert(isValidDecimal(literalString) || isValidHex(literalString), "Invalid number literal!"); return u256(literalString); } +u256 yul::valueOfStringLiteral(Literal const& _literal) +{ + yulAssert(_literal.kind == LiteralKind::String, "Expected string literal!"); + yulAssert(_literal.value.str().size() <= 32, "Literal string too long!"); + + return u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft)); +} + +u256 yul::valueOfBoolLiteral(Literal const& _literal) +{ + yulAssert(_literal.kind == LiteralKind::Boolean, "Expected bool literal!"); + + if (_literal.value == "true"_yulstring) + return u256(1); + else if (_literal.value == "false"_yulstring) + return u256(0); + + yulAssert(false, "Unexpected bool literal value!"); +} + +u256 yul::valueOfLiteral(Literal const& _literal) +{ + switch(_literal.kind) + { + case LiteralKind::Number: + return valueOfNumberLiteral(_literal); + case LiteralKind::Boolean: + return valueOfBoolLiteral(_literal); + case LiteralKind::String: + return valueOfStringLiteral(_literal); + default: + yulAssert(false, "Unexpected literal kind!"); + } +} + template<> bool Less::operator()(Literal const& _lhs, Literal const& _rhs) const { diff --git a/libyul/Utilities.h b/libyul/Utilities.h index 288bc6ee208d..827b43aae1af 100644 --- a/libyul/Utilities.h +++ b/libyul/Utilities.h @@ -27,6 +27,9 @@ namespace yul { dev::u256 valueOfNumberLiteral(Literal const& _literal); +dev::u256 valueOfStringLiteral(Literal const& _literal); +dev::u256 valueOfBoolLiteral(Literal const& _literal); +dev::u256 valueOfLiteral(Literal const& _literal); /** * Linear order on Yul AST nodes. diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 354627d2b166..78c5e7d6314f 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -68,7 +69,6 @@ void VariableReferenceCounter::operator()(ForLoop const& _forLoop) m_scope = originalScope; } - void VariableReferenceCounter::operator()(Block const& _block) { Scope* originalScope = m_scope; @@ -91,7 +91,6 @@ void VariableReferenceCounter::increaseRefIfFound(YulString _variableName) )); } - CodeTransform::CodeTransform( AbstractAssembly& _assembly, AsmAnalysisInfo& _analysisInfo, @@ -396,20 +395,8 @@ void CodeTransform::operator()(Identifier const& _identifier) void CodeTransform::operator()(Literal const& _literal) { m_assembly.setSourceLocation(_literal.location); - if (_literal.kind == LiteralKind::Number) - m_assembly.appendConstant(u256(_literal.value.str())); - else if (_literal.kind == LiteralKind::Boolean) - { - if (_literal.value == "true"_yulstring) - m_assembly.appendConstant(u256(1)); - else - m_assembly.appendConstant(u256(0)); - } - else - { - solAssert(_literal.value.str().size() <= 32, ""); - m_assembly.appendConstant(u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft))); - } + m_assembly.appendConstant(valueOfLiteral(_literal)); + checkStackHeight(&_literal); } @@ -616,11 +603,9 @@ void CodeTransform::operator()(ForLoop const& _forLoop) visitStatements(_forLoop.pre.statements); - // TODO: When we implement break and continue, the labels and the stack heights at that point - // have to be stored in a stack. AbstractAssembly::LabelID loopStart = m_assembly.newLabelId(); - AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId(); AbstractAssembly::LabelID postPart = m_assembly.newLabelId(); + AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId(); m_assembly.setSourceLocation(_forLoop.location); m_assembly.appendLabel(loopStart); @@ -630,6 +615,8 @@ void CodeTransform::operator()(ForLoop const& _forLoop) m_assembly.appendInstruction(solidity::Instruction::ISZERO); m_assembly.appendJumpToIf(loopEnd); + int const stackHeightBody = m_assembly.stackHeight(); + m_context->forLoopStack.emplace(Context::ForLoopLabels{ {postPart, stackHeightBody}, {loopEnd, stackHeightBody} }); (*this)(_forLoop.body); m_assembly.setSourceLocation(_forLoop.location); @@ -642,7 +629,19 @@ void CodeTransform::operator()(ForLoop const& _forLoop) m_assembly.appendLabel(loopEnd); finalizeBlock(_forLoop.pre, stackStartHeight); + m_context->forLoopStack.pop(); m_scope = originalScope; + checkStackHeight(&_forLoop); +} + +void CodeTransform::operator()(Break const&) +{ + yulAssert(false, "Code generation for break statement in Yul is not implemented yet."); +} + +void CodeTransform::operator()(Continue const&) +{ + yulAssert(false, "Code generation for continue statement in Yul is not implemented yet."); } void CodeTransform::operator()(Block const& _block) diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index 3ebf0f490e8b..141dd91effa7 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -30,6 +30,8 @@ #include #include +#include + namespace langutil { class ErrorReporter; @@ -57,6 +59,20 @@ struct CodeTransformContext std::map functionEntryIDs; std::map variableStackHeights; std::map variableReferences; + + struct JumpInfo + { + AbstractAssembly::LabelID label; ///< Jump's LabelID to jump to. + int targetStackHeight; ///< Stack height after the jump. + }; + + struct ForLoopLabels + { + JumpInfo post; ///< Jump info for jumping to post branch. + JumpInfo done; ///< Jump info for jumping to done branch. + }; + + std::stack forLoopStack; }; /** @@ -166,6 +182,8 @@ class CodeTransform: public boost::static_visitor<> void operator()(Switch const& _switch); void operator()(FunctionDefinition const&); void operator()(ForLoop const&); + void operator()(Break const&); + void operator()(Continue const&); void operator()(Block const& _block); private: diff --git a/libyul/optimiser/ASTCopier.cpp b/libyul/optimiser/ASTCopier.cpp index f18b0e6bb048..f98cb5653df7 100644 --- a/libyul/optimiser/ASTCopier.cpp +++ b/libyul/optimiser/ASTCopier.cpp @@ -138,6 +138,15 @@ Statement ASTCopier::operator()(ForLoop const& _forLoop) translate(_forLoop.body) }; } +Statement ASTCopier::operator()(Break const& _break) +{ + return Break{ _break }; +} + +Statement ASTCopier::operator()(Continue const& _continue) +{ + return Continue{ _continue }; +} Statement ASTCopier::operator ()(Block const& _block) { diff --git a/libyul/optimiser/ASTCopier.h b/libyul/optimiser/ASTCopier.h index 7e5e9c86888c..a408b43fbfb6 100644 --- a/libyul/optimiser/ASTCopier.h +++ b/libyul/optimiser/ASTCopier.h @@ -58,6 +58,8 @@ class StatementCopier: public boost::static_visitor virtual Statement operator()(Switch const& _switch) = 0; virtual Statement operator()(FunctionDefinition const&) = 0; virtual Statement operator()(ForLoop const&) = 0; + virtual Statement operator()(Break const&) = 0; + virtual Statement operator()(Continue const&) = 0; virtual Statement operator()(Block const& _block) = 0; }; @@ -83,6 +85,8 @@ class ASTCopier: public ExpressionCopier, public StatementCopier Statement operator()(Switch const& _switch) override; Statement operator()(FunctionDefinition const&) override; Statement operator()(ForLoop const&) override; + Statement operator()(Break const&) override; + Statement operator()(Continue const&) override; Statement operator()(Block const& _block) override; virtual Expression translate(Expression const& _expression); diff --git a/libyul/optimiser/ASTWalker.cpp b/libyul/optimiser/ASTWalker.cpp index 6adcb2e15ebe..fe08c4313a3c 100644 --- a/libyul/optimiser/ASTWalker.cpp +++ b/libyul/optimiser/ASTWalker.cpp @@ -161,6 +161,14 @@ void ASTModifier::operator()(ForLoop& _for) (*this)(_for.body); } +void ASTModifier::operator()(Break&) +{ +} + +void ASTModifier::operator()(Continue&) +{ +} + void ASTModifier::operator()(Block& _block) { walkVector(_block.statements); diff --git a/libyul/optimiser/ASTWalker.h b/libyul/optimiser/ASTWalker.h index 272725f9f153..a1f24479584c 100644 --- a/libyul/optimiser/ASTWalker.h +++ b/libyul/optimiser/ASTWalker.h @@ -56,6 +56,8 @@ class ASTWalker: public boost::static_visitor<> virtual void operator()(Switch const& _switch); virtual void operator()(FunctionDefinition const&); virtual void operator()(ForLoop const&); + virtual void operator()(Break const&) {} + virtual void operator()(Continue const&) {} virtual void operator()(Block const& _block); virtual void visit(Statement const& _st); @@ -91,6 +93,8 @@ class ASTModifier: public boost::static_visitor<> virtual void operator()(Switch& _switch); virtual void operator()(FunctionDefinition&); virtual void operator()(ForLoop&); + virtual void operator()(Break&); + virtual void operator()(Continue&); virtual void operator()(Block& _block); virtual void visit(Statement& _st); diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index b4f2d1f96dc4..c34384024c4d 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -130,6 +130,16 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) popScope(); } +void DataFlowAnalyzer::operator()(Break&) +{ + yulAssert(false, "Not implemented yet."); +} + +void DataFlowAnalyzer::operator()(Continue&) +{ + yulAssert(false, "Not implemented yet."); +} + void DataFlowAnalyzer::operator()(Block& _block) { size_t numScopes = m_variableScopes.size(); diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 5fb5db958d2f..7a569513ec55 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -53,6 +53,8 @@ class DataFlowAnalyzer: public ASTModifier void operator()(Switch& _switch) override; void operator()(FunctionDefinition&) override; void operator()(ForLoop&) override; + void operator()(Break& _continue) override; + void operator()(Continue& _continue) override; void operator()(Block& _block) override; protected: diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index e13940aa4331..a171f3610214 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -52,9 +52,16 @@ size_t CodeSize::codeSize(Block const& _block) return cs.m_size; } +size_t CodeSize::codeSizeIncludingFunctions(Block const& _block) +{ + CodeSize cs(false); + cs(_block); + return cs.m_size; +} + void CodeSize::visit(Statement const& _statement) { - if (_statement.type() == typeid(FunctionDefinition)) + if (_statement.type() == typeid(FunctionDefinition) && m_ignoreFunctions) return; else if (!( _statement.type() == typeid(Block) || diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index 5364646e46c2..c0f6a9cd0638 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -28,7 +28,7 @@ namespace yul /** * Metric for the size of code. * More specifically, the number of AST nodes. - * Ignores function definitions while traversing the AST. + * Ignores function definitions while traversing the AST by default. * If you want to know the size of a function, you have to invoke this on its body. * * As an exception, the following AST elements have a cost of zero: @@ -44,14 +44,16 @@ class CodeSize: public ASTWalker static size_t codeSize(Statement const& _statement); static size_t codeSize(Expression const& _expression); static size_t codeSize(Block const& _block); + static size_t codeSizeIncludingFunctions(Block const& _block); private: - CodeSize() {} + CodeSize(bool _ignoreFunctions = true): m_ignoreFunctions(_ignoreFunctions) {} void visit(Statement const& _statement) override; void visit(Expression const& _expression) override; private: + bool m_ignoreFunctions; size_t m_size = 0; }; diff --git a/libyul/optimiser/README.md b/libyul/optimiser/README.md index c25751795757..0c631615e6a2 100644 --- a/libyul/optimiser/README.md +++ b/libyul/optimiser/README.md @@ -2,7 +2,19 @@ Note that the Yul optimiser is still in research phase. Because of that, the following description might not fully reflect the current or even planned state of the optimiser. -## Yul Optimiser +Table of Contents: + +- [Preprocessing](#preprocessing) +- [Pseudo-SSA Transformation](#pseudo-ssa-transformation) +- [Tools](#tools) +- [Expression-Scale Simplifications](#expression-scale-simplifications) +- [Statement-Scale Simplifications](#statement-scale-simplifications) +- [Function Inlining](#function-inlining) +- [Cleanup](#cleanup) +- [Webassembly-sepcific](#webassembly-specific) + + +# Yul Optimiser The Yul optimiser consists of several stages and components that all transform the AST in a semantically equivalent way. The goal is to end up either with code @@ -12,7 +24,22 @@ optimisation steps. The optimiser currently follows a purely greedy strategy and does not do any backtracking. -## Disambiguator +All components of the optimiser are explained below, where +the following transformation steps are the main components: + + - [SSA Transform](#ssa-transform) + - [Common Subexpression Eliminator](#common-subexpression-eliminator) + - [Expression Simplifier](#expression-simplifier) + - [Redundant Assign Eliminator](#redundant-assign-eliminator) + - [Full Function Inliner](#full-function-inliner) + +## Preprocessing + +The preprocessing components perform transformations to get the program +into a certain normal form that is easier to work with. This normal +form is kept during the rest of the optimisation process. + +### Disambiguator The disambiguator takes an AST and returns a fresh copy where all identifiers have names unique to the input AST. This is a prerequisite for all other optimiser stages. @@ -22,16 +49,17 @@ and we can basically ignore the result of the analysis phase. All subsequent stages have the property that all names stay unique. This means if a new identifier needs to be introduced, a new unique name is generated. -## Function Hoister +### Function Hoister The function hoister moves all function definitions to the end of the topmost block. This is a semantically equivalent transformation as long as it is performed after the disambiguation stage. The reason is that moving a definition to a higher-level block cannot decrease its visibility and it is impossible to reference variables defined in a different function. -The benefit of this stage is that function definitions can be looked up more easily. +The benefit of this stage is that function definitions can be looked up more easily +and functions can be optimised in isolation without having to traverse the AST. -## Function Grouper +### Function Grouper The function grouper has to be applied after the disambiguator and the function hoister. Its effect is that all topmost elements that are not function definitions are moved @@ -41,22 +69,91 @@ After this step, a program has the following normal form: { I F... } -Where I is a block that does not contain any function definitions (not even recursively) +Where I is a (potentially empty) block that does not contain any function definitions (not even recursively) and F is a list of function definitions such that no function contains a function definition. -## Functional Inliner +The benefit of this stage is that we always know where the list of function begins. + + +### For Loop Init Rewriter + +This transformation moves the initialization part of a for-loop to before +the loop: + + for { Init... } C { Post... } { + Body... + } + +is transformed to + + { + Init... + for {} C { Post... } { + Body... + } + } + +This eases the rest of the optimisation process because we can ignore +the complicated scoping rules of the for loop initialisation block. + +## Pseudo-SSA Transformation -The functional inliner depends on the disambiguator, the function hoister and function grouper. -It performs function inlining such that the result of the inlining is an expression. This can -only be done if the body of the function to be inlined has the form ``{ r := E }`` where ``r`` -is the single return value of the function, ``E`` is an expression and all arguments in the -function call are so-called movable expressions. A movable expression is either a literal, a -variable or a function call (or EVM opcode) which does not have side-effects and also does not -depend on any side-effects. +The purpose of this components is to get the program into a longer form, +so that other components can more easily work with it. The final representation +will be similar to a static-single-assignment (SSA) form, with the difference +that it does not make use of explicit "phi" functions which combines the values +from different branches of control flow because such a feature does not exist +in the Yul language. Instead, assignments to existing variables are +used. -As an example, neither ``mload`` nor ``mstore`` would be allowed. +An example transformation is the following: -## Expression Splitter + { + let a := calldataload(0) + let b := calldataload(0x20) + if gt(a, 0) { + b := mul(b, 0x20) + } + a := add(a, 1) + sstore(a, add(b, 0x20)) + } + +When all the following transformation steps are applied, the program will look +as follows: + + { + let _1 := 0 + let a_1 := calldataload(_1) + let _2 := 0x20 + let b_1 := calldataload(_2) + let b := b_1 + let _3 := 0 + let _4 := gt(a_1, _3) + if _4 { + let _5 := 0x20 + let b_2 := mul(b_1, _5) + b := b_2 + } + let a_2 := add(a_1, 1) + let _6 := 0x20 + let _7 := add(b, _6) + sstore(a_2, _7) + } + +Note that the only variable that is re-assigned in this snippet is ``b``. +This re-assignment cannot be avoided because ``b`` has different values +depending on the control flow. All other variables never change their +value once they are defined. The advantage of this property is that +variables can be freely moved around and references to them +can be exchanged by their initial value (and vice-versa), +as long as these values are still valid in the new context. + +Of course, the code here is far from being optimised. To the contrary, it is much +longer. The hope is that this code will be easier to work with and furthermore, +there are optimiser steps that undo these changes and make the code more +compact again at the end. + +### Expression Splitter The expression splitter turns expressions like ``add(mload(x), mul(mload(y), 0x20))`` into a sequence of declarations of unique variables that are assigned sub-expressions @@ -77,19 +174,351 @@ Note that this transformation does not change the order of opcodes or function c It is not applied to loop conditions, because the loop control flow does not allow this "outlining" of the inner expressions in all cases. -The final program should be in a form such that with the exception of loop conditions, -function calls can only appear in the right-hand side of a variable declaration, -assignments or expression statements and all arguments have to be constants or variables. +The final program should be in a form such that (with the exception of loop conditions) +function calls cannot appear nested inside expressions +and all function call arguments have to be constants or variables. The benefits of this form are that it is much easier to re-order the sequence of opcodes -and it is also easier to perform function call inlining. The drawback is that -such code is much harder to read for humans. +and it is also easier to perform function call inlining. Furthermore, it is simpler +to replace individual parts of expressions or re-organize the "expression tree". +The drawback is that such code is much harder to read for humans. + +### SSA Transform + +This stage tries to replace repeated assignments to +existing variables by declarations of new variables as much as +possible. +The reassignments are still there, but all references to the +reassigned variables are replaced by the newly declared variables. + +Example: + + { + let a := 1 + mstore(a, 2) + a := 3 + } + +is transformed to + + { + let a_1 := 1 + let a := a_1 + mstore(a_1, 2) + let a_3 := 3 + a := a_3 + } + +Exact semantics: + +For any variable ``a`` that is assigned to somewhere in the code +(variables that are declared with value and never re-assigned +are not modified) perform the following transforms: + - replace ``let a := v`` by ``let a_i := v let a := a_i`` + - replace ``a := v`` by ``let a_i := v a := a_i`` +where ``i`` is a number such that ``a_i`` is yet unused. + +Furthermore, always record the current value of ``i`` used for ``a`` and replace each +reference to ``a`` by ``a_i``. +The current value mapping is cleared for a variable ``a`` at the end of each block +in which it was assigned to and at the end of the for loop init block if it is assigned +inside the for loop body or post block. + +After this stage, the Redundant Assign Eliminator is recommended to remove the unnecessary +intermediate assignments. + +This stage provides best results if the Expression Splitter and the Common Subexpression Eliminator +are run right before it, because then it does not generate excessive amounts of variables. +On the other hand, the Common Subexpression Eliminator could be more efficient if run after the +SSA transform. + +### Redundant Assign Eliminator + +The SSA transform always generates an assignment of the form ``a := a_i``, even though +these might be unnecessary in many cases, like the following example: + + { + let a := 1 + a := mload(a) + a := sload(a) + sstore(a, 1) + } + +The SSA transform converts this snippet to the following: + + { + let a_1 := 1 + a := a_1 + let a_2 := mload(a_1) + a := a_2 + let a_3 := sload(a_2) + a := a_3 + sstore(a_3, 1) + } + +The Redundant Assign Eliminator removes all the three assignments to ``a``, because +the value of ``a`` is not used and thus turn this +snippet into strict SSA form: + + { + let a_1 := 1 + let a_2 := mload(a_1) + let a_3 := sload(a_2) + sstore(a_3, 1) + } + +Of course the intricate parts of determining whether an assignment is redundant or not +are connected to joining control flow. + +The component works as follows in detail: + +The AST is traversed twice: in an information gathering step and in the +actual removal step. During information gathering, we maintain a +mapping from assignment statements to the three states +"unused", "undecided" and "used" which signifies whether the assigned +value will be used later by a reference to the variable. + +When an assignment is visited, it is added to the mapping in the "undecided" state +(see remark about for loops below) and every other assignment to the same variable +that is still in the "undecided" state is changed to "unused". +When a variable is referenced, the state of any assignment to that variable still +in the "undecided" state is changed to "used". + +At points where control flow splits, a copy +of the mapping is handed over to each branch. At points where control flow +joins, the two mappings coming from the two branches are combined in the following way: +Statements that are only in one mapping or have the same state are used unchanged. +Conflicting values are resolved in the following way: + + - "unused", "undecided" -> "undecided" + - "unused", "used" -> "used" + - "undecided, "used" -> "used" + +For for-loops, the condition, body and post-part are visited twice, taking +the joining control-flow at the condition into account. +In other words, we create three control flow paths: Zero runs of the loop, +one run and two runs and then combine them at the end. + +Simulating a third run or even more is unnecessary, which can be seen as follows: + +A state of an assignment at the beginning of the iteration will deterministically +result in a state of that assignment at the end of the iteration. Let this +state mapping function be called `f`. The combination of the three different +states `unused`, `undecided` and `used` as explained above is the `max` +operation where `unused = 0`, `undecided = 1` and `used = 2`. + +The proper way would be to compute + + max(s, f(s), f(f(s)), f(f(f(s))), ...) + +as state after the loop. Since `f` just has a range of three different values, +iterating it has to reach a cycle after at most three iterations, +and thus `f(f(f(s)))` has to equal one of `s`, `f(s)`, or `f(f(s))` +and thus + + max(s, f(s), f(f(s))) = max(s, f(s), f(f(s)), f(f(f(s))), ...). + +In summary, running the loop at most twice is enough because there are only three +different states. + +For switch statements that have a "default"-case, there is no control-flow +part that skips the switch. + +When a variable goes out of scope, all statements still in the "undecided" +state are changed to "unused", unless the variable is the return +parameter of a function - there, the state changes to "used". + +In the second traversal, all assignments that are in the "unused" state are removed. -## Expression Joiner +This step is usually run right after the SSA transform to complete +the generation of the pseudo-SSA. + +## Tools + +### Movability + +Movability is a property of an expression. It roughly means that the expression +is side-effect free and its evaluation only depends on the values of variables +and the call-constant state of the environment. Most expressions are movable. +The following parts make an expression non-movable: + + - function calls (might be relaxed in the future if all statements in the function are movable) + - opcodes that (can) have side-effects (like ``call`` or ``selfdestruct``) + - opcodes that read or write memory, storage or external state information + - opcodes that depend on the current PC, memory size or returndata size + +### Dataflow Analyzer + +The Dataflow Analyzer is not an optimizer step itself but is used as a tool +by other components. While traversing the AST, it tracks the current value of +each variable, as long as that value is a movable expression. +It records the variables that are part of the expression +that is currently assigned to each other variable. Upon each assignment to +a variable ``a``, the current stored value of ``a`` is updated and +all stored values of all variables ``b`` are cleared whenever ``a`` is part +of the currently stored expression for ``b``. + +At control-flow joins, knowledge about variables is cleared if they have or would be assigned +in any of the control-flow paths. For instance, upon entering a +for loop, all variables are cleared that will be assigned during the +body or the post block. + +## Expression-Scale Simplifications + +These simplification passes change expressions and replace them by equivalent +and hopefully simpler expressions. + +### Common Subexpression Eliminator + +This step uses the Dataflow Analyzer and replaces subexpressions that +syntactically match the current value of a variable by a reference to +that variable. This is an equivalence transform because such subexpressions have +to be movable. + +All subexpressions that are identifiers themselves are replaced by their +current value if the value is an identifier. + +The combination of the two rules above allow to compute a local value +numbering, which means that if two variables have the same +value, one of them will always be unused. The Unused Pruner or the +Redundant Assign Eliminator will then be able to fully eliminate such +variables. + +This step is especially efficient if the expression splitter is run +before. If the code is in pseudo-SSA form, +the values of variables are available for a longer time and thus we +have a higher chance of expressions to be replaceable. + +The expression simplifier will be able to perform better replacements +if the common subexpression eliminator was run right before it. + +### Expression Simplifier + +The Expression Simplifier uses the Dataflow Analyzer and makes use +of a list of equivalence transforms on expressions like ``X + 0 -> X`` +to simplify the code. + +It tries to match patterns like ``X + 0`` on each subexpression. +During the matching procedure, it resolves variables to their currently +assigned expressions to be able to match more deeply nested patterns +even when the code is in pseudo-SSA form. + +Some of the patterns like ``X - X -> 0`` can only be applied as long +as the expression ``X`` is movable, because otherwise it would remove its potential side-effects. +Since variable references are always movable, even if their current +value might not be, the Expression Simplifier is again more powerful +in split or pseudo-SSA form. + +## Statement-Scale Simplifications + +### Unused Pruner + +This step removes the definitions of all functions that are never referenced. + +It also removes the declaration of variables that are never referenced. +If the declaration assigns a value that is not movable, the expression is retained, +but its value is discarded. + +All movable expression statements (expressions that are not assigned) are removed. + +### Structural Simplifier + +This is a general step that performs various kinds of simplifications on +a structural level: + + - replace if statement with empty body by ``pop(condition)`` + - replace if statement with true condition by its body + - remove if statement with false condition + - turn switch with single case into if + - replace switch with only default case by ``pop(expression)`` and body + - replace switch with literal expression by matching case body + - replace for loop with false condition by its initialization part + +This component uses the Dataflow Analyzer. + +### Equivalent Function Combiner + +If two functions are syntactically equivalent, while allowing variable +renaming but not any re-ordering, then any reference to one of the +functions is replaced by the other. + +The actual removal of the function is performed by the Unused Pruner. + +### Block Flattener + +This stage eliminates nested blocks by inserting the statement in the +inner block at the appropriate place in the outer block: + + { + let x := 2 + { + let y := 3 + mstore(x, y) + } + } + +is transformed to + + { + let x := 2 + let y := 3 + mstore(x, y) + } + +As long as the code is disambiguated, this does not cause a problem because +the scopes of variables can only grow. + +## Function Inlining + +### Functional Inliner + +The functional inliner performs restricted function inlining. In particular, +the result of this inlining is always a single expression. This can +only be done if the function to be inlined has the form ``function f(...) -> r { r := E }`` where +``E`` is an expression that does not reference ``r`` and all arguments in the +function call are movable expressions. The function call is directly replaced +by ``E``, substituting the function call arguments. Because this can cause the +function call arguments to be duplicated, removed or re-ordered, they have +to be movable. + +### Full Function Inliner + +The Full Function Inliner replaces certain calls of certain functions +by the function's body. This is not very helpful in most cases, because +it just increases the code size but does not have a benefit. Furthermore, +code is usually very expensive and we would often rather have shorter +code than more efficient code. In same cases, though, inlining a function +can have positive effects on subsequent optimizer steps. This is the case +if one of the function arguments is a constant, for example. + +During inlining, a heuristic is used to tell if the function call +should be inlined or not. +The current heuristic does not inline into "large" functions unless +the called function is tiny. Functions that are only used once +are inlined, as well as medium-sized functions, while function +calls with constant arguments allow slightly larger functions. + + +In the future, we might want to have a backtracking component +that, instead of inlining a function right away, only specializes it, +which means that a copy of the function is generated where +a certain parameter is always replaced by a constant. After that, +we can run the optimizer on this specialized function. If it +results in heavy gains, the specialized function is kept, +otherwise the original function is used instead. + +## Cleanup + +The cleanup is performed at the end of the optimizer run. It tries +to combine split expressions into deeply nested ones again and also +improves the "compilability" for stack machines by eliminating +variables as much as possible. + +### Expression Joiner This is the opposite operation of the expression splitter. It turns a sequence of variable declarations that have exactly one reference into a complex expression. -This stage again fully preserves the order of function calls and opcode executions. +This stage fully preserves the order of function calls and opcode executions. It does not make use of any information concerning the commutability of opcodes; if moving the value of a variable to its place of use would change the order of any function call or opcode execution, the transformation is not performed. @@ -97,57 +526,82 @@ of any function call or opcode execution, the transformation is not performed. Note that the component will not move the assigned value of a variable assignment or a variable that is referenced more than once. -## Common Subexpression Eliminator +The snippet ``let x := add(0, 2) let y := mul(x, mload(2))`` is not transformed, +because it would cause the order of the call to the opcodes ``add`` and +``mload`` to be swapped - even though this would not make a difference +because ``add`` is movable. -This step replaces a subexpression by the value of a pre-existing variable -that currently has the same value (only if the value is movable), based -on a syntactic comparison. +When reordering opcodes like that, variable references and literals are ignored. +Because of that, the snippet ``let x := add(0, 2) let y := mul(x, 3)`` is +transformed to ``let y := mul(add(0, 2), 3)``, even though the ``add`` opcode +would be executed after the evaluation of the literal ``3``. -This can be used to compute a local value numbering, especially if the -expression splitter is used before. +### SSA Reverser -The expression simplifier will be able to perform better replacements -if the common subexpression eliminator was run right before it. +This is a tiny step that helps in reversing the effects of the SSA transform +if it is combined with the Common Subexpression Eliminator and the +Unused Pruner. -Prerequisites: Disambiguator +The SSA form we generate is detrimental to code generation on the EVM and +WebAssembly alike because it generates many local variables. It would +be better to just re-use existing variables with assignments instead of +fresh variable declarations. -## Full Function Inliner +The SSA transform rewrites -## Rematerialisation + a := E + mstore(a, 1) -The rematerialisation stage tries to replace variable references by the expression that -was last assigned to the variable. This is of course only beneficial if this expression -is comparatively cheap to evaluate. Furthermore, it is only semantically equivalent if -the value of the expression did not change between the point of assignment and the -point of use. The main benefit of this stage is that it can save stack slots if it -leads to a variable being eliminated completely (see below), but it can also -save a DUP opcode on the EVM if the expression is very cheap. +to -The algorithm only allows movable expressions (see above for a definition) in this case. -Expressions that contain other variables are also disallowed if one of those variables -have been assigned to in the meantime. This is also not applied to variables where -assignment and use span across loops and conditionals. + let a_1 := E + a := a_1 + mstore(a_1, 1) -## Unused Definition Pruner +The problem is that instead of ``a``, the variable ``a_1`` is used +whenever ``a`` was referenced. The SSA transform changes statements +of this form by just swapping out the declaration and the assignment. The above +snippet is turned into -If a variable or function is not referenced, it is removed from the code. -If there are two assignments to a variable where the first one is a movable expression -and the variable is not used between the two assignments (and the second is not inside -a loop or conditional, the first one is not inside), the first assignment is removed. + a := E + let a_1 := a + mstore(a_1, 1) -This step also removes movable expression statements. +This is a very simple equivalence transform, but when we now run the +Common Subexpression Eliminator, it will replace all occurrences of ``a_1`` +by ``a`` (until ``a`` is re-assigned). The Unused Pruner will then +eliminate the variable ``a_1`` altogether and thus fully reverse the +SSA transform. +### Stack Compressor -## Function Unifier +One problem that makes code generation for the Ethereum Virtual Machine +hard is the fact that there is a hard limit of 16 slots for reaching +down the expression stack. This more or less translates to a limit +of 16 local variables. The stack compressor takes Yul code and +compiles it to EVM bytecode. Whenever the stack difference is too +large, it records the function this happened in. -## Expression Simplifier +For each function that caused such a problem, the Rematerialiser +is called with a special request to aggressively eliminate specific +variables sorted by the cost of their values. -This step can only be applied for the EVM-flavoured dialect of Yul. It applies -simple rules like ``x + 0 == x`` to simplify expressions. +On failure, this procedure is repeated multiple times. -## Ineffective Statement Remover +### Rematerialiser + +The rematerialisation stage tries to replace variable references by the expression that +was last assigned to the variable. This is of course only beneficial if this expression +is comparatively cheap to evaluate. Furthermore, it is only semantically equivalent if +the value of the expression did not change between the point of assignment and the +point of use. The main benefit of this stage is that it can save stack slots if it +leads to a variable being eliminated completely (see below), but it can also +save a DUP opcode on the EVM if the expression is very cheap. -This step removes statements that have no side-effects. +The Rematerialiser uses the Dataflow Analyzer to track the current values of variables, +which are always movable. +If the value is very cheap or the variable was explicitly requested to be eliminated, +the variable reference is replaced by its current value. ## WebAssembly specific diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index eb79be713965..36f63848fecd 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -149,6 +149,16 @@ void RedundantAssignEliminator::operator()(ForLoop const& _forLoop) join(zeroRuns); } +void RedundantAssignEliminator::operator()(Break const&) +{ + yulAssert(false, "Not implemented yet."); +} + +void RedundantAssignEliminator::operator()(Continue const&) +{ + yulAssert(false, "Not implemented yet."); +} + void RedundantAssignEliminator::operator()(Block const& _block) { // This will set all variables that are declared in this diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h index 2f4dd380ff07..e35c0143e6f1 100644 --- a/libyul/optimiser/RedundantAssignEliminator.h +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -112,6 +112,8 @@ class RedundantAssignEliminator: public ASTWalker void operator()(Switch const& _switch) override; void operator()(FunctionDefinition const&) override; void operator()(ForLoop const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; void operator()(Block const& _block) override; static void run(Dialect const& _dialect, Block& _ast); diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index 34ee26a16c44..dd94a4ad3397 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -21,10 +21,15 @@ #include #include +#include +#include + using namespace std; using namespace dev; using namespace yul; +using OptionalStatements = boost::optional>; + namespace { ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _location, Expression&& _expression) @@ -36,6 +41,86 @@ ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _ }}; } +void removeEmptyDefaultFromSwitch(Switch& _switchStmt) +{ + boost::remove_erase_if( + _switchStmt.cases, + [](Case const& _case) { return !_case.value && _case.body.statements.empty(); } + ); +} + +void removeEmptyCasesFromSwitch(Switch& _switchStmt) +{ + bool hasDefault = boost::algorithm::any_of( + _switchStmt.cases, + [](Case const& _case) { return !_case.value; } + ); + + if (hasDefault) + return; + + boost::remove_erase_if( + _switchStmt.cases, + [](Case const& _case) { return _case.body.statements.empty(); } + ); +} + +OptionalStatements replaceConstArgSwitch(Switch& _switchStmt, u256 const& _constExprVal) +{ + Block* matchingCaseBlock = nullptr; + Case* defaultCase = nullptr; + + for (auto& _case: _switchStmt.cases) + { + if (_case.value && valueOfLiteral(*_case.value) == _constExprVal) + { + matchingCaseBlock = &_case.body; + break; + } + else if (!_case.value) + defaultCase = &_case; + } + + if (!matchingCaseBlock && defaultCase) + matchingCaseBlock = &defaultCase->body; + + OptionalStatements s = vector{}; + + if (matchingCaseBlock) + s->emplace_back(std::move(*matchingCaseBlock)); + + return s; +} + +OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt) +{ + yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!"); + + auto& switchCase = _switchStmt.cases.front(); + auto loc = locationOf(*_switchStmt.expression); + if (switchCase.value) + { + OptionalStatements s = vector{}; + s->emplace_back(If{ + std::move(_switchStmt.location), + make_unique(FunctionalInstruction{ + std::move(loc), + solidity::Instruction::EQ, + {std::move(*switchCase.value), std::move(*_switchStmt.expression)} + }), + std::move(switchCase.body) + }); + return s; + } + else + { + OptionalStatements s = vector{}; + s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression))); + s->emplace_back(std::move(switchCase.body)); + return s; + } +} + } void StructuralSimplifier::operator()(Block& _block) @@ -45,6 +130,19 @@ void StructuralSimplifier::operator()(Block& _block) popScope(); } +OptionalStatements StructuralSimplifier::reduceNoCaseSwitch(Switch& _switchStmt) const +{ + yulAssert(_switchStmt.cases.empty(), "Expected no case!"); + + auto loc = locationOf(*_switchStmt.expression); + + OptionalStatements s = vector{}; + + s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression))); + + return s; +} + boost::optional StructuralSimplifier::hasLiteralValue(Expression const& _expression) const { Expression const* expr = &_expression; @@ -59,7 +157,7 @@ boost::optional StructuralSimplifier::hasLiteralValue(Expression cons if (expr && expr->type() == typeid(Literal)) { Literal const& literal = boost::get(*expr); - return valueOfNumberLiteral(literal); + return valueOfLiteral(literal); } return boost::optional(); @@ -67,7 +165,6 @@ boost::optional StructuralSimplifier::hasLiteralValue(Expression cons void StructuralSimplifier::simplify(std::vector& _statements) { - using OptionalStatements = boost::optional>; GenericFallbackReturnsVisitor const visitor( [&](If& _ifStmt) -> OptionalStatements { if (_ifStmt.body.statements.empty()) @@ -83,58 +180,18 @@ void StructuralSimplifier::simplify(std::vector& _statements) return {}; }, [&](Switch& _switchStmt) -> OptionalStatements { - if (_switchStmt.cases.size() == 1) - { - auto& switchCase = _switchStmt.cases.front(); - auto loc = locationOf(*_switchStmt.expression); - if (switchCase.value) - { - OptionalStatements s = vector{}; - s->emplace_back(If{ - std::move(_switchStmt.location), - make_unique(FunctionalInstruction{ - std::move(loc), - solidity::Instruction::EQ, - {std::move(*switchCase.value), std::move(*_switchStmt.expression)} - }), std::move(switchCase.body)}); - return s; - } - else - { - OptionalStatements s = vector{}; - s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression))); - s->emplace_back(std::move(switchCase.body)); - return s; - } - } - else if (boost::optional const constExprVal = hasLiteralValue(*_switchStmt.expression)) - { - Block* matchingCaseBlock = nullptr; - Case* defaultCase = nullptr; - - for (auto& _case: _switchStmt.cases) - { - if (_case.value && valueOfNumberLiteral(*_case.value) == constExprVal) - { - matchingCaseBlock = &_case.body; - break; - } - else if (!_case.value) - defaultCase = &_case; - } - - if (!matchingCaseBlock && defaultCase) - matchingCaseBlock = &defaultCase->body; + if (boost::optional const constExprVal = hasLiteralValue(*_switchStmt.expression)) + return replaceConstArgSwitch(_switchStmt, constExprVal.get()); - OptionalStatements s = vector{}; + removeEmptyDefaultFromSwitch(_switchStmt); + removeEmptyCasesFromSwitch(_switchStmt); - if (matchingCaseBlock) - s->emplace_back(std::move(*matchingCaseBlock)); + if (_switchStmt.cases.empty()) + return reduceNoCaseSwitch(_switchStmt); + else if (_switchStmt.cases.size() == 1) + return reduceSingleCaseSwitch(_switchStmt); - return s; - } - else - return {}; + return {}; }, [&](ForLoop& _forLoop) -> OptionalStatements { if (expressionAlwaysFalse(*_forLoop.condition)) @@ -148,10 +205,11 @@ void StructuralSimplifier::simplify(std::vector& _statements) _statements, [&](Statement& _stmt) -> OptionalStatements { - visit(_stmt); OptionalStatements result = boost::apply_visitor(visitor, _stmt); if (result) simplify(*result); + else + visit(_stmt); return result; } ); diff --git a/libyul/optimiser/StructuralSimplifier.h b/libyul/optimiser/StructuralSimplifier.h index 7bf8b7ec477d..222324700593 100644 --- a/libyul/optimiser/StructuralSimplifier.h +++ b/libyul/optimiser/StructuralSimplifier.h @@ -28,10 +28,13 @@ namespace yul * - replace if with empty body with pop(condition) * - replace if with true condition with its body * - remove if with false condition + * - remove empty default switch case + * - remove empty switch case if no default case exists + * - replace switch with no cases with pop(expression) * - turn switch with single case into if * - replace switch with only default case with pop(expression) and body * - replace switch with const expr with matching case body - * - remove for with false condition + * - replace for with false condition by its initialization part * * Prerequisites: Disambiguator * @@ -49,6 +52,7 @@ class StructuralSimplifier: public DataFlowAnalyzer bool expressionAlwaysTrue(Expression const& _expression); bool expressionAlwaysFalse(Expression const& _expression); boost::optional hasLiteralValue(Expression const& _expression) const; + boost::optional> reduceNoCaseSwitch(Switch& _switchStmt) const; }; } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 5a623e99e7db..ef9026999ca4 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -80,8 +81,16 @@ void OptimiserSuite::run( NameDispenser dispenser{*_dialect, ast}; - for (size_t i = 0; i < 4; i++) + size_t codeSize = 0; + for (size_t rounds = 0; rounds < 12; ++rounds) { + { + size_t newSize = CodeSize::codeSizeIncludingFunctions(ast); + if (newSize == codeSize) + break; + codeSize = newSize; + } + { // Turn into SSA and simplify ExpressionSplitter{*_dialect, dispenser}(ast); diff --git a/libyul/optimiser/SyntacticalEquality.h b/libyul/optimiser/SyntacticalEquality.h index af240717b9de..c5e722c74b8d 100644 --- a/libyul/optimiser/SyntacticalEquality.h +++ b/libyul/optimiser/SyntacticalEquality.h @@ -55,6 +55,8 @@ class SyntacticallyEqual bool statementEqual(Switch const& _lhs, Switch const& _rhs); bool switchCaseEqual(Case const& _lhs, Case const& _rhs); bool statementEqual(ForLoop const& _lhs, ForLoop const& _rhs); + bool statementEqual(Break const&, Break const&) { return true; } + bool statementEqual(Continue const&, Continue const&) { return true; } bool statementEqual(Block const& _lhs, Block const& _rhs); private: bool statementEqual(Instruction const& _lhs, Instruction const& _rhs); diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index 65db8025beef..f9878e4dadc5 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -64,8 +64,8 @@ void UnusedPruner::operator()(Block& _block) { VariableDeclaration& varDecl = boost::get(statement); // Multi-variable declarations are special. We can only remove it - // if all vairables are unused and the right-hand-side is either - // movable or it return a single value. In the latter case, we + // if all variables are unused and the right-hand-side is either + // movable or it returns a single value. In the latter case, we // replace `let a := f()` by `pop(f())` (in pure Yul, this will be // `drop(f())`). if (boost::algorithm::none_of( diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 820ec55aefc2..f70c64b3ff5c 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -202,6 +202,31 @@ class ExecutionFramework return m_blockNumber; } + template + static bytes encodeArray(bool _dynamicallySized, bool _dynamicallyEncoded, Range const& _elements) + { + bytes result; + if (_dynamicallySized) + result += encode(u256(_elements.size())); + if (_dynamicallyEncoded) + { + u256 offset = u256(_elements.size()) * 32; + std::vector subEncodings; + for (auto const& element: _elements) + { + result += encode(offset); + subEncodings.emplace_back(encode(element)); + offset += subEncodings.back().size(); + } + for (auto const& subEncoding: subEncodings) + result += subEncoding; + } + else + for (auto const& element: _elements) + result += encode(element); + return result; + } + private: template auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments) diff --git a/test/Metadata.cpp b/test/Metadata.cpp index 41e98b486412..b0f3c5994e77 100644 --- a/test/Metadata.cpp +++ b/test/Metadata.cpp @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include @@ -31,7 +33,7 @@ namespace dev namespace test { -bytes bytecodeSansMetadata(bytes const& _bytecode) +bytes onlyMetadata(bytes const& _bytecode) { unsigned size = _bytecode.size(); if (size < 5) @@ -43,6 +45,14 @@ bytes bytecodeSansMetadata(bytes const& _bytecode) unsigned char firstByte = _bytecode[size - metadataSize - 2]; if (firstByte != 0xa1 && firstByte != 0xa2) return bytes{}; + return bytes(_bytecode.end() - metadataSize - 2, _bytecode.end() - 2); +} + +bytes bytecodeSansMetadata(bytes const& _bytecode) +{ + unsigned metadataSize = onlyMetadata(_bytecode).size(); + if (metadataSize == 0) + return bytes{}; return bytes(_bytecode.begin(), _bytecode.end() - metadataSize - 2); } @@ -51,6 +61,116 @@ string bytecodeSansMetadata(string const& _bytecode) return toHex(bytecodeSansMetadata(fromHex(_bytecode, WhenError::Throw))); } +DEV_SIMPLE_EXCEPTION(CBORException); + +class TinyCBORParser +{ +public: + explicit TinyCBORParser(bytes const& _metadata): m_pos(0), m_metadata(_metadata) + { + assertThrow((m_pos + 1) < _metadata.size(), CBORException, "Input too short."); + } + unsigned mapItemCount() + { + assertThrow(nextType() == MajorType::Map, CBORException, "Fixed-length map expected."); + return readLength(); + } + string readKey() + { + return readString(); + } + string readValue() + { + switch(nextType()) + { + case MajorType::ByteString: + return toHex(readBytes(readLength())); + case MajorType::TextString: + return readString(); + case MajorType::SimpleData: + { + unsigned value = nextImmediate(); + m_pos++; + if (value == 20) + return "false"; + else if (value == 21) + return "true"; + else + assertThrow(false, CBORException, "Unsupported simple value (not a boolean)."); + } + default: + assertThrow(false, CBORException, "Unsupported value type."); + } + } +private: + enum class MajorType + { + ByteString, + TextString, + Map, + SimpleData + }; + MajorType nextType() const + { + unsigned value = (m_metadata.at(m_pos) >> 5) & 0x7; + switch (value) + { + case 2: return MajorType::ByteString; + case 3: return MajorType::TextString; + case 5: return MajorType::Map; + case 7: return MajorType::SimpleData; + default: assertThrow(false, CBORException, "Unsupported major type."); + } + } + unsigned nextImmediate() const { return m_metadata.at(m_pos) & 0x1f; } + unsigned readLength() + { + unsigned length = m_metadata.at(m_pos++) & 0x1f; + if (length < 24) + return length; + if (length == 24) + return m_metadata.at(m_pos++); + // Unsupported length kind. (Only by this parser.) + assertThrow(false, CBORException, string("Unsupported length ") + to_string(length)); + } + bytes readBytes(unsigned length) + { + bytes ret{m_metadata.begin() + m_pos, m_metadata.begin() + m_pos + length}; + m_pos += length; + return ret; + } + string readString() + { + // Expect a text string. + assertThrow(nextType() == MajorType::TextString, CBORException, "String expected."); + bytes tmp{readBytes(readLength())}; + return string{tmp.begin(), tmp.end()}; + } + unsigned m_pos; + bytes const& m_metadata; +}; + +boost::optional> parseCBORMetadata(bytes const& _metadata) +{ + try + { + TinyCBORParser parser(_metadata); + map ret; + unsigned count = parser.mapItemCount(); + for (unsigned i = 0; i < count; i++) + { + string key = parser.readKey(); + string value = parser.readValue(); + ret[move(key)] = move(value); + } + return ret; + } + catch (CBORException const&) + { + return {}; + } +} + bool isValidMetadata(string const& _metadata) { Json::Value metadata; diff --git a/test/Metadata.h b/test/Metadata.h index 113a54bee35c..4c7a10b31205 100644 --- a/test/Metadata.h +++ b/test/Metadata.h @@ -20,6 +20,10 @@ */ #include + +#include + +#include #include namespace dev @@ -27,6 +31,9 @@ namespace dev namespace test { +/// Returns only the CBOR metadata. +bytes onlyMetadata(bytes const& _bytecode); + /// Returns the bytecode with the metadata hash stripped out. bytes bytecodeSansMetadata(bytes const& _bytecode); @@ -34,6 +41,15 @@ bytes bytecodeSansMetadata(bytes const& _bytecode); /// Throws exception on invalid hex string. std::string bytecodeSansMetadata(std::string const& _bytecode); +/// Parse CBOR metadata into a map. Expects the input CBOR to be a +/// fixed length map, with each key being a string. The values +/// are parsed as follows: +/// - strings into strings +/// - bytes into hex strings +/// - booleans into "true"/"false" strings +/// - everything else is invalid +boost::optional> parseCBORMetadata(bytes const& _metadata); + /// Expects a serialised metadata JSON and returns true if the /// content is valid metadata. bool isValidMetadata(std::string const& _metadata); diff --git a/test/cmdlineTests/data_storage/output b/test/cmdlineTests/data_storage/output index 42e2225a9ea8..335c3154e5be 100644 --- a/test/cmdlineTests/data_storage/output +++ b/test/cmdlineTests/data_storage/output @@ -2,6 +2,6 @@ ======= data_storage/input.sol:C ======= Gas estimation: construction: - 300 + 258600 = 258900 + 294 + 255000 = 255294 external: - f(): 258 \ No newline at end of file + f(): 252 diff --git a/test/cmdlineTests/gas_test_abiv2/output b/test/cmdlineTests/gas_test_abiv2/output index bc2d1f97bbc1..f555b7ffdb90 100644 --- a/test/cmdlineTests/gas_test_abiv2/output +++ b/test/cmdlineTests/gas_test_abiv2/output @@ -2,10 +2,10 @@ ======= gas_test_abiv2/input.sol:C ======= Gas estimation: construction: - 1147 + 1103000 = 1104147 + 1140 + 1096600 = 1097740 external: a(): 530 - b(uint256): 1124 + b(uint256): 1118 f1(uint256): 586 f2(uint256[],string[],uint16,address): infinite f3(uint16[],string[],uint16,address): infinite diff --git a/test/cmdlineTests/gas_test_abiv2_optimize_yul/output b/test/cmdlineTests/gas_test_abiv2_optimize_yul/output index c61965beae5e..8a77186cb309 100644 --- a/test/cmdlineTests/gas_test_abiv2_optimize_yul/output +++ b/test/cmdlineTests/gas_test_abiv2_optimize_yul/output @@ -2,10 +2,10 @@ ======= gas_test_abiv2_optimize_yul/input.sol:C ======= Gas estimation: construction: - 651 + 617400 = 618051 + 651 + 617200 = 617851 external: a(): 429 - b(uint256): 887 + b(uint256): 884 f1(uint256): 351 f2(uint256[],string[],uint16,address): infinite f3(uint16[],string[],uint16,address): infinite diff --git a/test/cmdlineTests/gas_test_dispatch/output b/test/cmdlineTests/gas_test_dispatch/output index a7d6c8d46f14..c33503f77178 100644 --- a/test/cmdlineTests/gas_test_dispatch/output +++ b/test/cmdlineTests/gas_test_dispatch/output @@ -2,52 +2,52 @@ ======= gas_test_dispatch/input.sol:Large ======= Gas estimation: construction: - 683 + 650600 = 651283 + 670 + 635000 = 635670 external: a(): 451 - b(uint256): 852 - f0(uint256): 433 - f1(uint256): 40764 - f2(uint256): 20705 - f3(uint256): 20793 - f4(uint256): 20771 - f5(uint256): 20749 - f6(uint256): 20772 - f7(uint256): 20684 - f8(uint256): 20684 - f9(uint256): 20706 - g0(uint256): 319 - g1(uint256): 40719 - g2(uint256): 20682 - g3(uint256): 20770 - g4(uint256): 20748 - g5(uint256): 20704 - g6(uint256): 20727 - g7(uint256): 20726 - g8(uint256): 20704 - g9(uint256): 20661 + b(uint256): 846 + f0(uint256): 427 + f1(uint256): 40752 + f2(uint256): 20693 + f3(uint256): 20781 + f4(uint256): 20759 + f5(uint256): 20737 + f6(uint256): 20760 + f7(uint256): 20672 + f8(uint256): 20672 + f9(uint256): 20694 + g0(uint256): 313 + g1(uint256): 40707 + g2(uint256): 20670 + g3(uint256): 20758 + g4(uint256): 20736 + g5(uint256): 20692 + g6(uint256): 20715 + g7(uint256): 20714 + g8(uint256): 20692 + g9(uint256): 20649 ======= gas_test_dispatch/input.sol:Medium ======= Gas estimation: construction: - 300 + 256800 = 257100 + 294 + 251200 = 251494 external: a(): 428 - b(uint256): 852 - f1(uint256): 40675 - f2(uint256): 20705 - f3(uint256): 20749 - g0(uint256): 319 - g7(uint256): 20704 - g8(uint256): 20682 - g9(uint256): 20638 + b(uint256): 846 + f1(uint256): 40663 + f2(uint256): 20693 + f3(uint256): 20737 + g0(uint256): 313 + g7(uint256): 20692 + g8(uint256): 20670 + g9(uint256): 20626 ======= gas_test_dispatch/input.sol:Small ======= Gas estimation: construction: - 129 + 83000 = 83129 + 129 + 81800 = 81929 external: fallback: 118 a(): 383 - b(uint256): 808 - f1(uint256): 40675 + b(uint256): 802 + f1(uint256): 40663 diff --git a/test/cmdlineTests/gas_test_dispatch_optimize/output b/test/cmdlineTests/gas_test_dispatch_optimize/output index fe1410212f49..34004f871497 100644 --- a/test/cmdlineTests/gas_test_dispatch_optimize/output +++ b/test/cmdlineTests/gas_test_dispatch_optimize/output @@ -2,52 +2,52 @@ ======= gas_test_dispatch_optimize/input.sol:Large ======= Gas estimation: construction: - 300 + 259200 = 259500 + 300 + 260000 = 260300 external: a(): 398 - b(uint256): 1108 + b(uint256): 1105 f0(uint256): 334 - f1(uint256): 40898 - f2(uint256): 20958 - f3(uint256): 21046 - f4(uint256): 21024 - f5(uint256): 21002 - f6(uint256): 20914 - f7(uint256): 20694 - f8(uint256): 20826 - f9(uint256): 20848 + f1(uint256): 40886 + f2(uint256): 20952 + f3(uint256): 21040 + f4(uint256): 21018 + f5(uint256): 20996 + f6(uint256): 20908 + f7(uint256): 20688 + f8(uint256): 20820 + f9(uint256): 20842 g0(uint256): 574 - g1(uint256): 40610 - g2(uint256): 20692 - g3(uint256): 20780 - g4(uint256): 20758 - g5(uint256): 20846 - g6(uint256): 20626 - g7(uint256): 20736 - g8(uint256): 20714 - g9(uint256): 20560 + g1(uint256): 40598 + g2(uint256): 20686 + g3(uint256): 20774 + g4(uint256): 20752 + g5(uint256): 20840 + g6(uint256): 20620 + g7(uint256): 20730 + g8(uint256): 20708 + g9(uint256): 20554 ======= gas_test_dispatch_optimize/input.sol:Medium ======= Gas estimation: construction: - 183 + 140200 = 140383 + 183 + 140400 = 140583 external: a(): 398 - b(uint256): 866 - f1(uint256): 40678 - f2(uint256): 20716 - f3(uint256): 20760 + b(uint256): 863 + f1(uint256): 40666 + f2(uint256): 20710 + f3(uint256): 20754 g0(uint256): 332 - g7(uint256): 20626 - g8(uint256): 20604 - g9(uint256): 20560 + g7(uint256): 20620 + g8(uint256): 20598 + g9(uint256): 20554 ======= gas_test_dispatch_optimize/input.sol:Small ======= Gas estimation: construction: - 117 + 64600 = 64717 + 111 + 63600 = 63711 external: fallback: 118 a(): 376 - b(uint256): 756 - f1(uint256): 40612 + b(uint256): 753 + f1(uint256): 40600 diff --git a/test/externalTests.sh b/test/externalTests.sh index 299e32139412..0ee492d9cee6 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -91,10 +91,15 @@ function test_truffle module.exports['compilers'] = {solc: {version: "$DIR/solc"} }; EOF - npx truffle compile - echo "Verify that the correct version ($SOLCVERSION) of the compiler was used to compile the contracts..." - grep -e "$SOLCVERSION" -r build/contracts > /dev/null - npm run test + for optimize in "{enabled: false }" "{enabled: true }" "{enabled: true, details: { yul: true } }" + do + rm -rf build || true + echo "module.exports['compilers']['solc']['settings'] = {optimizer: $optimize };" >> truffle*.js + npx truffle compile + echo "Verify that the correct version ($SOLCVERSION) of the compiler was used to compile the contracts..." + grep -e "$SOLCVERSION" -r build/contracts > /dev/null + npm run test + done ) rm -rf "$DIR" } diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 188aa56a73c7..6d76c201aa87 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -238,6 +238,36 @@ BOOST_AUTO_TEST_CASE(cse_associativity2) checkCSE(input, {Instruction::DUP2, Instruction::DUP2, Instruction::ADD, u256(5), Instruction::ADD}); } +BOOST_AUTO_TEST_CASE(cse_double_shift_right_overflow) +{ + if (dev::test::Options::get().evmVersion().hasBitwiseShifting()) + { + AssemblyItems input{ + Instruction::CALLVALUE, + u256(2), + Instruction::SHR, + u256(-1), + Instruction::SHR + }; + checkCSE(input, {u256(0)}); + } +} + +BOOST_AUTO_TEST_CASE(cse_double_shift_left_overflow) +{ + if (dev::test::Options::get().evmVersion().hasBitwiseShifting()) + { + AssemblyItems input{ + Instruction::DUP1, + u256(2), + Instruction::SHL, + u256(-1), + Instruction::SHL + }; + checkCSE(input, {u256(0)}); + } +} + BOOST_AUTO_TEST_CASE(cse_storage) { AssemblyItems input{ @@ -969,27 +999,60 @@ BOOST_AUTO_TEST_CASE(peephole_swap_comparison) BOOST_AUTO_TEST_CASE(peephole_truthy_and) { - AssemblyItems items{ - AssemblyItem(Tag, 1), - Instruction::BALANCE, - u256(0), - Instruction::NOT, - Instruction::AND, - AssemblyItem(PushTag, 1), - Instruction::JUMPI - }; - AssemblyItems expectation{ - AssemblyItem(Tag, 1), - Instruction::BALANCE, - AssemblyItem(PushTag, 1), - Instruction::JUMPI - }; - PeepholeOptimiser peepOpt(items); - BOOST_REQUIRE(peepOpt.optimise()); - BOOST_CHECK_EQUAL_COLLECTIONS( - items.begin(), items.end(), - expectation.begin(), expectation.end() - ); + AssemblyItems items{ + AssemblyItem(Tag, 1), + Instruction::BALANCE, + u256(0), + Instruction::NOT, + Instruction::AND, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + AssemblyItems expectation{ + AssemblyItem(Tag, 1), + Instruction::BALANCE, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + PeepholeOptimiser peepOpt(items); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + + +BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi) +{ + AssemblyItems items{ + AssemblyItem(Tag, 1), + u256(0), + Instruction::CALLDATALOAD, + Instruction::ISZERO, + Instruction::ISZERO, + AssemblyItem(PushTag, 1), + Instruction::JUMPI, + u256(0), + u256(0x20), + Instruction::RETURN + }; + AssemblyItems expectation{ + AssemblyItem(Tag, 1), + u256(0), + Instruction::CALLDATALOAD, + AssemblyItem(PushTag, 1), + Instruction::JUMPI, + u256(0), + u256(0x20), + Instruction::RETURN + }; + PeepholeOptimiser peepOpt(items); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); } BOOST_AUTO_TEST_CASE(jumpdest_removal) diff --git a/test/libsolidity/GasCosts.cpp b/test/libsolidity/GasCosts.cpp index 31ba4a0e9460..985dd3d24991 100644 --- a/test/libsolidity/GasCosts.cpp +++ b/test/libsolidity/GasCosts.cpp @@ -69,12 +69,12 @@ BOOST_AUTO_TEST_CASE(string_storage) compileAndRun(sourceCode); if (Options::get().evmVersion() <= EVMVersion::byzantium()) - CHECK_GAS(134435, 130591, 100); + CHECK_GAS(133899, 130591, 100); // This is only correct on >=Constantinople. else if (Options::get().useABIEncoderV2) - CHECK_GAS(151819, 136003, 100); + CHECK_GAS(151283, 136003, 100); else - CHECK_GAS(127225, 120159, 100); + CHECK_GAS(126689, 120159, 100); if (Options::get().evmVersion() >= EVMVersion::byzantium()) { callContractFunction("f()"); diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index abd659b61201..ebdb46545b33 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -320,6 +320,153 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_alias) } } +BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_1) +{ + CompilerStack c; + c.addSource("A.sol", R"( + pragma solidity >=0.0; + pragma experimental ABIEncoderV2; + + contract A + { + struct S { uint a; } + S public s; + function f(S memory _s) returns (S memory,S memory) { } + } + )"); + + c.addSource("B.sol", R"( + pragma solidity >=0.0; + pragma experimental ABIEncoderV2; + + import "./A.sol"; + contract B is A { } + )"); + + c.addSource("C.sol", R"( + pragma solidity >=0.0; + + import "./B.sol"; + contract C is B { } + )"); + + c.setEVMVersion(dev::test::Options::get().evmVersion()); + BOOST_CHECK(!c.compile()); + + int typeErrors = 0; + + // Sometimes we get the prerelease warning, sometimes not. + for (auto const& e: c.errors()) + { + if (e->type() != langutil::Error::Type::TypeError) + continue; + + typeErrors++; + + string const* msg = e->comment(); + BOOST_REQUIRE(msg); + BOOST_CHECK_EQUAL(*msg, std::string("Contract \"C\" does not use the new experimental ABI encoder but wants to inherit from a contract which uses types that require it. Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature.")); + } + BOOST_CHECK_EQUAL(typeErrors, 1); +} + +BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_2) +{ + CompilerStack c; + c.addSource("A.sol", R"( + pragma solidity >=0.0; + pragma experimental ABIEncoderV2; + + contract A + { + struct S { uint a; } + S public s; + function f(S memory _s) returns (S memory,S memory) { } + } + )"); + + c.addSource("B.sol", R"( + pragma solidity >=0.0; + + import "./A.sol"; + contract B is A { } + )"); + + c.addSource("C.sol", R"( + pragma solidity >=0.0; + + import "./B.sol"; + contract C is B { } + )"); + + c.setEVMVersion(dev::test::Options::get().evmVersion()); + BOOST_CHECK(!c.compile()); + + int typeErrors = 0; + + // Sometimes we get the prerelease warning, sometimes not. + for (auto const& e: c.errors()) + { + if (e->type() != langutil::Error::Type::TypeError) + continue; + + typeErrors++; + + string const* msg = e->comment(); + BOOST_REQUIRE(msg); + BOOST_CHECK_EQUAL(*msg, std::string("Contract \"B\" does not use the new experimental ABI encoder but wants to inherit from a contract which uses types that require it. Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature.")); + } + BOOST_CHECK_EQUAL(typeErrors, 1); +} + +BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_match) +{ + CompilerStack c; + c.addSource("A.sol", R"( + pragma solidity >=0.0; + pragma experimental ABIEncoderV2; + + contract A + { + struct S { uint a; } + S public s; + function f(S memory _s) public returns (S memory,S memory) { } + } + )"); + + c.addSource("B.sol", R"( + pragma solidity >=0.0; + pragma experimental ABIEncoderV2; + + import "./A.sol"; + contract B is A { } + )"); + + c.addSource("C.sol", R"( + pragma solidity >=0.0; + pragma experimental ABIEncoderV2; + + import "./B.sol"; + contract C is B { } + )"); + + c.setEVMVersion(dev::test::Options::get().evmVersion()); + BOOST_CHECK(c.compile()); + + int typeErrors = 0; + + // Sometimes we get the prerelease warning, sometimes not. + for (auto const& e: c.errors()) + { + if (e->type() != langutil::Error::Type::TypeError) + continue; + + typeErrors++; + } + + BOOST_CHECK_EQUAL(typeErrors, 0); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index 007ee2b6bf45..d1e0cd678c62 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -25,6 +25,8 @@ #include #include +using namespace std; + namespace dev { namespace solidity @@ -32,6 +34,18 @@ namespace solidity namespace test { +namespace +{ +map requireParsedCBORMetadata(bytes const& _bytecode) +{ + bytes cborMetadata = dev::test::onlyMetadata(_bytecode); + BOOST_REQUIRE(!cborMetadata.empty()); + boost::optional> tmp = dev::test::parseCBORMetadata(cborMetadata); + BOOST_REQUIRE(tmp); + return *tmp; +} +} + BOOST_AUTO_TEST_SUITE(Metadata) BOOST_AUTO_TEST_CASE(metadata_stamp) @@ -54,11 +68,10 @@ BOOST_AUTO_TEST_CASE(metadata_stamp) BOOST_CHECK(dev::test::isValidMetadata(metadata)); bytes hash = dev::swarmHash(metadata).asBytes(); BOOST_REQUIRE(hash.size() == 32); - BOOST_REQUIRE(bytecode.size() >= 2); - size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]); - BOOST_REQUIRE(metadataCBORSize < bytecode.size() - 2); - bytes expectation = bytes{0xa1, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} + hash; - BOOST_CHECK(std::equal(expectation.begin(), expectation.end(), bytecode.end() - metadataCBORSize - 2)); + auto const cborMetadata = requireParsedCBORMetadata(bytecode); + BOOST_CHECK(cborMetadata.size() == 1); + BOOST_CHECK(cborMetadata.count("bzzr0") == 1); + BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash)); } BOOST_AUTO_TEST_CASE(metadata_stamp_experimental) @@ -81,14 +94,12 @@ BOOST_AUTO_TEST_CASE(metadata_stamp_experimental) BOOST_CHECK(dev::test::isValidMetadata(metadata)); bytes hash = dev::swarmHash(metadata).asBytes(); BOOST_REQUIRE(hash.size() == 32); - BOOST_REQUIRE(bytecode.size() >= 2); - size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]); - BOOST_REQUIRE(metadataCBORSize < bytecode.size() - 2); - bytes expectation = - bytes{0xa2, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} + - hash + - bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5}; - BOOST_CHECK(std::equal(expectation.begin(), expectation.end(), bytecode.end() - metadataCBORSize - 2)); + auto const cborMetadata = requireParsedCBORMetadata(bytecode); + BOOST_CHECK(cborMetadata.size() == 2); + BOOST_CHECK(cborMetadata.count("bzzr0") == 1); + BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash)); + BOOST_CHECK(cborMetadata.count("experimental") == 1); + BOOST_CHECK(cborMetadata.at("experimental") == "true"); } BOOST_AUTO_TEST_CASE(metadata_relevant_sources) diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 6db4c2a75a54..f8c9fa110cd3 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -75,12 +75,12 @@ bool SemanticTest::run(ostream& _stream, string const& _linePrefix, bool const _ AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; for (auto const& test: m_tests) _stream << test.format(_linePrefix, false, _formatted) << endl; - + _stream << endl; AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl; for (auto const& test: m_tests) _stream << test.format(_linePrefix, true, _formatted) << endl; - AnsiColorized(_stream, _formatted, {BOLD, RED}) << _linePrefix + AnsiColorized(_stream, _formatted, {BOLD, RED}) << _linePrefix << endl << _linePrefix << "Attention: Updates on the test will apply the detected format displayed." << endl; return false; } diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 63f2b3a622af..3acb562575b0 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -755,24 +755,23 @@ BOOST_AUTO_TEST_CASE(library_function) library test { struct StructType { uint a; } function f(StructType storage b, uint[] storage c, test d) public returns (uint[] memory e, StructType storage f) { f = f; } + function f1(uint[] memory c, test d) public pure returns (uint[] memory e) { } } )"; char const* interface = R"( [ { - "constant" : false, + "constant" : true, "payable" : false, - "stateMutability": "nonpayable", - "name": "f", + "stateMutability": "pure", + "name": "f1", "inputs": [ - { "name": "b", "type": "test.StructType storage" }, - { "name": "c", "type": "uint256[] storage" }, + { "name": "c", "type": "uint256[]" }, { "name": "d", "type": "test" } ], "outputs": [ - { "name": "e", "type": "uint256[]" }, - { "name": "f", "type": "test.StructType storage" } + { "name": "e", "type": "uint256[]" } ], "type" : "function" } @@ -1040,13 +1039,13 @@ BOOST_AUTO_TEST_CASE(structs_in_libraries) library L { struct S { uint a; T[] sub; bytes b; } struct T { uint[2] x; } - function f(L.S storage s) public {} - function g(L.S memory s) public {} + function f(L.S storage s) public view {} + function g(L.S memory s) public view {} } )"; char const* interface = R"( [{ - "constant": false, + "constant": true, "inputs": [ { "components": [ @@ -1076,24 +1075,9 @@ BOOST_AUTO_TEST_CASE(structs_in_libraries) "name": "g", "outputs": [], "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "s", - "type": "L.S storage" - } - ], - "name": "f", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" - }] - )"; + }])"; checkInterface(sourceCode, "L", interface); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index d7507aa545e4..0dc1ad22c578 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -32,6 +32,7 @@ #include +#include #include #include @@ -2620,43 +2621,6 @@ BOOST_AUTO_TEST_CASE(packed_ripemd160) testContractAgainstCpp("a(bytes32)", f, u256(-1)); } -BOOST_AUTO_TEST_CASE(ecrecover) -{ - char const* sourceCode = R"( - contract test { - function a(bytes32 h, uint8 v, bytes32 r, bytes32 s) public returns (address addr) { - return ecrecover(h, v, r, s); - } - } - )"; - compileAndRun(sourceCode); - u256 h("0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"); - uint8_t v = 28; - u256 r("0x73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f"); - u256 s("0xeeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"); - u160 addr("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"); - ABI_CHECK(callContractFunction("a(bytes32,uint8,bytes32,bytes32)", h, v, r, s), encodeArgs(addr)); -} - -BOOST_AUTO_TEST_CASE(ecrecover_abiV2) -{ - char const* sourceCode = R"( - pragma experimental ABIEncoderV2; - contract test { - function a(bytes32 h, uint8 v, bytes32 r, bytes32 s) public returns (address addr) { - return ecrecover(h, v, r, s); - } - } - )"; - compileAndRun(sourceCode); - u256 h("0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"); - uint8_t v = 28; - u256 r("0x73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f"); - u256 s("0xeeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"); - u160 addr("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"); - ABI_CHECK(callContractFunction("a(bytes32,uint8,bytes32,bytes32)", h, v, r, s), encodeArgs(addr)); -} - BOOST_AUTO_TEST_CASE(inter_contract_calls) { char const* sourceCode = R"( @@ -8054,11 +8018,11 @@ BOOST_AUTO_TEST_CASE(struct_named_constructor) BOOST_AUTO_TEST_CASE(calldata_array) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { function f(uint[2] calldata s) external pure returns (uint256 a, uint256 b) { - a = s[0]; - b = s[1]; + a = s[0]; + b = s[1]; } } )"; @@ -8070,12 +8034,12 @@ BOOST_AUTO_TEST_CASE(calldata_array) BOOST_AUTO_TEST_CASE(calldata_struct) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint256 a; uint256 b; } function f(S calldata s) external pure returns (uint256 a, uint256 b) { - a = s.a; - b = s.b; + a = s.a; + b = s.b; } } )"; @@ -8087,11 +8051,11 @@ BOOST_AUTO_TEST_CASE(calldata_struct) BOOST_AUTO_TEST_CASE(calldata_struct_and_ints) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint256 a; uint256 b; } function f(uint256 a, S calldata s, uint256 b) external pure returns (uint256, uint256, uint256, uint256) { - return (a, s.a, s.b, b); + return (a, s.a, s.b, b); } } )"; @@ -8104,17 +8068,17 @@ BOOST_AUTO_TEST_CASE(calldata_struct_and_ints) BOOST_AUTO_TEST_CASE(calldata_structs) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S1 { uint256 a; uint256 b; } struct S2 { uint256 a; } function f(S1 calldata s1, S2 calldata s2, S1 calldata s3) - external pure returns (uint256 a, uint256 b, uint256 c, uint256 d, uint256 e) { - a = s1.a; - b = s1.b; - c = s2.a; - d = s3.a; - e = s3.b; + external pure returns (uint256 a, uint256 b, uint256 c, uint256 d, uint256 e) { + a = s1.a; + b = s1.b; + c = s2.a; + d = s3.a; + e = s3.b; } } )"; @@ -8126,14 +8090,14 @@ BOOST_AUTO_TEST_CASE(calldata_structs) BOOST_AUTO_TEST_CASE(calldata_struct_array_member) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint256 a; uint256[2] b; uint256 c; } function f(S calldata s) external pure returns (uint256 a, uint256 b0, uint256 b1, uint256 c) { - a = s.a; - b0 = s.b[0]; - b1 = s.b[1]; - c = s.c; + a = s.a; + b0 = s.b[0]; + b1 = s.b[1]; + c = s.c; } } )"; @@ -8145,15 +8109,15 @@ BOOST_AUTO_TEST_CASE(calldata_struct_array_member) BOOST_AUTO_TEST_CASE(calldata_array_of_struct) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint256 a; uint256 b; } function f(S[] calldata s) external pure returns (uint256 l, uint256 a, uint256 b, uint256 c, uint256 d) { - l = s.length; - a = s[0].a; - b = s[0].b; - c = s[1].a; - d = s[1].b; + l = s.length; + a = s[0].a; + b = s[0].b; + c = s[1].a; + d = s[1].b; } } )"; @@ -8165,16 +8129,16 @@ BOOST_AUTO_TEST_CASE(calldata_array_of_struct) BOOST_AUTO_TEST_CASE(calldata_array_of_struct_to_memory) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint256 a; uint256 b; } function f(S[] calldata s) external pure returns (uint256 l, uint256 a, uint256 b, uint256 c, uint256 d) { - S[] memory m = s; - l = m.length; - a = m[0].a; - b = m[0].b; - c = m[1].a; - d = m[1].b; + S[] memory m = s; + l = m.length; + a = m[0].a; + b = m[0].b; + c = m[1].a; + d = m[1].b; } } )"; @@ -8187,12 +8151,12 @@ BOOST_AUTO_TEST_CASE(calldata_array_of_struct_to_memory) BOOST_AUTO_TEST_CASE(calldata_struct_to_memory) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint256 a; uint256 b; } function f(S calldata s) external pure returns (uint256, uint256) { - S memory m = s; - return (m.a, m.b); + S memory m = s; + return (m.a, m.b); } } )"; @@ -8204,12 +8168,12 @@ BOOST_AUTO_TEST_CASE(calldata_struct_to_memory) BOOST_AUTO_TEST_CASE(nested_calldata_struct) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S1 { uint256 a; uint256 b; } struct S2 { uint256 a; uint256 b; S1 s; uint256 c; } function f(S2 calldata s) external pure returns (uint256 a, uint256 b, uint256 sa, uint256 sb, uint256 c) { - return (s.a, s.b, s.s.a, s.s.b, s.c); + return (s.a, s.b, s.s.a, s.s.b, s.c); } } )"; @@ -8221,13 +8185,13 @@ BOOST_AUTO_TEST_CASE(nested_calldata_struct) BOOST_AUTO_TEST_CASE(nested_calldata_struct_to_memory) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S1 { uint256 a; uint256 b; } struct S2 { uint256 a; uint256 b; S1 s; uint256 c; } function f(S2 calldata s) external pure returns (uint256 a, uint256 b, uint256 sa, uint256 sb, uint256 c) { - S2 memory m = s; - return (m.a, m.b, m.s.a, m.s.b, m.c); + S2 memory m = s; + return (m.a, m.b, m.s.a, m.s.b, m.c); } } )"; @@ -8239,11 +8203,11 @@ BOOST_AUTO_TEST_CASE(nested_calldata_struct_to_memory) BOOST_AUTO_TEST_CASE(calldata_struct_short) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint256 a; uint256 b; } function f(S calldata) external pure returns (uint256) { - return msg.data.length; + return msg.data.length; } } )"; @@ -8262,12 +8226,12 @@ BOOST_AUTO_TEST_CASE(calldata_struct_short) BOOST_AUTO_TEST_CASE(calldata_struct_cleaning) { char const* sourceCode = R"( - pragma experimental ABIEncoderV2; + pragma experimental ABIEncoderV2; contract C { struct S { uint8 a; bytes1 b; } function f(S calldata s) external pure returns (uint256 a, bytes32 b) { - uint8 tmp1 = s.a; - bytes1 tmp2 = s.b; + uint8 tmp1 = s.a; + bytes1 tmp2 = s.b; assembly { a := tmp1 b := tmp2 @@ -8278,7 +8242,7 @@ BOOST_AUTO_TEST_CASE(calldata_struct_cleaning) )"; compileAndRun(sourceCode, 0, "C"); - // double check that the valid case goes through + // double check that the valid case goes through ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(0x12), bytes{0x34} + bytes(31,0)), encodeArgs(0x12, bytes{0x34} + bytes(31,0))); ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(0x1234), bytes{0x56, 0x78} + bytes(30,0)), encodeArgs(0x34, bytes{0x56} + bytes(31,0))); ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(-1), u256(-1)), encodeArgs(0xFF, bytes{0xFF} + bytes(31,0))); @@ -8294,10 +8258,10 @@ BOOST_AUTO_TEST_CASE(calldata_struct_function_type) return s.fn(42); } function g(uint256 a) external returns (uint256) { - return a * 3; + return a * 3; } function h(uint256 a) external returns (uint256) { - return 23; + return 23; } } )"; @@ -8309,6 +8273,348 @@ BOOST_AUTO_TEST_CASE(calldata_struct_function_type) ABI_CHECK(callContractFunctionNoEncoding("f((function))", fn_C_h), encodeArgs(23)); } +BOOST_AUTO_TEST_CASE(calldata_array_dynamic_bytes) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f1(bytes[1] calldata a) external returns (uint256, uint256, uint256, uint256) { + return (a[0].length, uint8(a[0][0]), uint8(a[0][1]), uint8(a[0][2])); + } + function f2(bytes[1] calldata a, bytes[1] calldata b) external returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) { + return (a[0].length, uint8(a[0][0]), uint8(a[0][1]), uint8(a[0][2]), b[0].length, uint8(b[0][0]), uint8(b[0][1])); + } + function g1(bytes[2] calldata a) external returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) { + return (a[0].length, uint8(a[0][0]), uint8(a[0][1]), uint8(a[0][2]), a[1].length, uint8(a[1][0]), uint8(a[1][1]), uint8(a[1][2])); + } + function g2(bytes[] calldata a) external returns (uint256[8] memory) { + return [a.length, a[0].length, uint8(a[0][0]), uint8(a[0][1]), a[1].length, uint8(a[1][0]), uint8(a[1][1]), uint8(a[1][2])]; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + bytes bytes010203 = bytes{1,2,3}+bytes(29,0); + bytes bytes040506 = bytes{4,5,6}+bytes(29,0); + bytes bytes0102 = bytes{1,2}+bytes(30,0); + ABI_CHECK( + callContractFunction("f1(bytes[1])", 0x20, 0x20, 3, bytes010203), + encodeArgs(3, 1, 2, 3) + ); + ABI_CHECK( + callContractFunction("f2(bytes[1],bytes[1])", 0x40, 0xA0, 0x20, 3, bytes010203, 0x20, 2, bytes0102), + encodeArgs(3, 1, 2, 3, 2, 1, 2) + ); + ABI_CHECK( + callContractFunction("g1(bytes[2])", 0x20, 0x40, 0x80, 3, bytes010203, 3, bytes040506), + encodeArgs(3, 1, 2, 3, 3, 4, 5, 6) + ); + // same offset for both arrays + ABI_CHECK( + callContractFunction("g1(bytes[2])", 0x20, 0x40, 0x40, 3, bytes010203), + encodeArgs(3, 1, 2, 3, 3, 1, 2, 3) + ); + ABI_CHECK( + callContractFunction("g2(bytes[])", 0x20, 2, 0x40, 0x80, 2, bytes0102, 3, bytes040506), + encodeArgs(2, 2, 1, 2, 3, 4, 5, 6) + ); +} + +BOOST_AUTO_TEST_CASE(calldata_dynamic_array_to_memory) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f(uint256[][] calldata a) external returns (uint, uint256[] memory) { + uint256[] memory m = a[0]; + return (a.length, m); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + ABI_CHECK( + callContractFunction("f(uint256[][])", 0x20, 1, 0x20, 2, 23, 42), + encodeArgs(1, 0x40, 2, 23, 42) + ); +} + +BOOST_AUTO_TEST_CASE(calldata_bytes_array_to_memory) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f(bytes[] calldata a) external returns (uint, uint, bytes memory) { + bytes memory m = a[0]; + return (a.length, m.length, m); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + ABI_CHECK( + callContractFunction("f(bytes[])", 0x20, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)), + encodeArgs(1, 2, 0x60, 2, bytes{'a','b'} + bytes(30, 0)) + ); + + ABI_CHECK( + callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, bytes(32, 'x')), + encodeArgs(1, 32, 0x60, 32, bytes(32, 'x')) + ); + bytes x_zero_a = bytes{'x'} + bytes(30, 0) + bytes{'a'}; + bytes a_zero_x = bytes{'a'} + bytes(30, 0) + bytes{'x'}; + bytes a_m_x = bytes{'a'} + bytes(30, 'm') + bytes{'x'}; + ABI_CHECK( + callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, x_zero_a), + encodeArgs(1, 32, 0x60, 32, x_zero_a) + ); + ABI_CHECK( + callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, a_zero_x), + encodeArgs(1, 32, 0x60, 32, a_zero_x) + ); + ABI_CHECK( + callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, a_m_x), + encodeArgs(1, 32, 0x60, 32, a_m_x) + ); +} + +BOOST_AUTO_TEST_CASE(calldata_bytes_array_bounds) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f(bytes[] calldata a, uint256 i) external returns (uint) { + return uint8(a[0][i]); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + ABI_CHECK( + callContractFunction("f(bytes[],uint256)", 0x40, 0, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)), + encodeArgs('a') + ); + ABI_CHECK( + callContractFunction("f(bytes[],uint256)", 0x40, 1, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)), + encodeArgs('b') + ); + ABI_CHECK( + callContractFunction("f(bytes[],uint256)", 0x40, 2, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)), + encodeArgs() + ); +} + +BOOST_AUTO_TEST_CASE(calldata_string_array) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f(string[] calldata a) external returns (uint, uint, uint, string memory) { + string memory s1 = a[0]; + bytes memory m1 = bytes(s1); + return (a.length, m1.length, uint8(m1[0]), s1); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + ABI_CHECK( + callContractFunction("f(string[])", 0x20, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)), + encodeArgs(1, 2, 'a', 0x80, 2, bytes{'a', 'b'} + bytes(30, 0)) + ); +} + +BOOST_AUTO_TEST_CASE(calldata_array_two_dimensional) +{ + vector> data { + { 0x0A01, 0x0A02, 0x0A03 }, + { 0x0B01, 0x0B02, 0x0B03, 0x0B04 } + }; + + for (bool outerDynamicallySized: { true, false }) + { + string arrayType = outerDynamicallySized ? "uint256[][]" : "uint256[][2]"; + string sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function test()" + arrayType + R"( calldata a) external returns (uint256) { + return a.length; + } + function test()" + arrayType + R"( calldata a, uint256 i) external returns (uint256) { + return a[i].length; + } + function test()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) { + return a[i][j]; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + bytes encoding = encodeArray( + outerDynamicallySized, + true, + data | boost::adaptors::transformed([&](vector const& _values) { + return encodeArray(true, false, _values); + }) + ); + + ABI_CHECK(callContractFunction("test(" + arrayType + ")", 0x20, encoding), encodeArgs(data.size())); + for (size_t i = 0; i < data.size(); i++) + { + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, i, encoding), encodeArgs(data[i].size())); + for (size_t j = 0; j < data[i].size(); j++) + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j])); + // out of bounds access + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, data[i].size(), encoding), encodeArgs()); + } + // out of bounds access + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, data.size(), encoding), encodeArgs()); + } +} + +BOOST_AUTO_TEST_CASE(calldata_array_dynamic_three_dimensional) +{ + vector>> data { + { + { 0x010A01, 0x010A02, 0x010A03 }, + { 0x010B01, 0x010B02, 0x010B03 } + }, + { + { 0x020A01, 0x020A02, 0x020A03 }, + { 0x020B01, 0x020B02, 0x020B03 } + } + }; + + for (bool outerDynamicallySized: { true, false }) + for (bool middleDynamicallySized: { true, false }) + for (bool innerDynamicallySized: { true, false }) + { + // only test dynamically encoded arrays + if (!outerDynamicallySized && !middleDynamicallySized && !innerDynamicallySized) + continue; + + string arrayType = "uint256"; + arrayType += innerDynamicallySized ? "[]" : "[3]"; + arrayType += middleDynamicallySized ? "[]" : "[2]"; + arrayType += outerDynamicallySized ? "[]" : "[2]"; + + string sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function test()" + arrayType + R"( calldata a) external returns (uint256) { + return a.length; + } + function test()" + arrayType + R"( calldata a, uint256 i) external returns (uint256) { + return a[i].length; + } + function test()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) { + return a[i][j].length; + } + function test()" + arrayType + R"( calldata a, uint256 i, uint256 j, uint256 k) external returns (uint256) { + return a[i][j][k]; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + bytes encoding = encodeArray( + outerDynamicallySized, + middleDynamicallySized || innerDynamicallySized, + data | boost::adaptors::transformed([&](auto const& _middleData) { + return encodeArray( + middleDynamicallySized, + innerDynamicallySized, + _middleData | boost::adaptors::transformed([&](auto const& _values) { + return encodeArray(innerDynamicallySized, false, _values); + }) + ); + }) + ); + + ABI_CHECK(callContractFunction("test(" + arrayType + ")", 0x20, encoding), encodeArgs(data.size())); + for (size_t i = 0; i < data.size(); i++) + { + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, i, encoding), encodeArgs(data[i].size())); + for (size_t j = 0; j < data[i].size(); j++) + { + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j].size())); + for (size_t k = 0; k < data[i][j].size(); k++) + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, k, encoding), encodeArgs(data[i][j][k])); + // out of bounds access + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, data[i][j].size(), encoding), encodeArgs()); + } + // out of bounds access + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, data[i].size(), encoding), encodeArgs()); + } + // out of bounds access + ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, data.size(), encoding), encodeArgs()); + } +} + +BOOST_AUTO_TEST_CASE(calldata_array_dynamic_invalid) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f(uint256[][] calldata a) external returns (uint) { + return 42; + } + function g(uint256[][] calldata a) external returns (uint) { + a[0]; + return 42; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + // valid access stub + ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 0), encodeArgs(42)); + // invalid on argument decoding + ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 1), encodeArgs()); + // invalid on outer access + ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 1, 0x20), encodeArgs(42)); + ABI_CHECK(callContractFunction("g(uint256[][])", 0x20, 1, 0x20), encodeArgs()); + // invalid on inner access + ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 1, 0x20, 2, 0x42), encodeArgs(42)); + ABI_CHECK(callContractFunction("g(uint256[][])", 0x20, 1, 0x20, 2, 0x42), encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(calldata_array_dynamic_invalid_static_middle) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f(uint256[][1][] calldata a) external returns (uint) { + return 42; + } + function g(uint256[][1][] calldata a) external returns (uint) { + a[0]; + return 42; + } + function h(uint256[][1][] calldata a) external returns (uint) { + a[0][0]; + return 42; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + + // valid access stub + ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 0), encodeArgs(42)); + // invalid on argument decoding + ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1), encodeArgs()); + // invalid on outer access + ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1, 0x20), encodeArgs(42)); + ABI_CHECK(callContractFunction("g(uint256[][1][])", 0x20, 1, 0x20), encodeArgs()); + // invalid on inner access + ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1, 0x20, 0x20), encodeArgs(42)); + ABI_CHECK(callContractFunction("g(uint256[][1][])", 0x20, 1, 0x20, 0x20), encodeArgs(42)); + ABI_CHECK(callContractFunction("h(uint256[][1][])", 0x20, 1, 0x20, 0x20), encodeArgs()); + ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1, 0x20, 0x20, 1), encodeArgs(42)); + ABI_CHECK(callContractFunction("g(uint256[][1][])", 0x20, 1, 0x20, 0x20, 1), encodeArgs(42)); + ABI_CHECK(callContractFunction("h(uint256[][1][])", 0x20, 1, 0x20, 0x20, 1), encodeArgs()); +} + BOOST_AUTO_TEST_CASE(literal_strings) { char const* sourceCode = R"( @@ -11071,69 +11377,6 @@ BOOST_AUTO_TEST_CASE(mutex) BOOST_CHECK_EQUAL(balanceAt(fund), 460); } -BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input) -{ - // ecrecover should return zero for malformed input - // (v should be 27 or 28, not 1) - // Note that the precompile does not return zero but returns nothing. - char const* sourceCode = R"( - contract C { - function f() public returns (address) { - return ecrecover(bytes32(uint(-1)), 1, bytes32(uint(2)), bytes32(uint(3))); - } - } - )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0))); -} - -BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input_proper) -{ - char const* sourceCode = R"( - contract C { - function f() public returns (address) { - return recover( - 0x77e5189111eb6557e8a637b27ef8fbb15bc61d61c2f00cc48878f3a296e5e0ca, - 0, // invalid v value - 0x6944c77849b18048f6abe0db8084b0d0d0689cdddb53d2671c36967b58691ad4, - 0xef4f06ba4f78319baafd0424365777241af4dfd3da840471b4b4b087b7750d0d, - 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c, - 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c - ); - } - function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s, uint blockExpired, bytes32 salt) - public returns (address) - { - require(hash == keccak256(abi.encodePacked(blockExpired, salt))); - return ecrecover(hash, v, r, s); - } - } - )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0))); -} - -BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input_asm) -{ - char const* sourceCode = R"( - contract C { - function f() public returns (address) { - assembly { - mstore(mload(0x40), 0xca35b7d915458ef540ade6068dfe2f44e8fa733c) - } - return ecrecover( - 0x77e5189111eb6557e8a637b27ef8fbb15bc61d61c2f00cc48878f3a296e5e0ca, - 0, // invalid v value - 0x6944c77849b18048f6abe0db8084b0d0d0689cdddb53d2671c36967b58691ad4, - 0xef4f06ba4f78319baafd0424365777241af4dfd3da840471b4b4b087b7750d0d - ); - } - } - )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0))); -} - BOOST_AUTO_TEST_CASE(calling_nonexisting_contract_throws) { char const* sourceCode = R"YY( @@ -14766,11 +15009,21 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople_combined) c := shl(0xd0, shl(0x40, a)) } } + function shl_combined_overflow(uint a) public returns (uint c) { + assembly { + c := shl(0x01, shl(not(0x00), a)) + } + } function shr_combined_large(uint a) public returns (uint c) { assembly { c := shr(0xd0, shr(0x40, a)) } } + function shr_combined_overflow(uint a) public returns (uint c) { + assembly { + c := shr(0x01, shr(not(0x00), a)) + } + } function sar_combined_large(uint a) public returns (uint c) { assembly { c := sar(0xd0, sar(0x40, a)) @@ -14809,8 +15062,10 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople_combined) BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); diff --git a/test/libsolidity/semanticTests/ecrecover/ecrecover.sol b/test/libsolidity/semanticTests/ecrecover/ecrecover.sol new file mode 100644 index 000000000000..1beb451de8b0 --- /dev/null +++ b/test/libsolidity/semanticTests/ecrecover/ecrecover.sol @@ -0,0 +1,12 @@ +contract test { + function a(bytes32 h, uint8 v, bytes32 r, bytes32 s) public returns (address addr) { + return ecrecover(h, v, r, s); + } +} +// ---- +// a(bytes32,uint8,bytes32,bytes32): +// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c, +// 28, +// 0x73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f, +// 0xeeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549 +// -> 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b diff --git a/test/libsolidity/semanticTests/ecrecover/ecrecover_abiV2.sol b/test/libsolidity/semanticTests/ecrecover/ecrecover_abiV2.sol new file mode 100644 index 000000000000..fa3adbff8e74 --- /dev/null +++ b/test/libsolidity/semanticTests/ecrecover/ecrecover_abiV2.sol @@ -0,0 +1,13 @@ +pragma experimental ABIEncoderV2; +contract test { + function a(bytes32 h, uint8 v, bytes32 r, bytes32 s) public returns (address addr) { + return ecrecover(h, v, r, s); + } +} +// ---- +// a(bytes32,uint8,bytes32,bytes32): +// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c, +// 28, +// 0x73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f, +// 0xeeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549 +// -> 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b diff --git a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input.sol b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input.sol new file mode 100644 index 000000000000..fe0cbf412065 --- /dev/null +++ b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input.sol @@ -0,0 +1,10 @@ +contract C { + // ecrecover should return zero for malformed input + // (v should be 27 or 28, not 1) + // Note that the precompile does not return zero but returns nothing. + function f() public returns (address) { + return ecrecover(bytes32(uint(-1)), 1, bytes32(uint(2)), bytes32(uint(3))); + } +} +// ---- +// f() -> 0 diff --git a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_asm.sol b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_asm.sol new file mode 100644 index 000000000000..5e8c3cdbf74f --- /dev/null +++ b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_asm.sol @@ -0,0 +1,15 @@ +contract C { + function f() public returns (address) { + assembly { + mstore(mload(0x40), 0xca35b7d915458ef540ade6068dfe2f44e8fa733c) + } + return ecrecover( + 0x77e5189111eb6557e8a637b27ef8fbb15bc61d61c2f00cc48878f3a296e5e0ca, + 0, // invalid v value + 0x6944c77849b18048f6abe0db8084b0d0d0689cdddb53d2671c36967b58691ad4, + 0xef4f06ba4f78319baafd0424365777241af4dfd3da840471b4b4b087b7750d0d + ); + } +} +// ---- +// f() -> 0 diff --git a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_proper.sol b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_proper.sol new file mode 100644 index 000000000000..511bd4e41c19 --- /dev/null +++ b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_proper.sol @@ -0,0 +1,20 @@ +contract C { + function f() public returns (address) { + return recover( + 0x77e5189111eb6557e8a637b27ef8fbb15bc61d61c2f00cc48878f3a296e5e0ca, + 0, // invalid v value + 0x6944c77849b18048f6abe0db8084b0d0d0689cdddb53d2671c36967b58691ad4, + 0xef4f06ba4f78319baafd0424365777241af4dfd3da840471b4b4b087b7750d0d, + 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c, + 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c + ); + } + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s, uint blockExpired, bytes32 salt) + public returns (address) + { + require(hash == keccak256(abi.encodePacked(blockExpired, salt))); + return ecrecover(hash, v, r, s); + } +} +// ---- +// f() -> 0 diff --git a/test/libsolidity/semanticTests/smoke_test.sol b/test/libsolidity/semanticTests/smoke_test.sol index 0b4b9e283df3..d25e61b8ae8a 100644 --- a/test/libsolidity/semanticTests/smoke_test.sol +++ b/test/libsolidity/semanticTests/smoke_test.sol @@ -1,6 +1,6 @@ contract C { function f() public returns (uint) { - return 1; + return 2; } function g(uint x, uint y) public returns (uint) { return x - y; @@ -8,18 +8,24 @@ contract C { function h() public payable returns (uint) { return f(); } - function x(bytes32 b) public returns (bytes32) { + function i(bytes32 b) public returns (bytes32) { return b; } - function t(bool b) public returns (bool) { + function j(bool b) public returns (bool) { return !b; } + function k(bytes32 b) public returns (bytes32) { + return b; + } + function s() public returns (uint256) { + return msg.data.length; + } } // ---- -// f() -> 1 +// f() -> 2 // g(uint256,uint256): 1, -2 -> 3 -// h(), 1 ether -> 1 -// j() -> FAILURE -// i() # Does not exist. # -> FAILURE # Reverts. # -// x(bytes32): 0x31 -> 0x31 -// t(bool): true -> false +// h(), 1 ether -> 2 +// i() -> FAILURE +// j(bool): true -> false +// k(bytes32): 0x31 -> 0x31 +// s(): hex"4200ef" -> 7 diff --git a/test/libsolidity/semanticTests/smoke_test_alignment.sol b/test/libsolidity/semanticTests/smoke_test_alignment.sol new file mode 100644 index 000000000000..b5e23c022866 --- /dev/null +++ b/test/libsolidity/semanticTests/smoke_test_alignment.sol @@ -0,0 +1,28 @@ +contract C { + uint256 public stateDecimal = 0x20; +} + +contract D { + bool public stateBool = true; + uint256 public stateDecimal = 42; + bytes32 public stateBytes = "\x42\x00\xef"; + + function internalStateDecimal() public returns (uint256) { + return (new C()).stateDecimal(); + } + + function update(bool _bool, uint256 _decimal, bytes32 _bytes) public returns (bool, uint256, bytes32) { + stateBool = _bool; + stateDecimal = _decimal; + stateBytes = _bytes; + return (stateBool, stateDecimal, stateBytes); + } +} +// ---- +// stateBool() -> true +// stateBool() -> right(true) +// stateDecimal() -> 42 +// stateDecimal() -> right(42) +// stateBytes() -> left(0x4200ef) +// internalStateDecimal() -> 0x20 +// update(bool,uint256,bytes32): false, -23, left(0x2300ef) -> false, -23, left(0x2300ef) diff --git a/test/libsolidity/semanticTests/structs/calldata/dynamic_nested.sol b/test/libsolidity/semanticTests/structs/calldata/dynamic_nested.sol new file mode 100644 index 000000000000..59cde317189a --- /dev/null +++ b/test/libsolidity/semanticTests/structs/calldata/dynamic_nested.sol @@ -0,0 +1,11 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S2 { uint256 b; } + struct S { uint256 a; S2[] children; } + function f(S calldata s) external pure returns (uint256, uint256, uint256, uint256) { + return (s.children.length, s.a, s.children[0].b, s.children[1].b); + } +} +// ---- +// f((uint256,(uint256)[])): 32, 17, 64, 2, 23, 42 -> 2, 17, 23, 42 diff --git a/test/libsolidity/semanticTests/structs/calldata/dynamically_encoded.sol b/test/libsolidity/semanticTests/structs/calldata/dynamically_encoded.sol new file mode 100644 index 000000000000..3acea52e7fa5 --- /dev/null +++ b/test/libsolidity/semanticTests/structs/calldata/dynamically_encoded.sol @@ -0,0 +1,10 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S { uint256[] a; } + function f(S calldata s) external pure returns (uint256 a, uint256 b, uint256 c) { + return (s.a.length, s.a[0], s.a[1]); + } +} +// ---- +// f((uint256[])): 32, 32, 2, 42, 23 -> 2, 42, 23 diff --git a/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol b/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol new file mode 100644 index 000000000000..1b77598d8309 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol @@ -0,0 +1,43 @@ +pragma experimental SMTChecker; + +/** + * @title MerkleProof + * @dev Merkle proof verification based on + * https://github.com/ameensol/merkle-tree-solidity/blob/master/src/MerkleProof.sol + */ +library MerkleProof { + /** + * @dev Verifies a Merkle proof proving the existence of a leaf in a Merkle tree. Assumes that each pair of leaves + * and each pair of pre-images are sorted. + * @param proof Merkle proof containing sibling hashes on the branch from the leaf to the root of the Merkle tree + * @param root Merkle root + * @param leaf Leaf of Merkle tree + */ + function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + bytes32 computedHash = leaf; + + for (uint256 i = 0; i < proof.length; i++) { + bytes32 proofElement = proof[i]; + + if (computedHash < proofElement) { + // Hash(current computed hash + current element of the proof) + computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); + } else { + // Hash(current element of the proof + current computed hash) + computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); + } + } + + // Check if the computed hash (root) is equal to the provided root + return computedHash == root; + } +} + +// ---- +// Warning: (755-767): Assertion checker does not yet support this expression. +// Warning: (988-1032): Assertion checker does not yet implement this type of function call. +// Warning: (988-1032): Internal error: Expression undefined for SMT solver. +// Warning: (1175-1219): Assertion checker does not yet implement this type of function call. +// Warning: (1175-1219): Internal error: Expression undefined for SMT solver. +// Warning: (755-767): Assertion checker does not yet support this expression. +// Warning: (769-772): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/simple/static_array.sol b/test/libsolidity/smtCheckerTests/simple/static_array.sol index be49ab7febc0..4147c0169ba9 100644 --- a/test/libsolidity/smtCheckerTests/simple/static_array.sol +++ b/test/libsolidity/smtCheckerTests/simple/static_array.sol @@ -7,6 +7,3 @@ contract C int[3*1] x; } // ---- -// Warning: (92-100): Assertion checker does not yet support the type of this variable. -// Warning: (149-159): Assertion checker does not yet support the type of this variable. -// Warning: (153-156): Assertion checker does not yet implement this operator on non-integer types. diff --git a/test/libsolidity/smtCheckerTests/special/msg_data.sol b/test/libsolidity/smtCheckerTests/special/msg_data.sol index b667f971dd56..dff997395c69 100644 --- a/test/libsolidity/smtCheckerTests/special/msg_data.sol +++ b/test/libsolidity/smtCheckerTests/special/msg_data.sol @@ -8,6 +8,4 @@ contract C } // ---- // Warning: (86-101): Assertion checker does not yet support this expression. -// Warning: (86-94): Assertion checker does not yet support this global variable. -// Warning: (86-101): Internal error: Expression undefined for SMT solver. // Warning: (79-106): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/typecast/enum_from_uint.sol b/test/libsolidity/smtCheckerTests/typecast/enum_from_uint.sol new file mode 100644 index 000000000000..44c252868d08 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/enum_from_uint.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C +{ + enum D { Left, Right } + function f(uint x) public pure { + require(x == 0); + D _a = D(x); + assert(_a == D.Left); + } +} +// ---- +// Warning: (132-136): Type conversion is not yet fully supported and might yield false positives. +// Warning: (140-160): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/typecast/enum_to_uint_max_value.sol b/test/libsolidity/smtCheckerTests/typecast/enum_to_uint_max_value.sol new file mode 100644 index 000000000000..b061777f97cc --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/enum_to_uint_max_value.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + enum D { Left, Right } + function f(D _a) public pure { + uint x = uint(_a); + assert(x < 10); + } +} +// ---- +// Warning: (113-121): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_1.sol b/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_1.sol new file mode 100644 index 000000000000..8cd4686529d3 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_1.sol @@ -0,0 +1,31 @@ +pragma experimental SMTChecker; + +contract C +{ + function f( + uint[] memory a, + uint[] memory b, + uint[][] memory cc, + uint8[][] memory dd, + uint[][][] memory eee + ) internal pure { + require(a[0] == 2); + require(cc[0][0] == 50); + require(dd[0][0] == 10); + require(eee[0][0][0] == 50); + b[0] = 1; + // Fails because b == a is possible. + assert(a[0] == 2); + // Fails because b == cc[0] is possible. + assert(cc[0][0] == 50); + // Should not fail since knowledge is erased only for uint[]. + assert(dd[0][0] == 10); + // Fails because b == ee[0][0] is possible. + assert(eee[0][0][0] == 50); + assert(b[0] == 1); + } +} +// ---- +// Warning: (345-362): Assertion violation happens here +// Warning: (409-431): Assertion violation happens here +// Warning: (571-597): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_2.sol b/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_2.sol new file mode 100644 index 000000000000..d015d147173b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_2.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint[] memory a, uint[] memory b) internal view { + require(array[0] == 42); + require(a[0] == 2); + b[0] = 1; + // Erasing knowledge about memory references should not + // erase knowledge about state variables. + assert(array[0] == 42); + assert(a[0] == 2); + assert(b[0] == 1); + } +} +// ---- +// Warning: (314-331): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_3.sol b/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_3.sol new file mode 100644 index 000000000000..f6d6b1977d66 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_aliasing_memory_3.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint[] memory a, uint[] memory b) internal view { + require(array[0] == 42); + uint[] storage c = array; + require(a[0] == 2); + b[0] = 1; + // Erasing knowledge about memory references should not + // erase knowledge about state variables. + assert(array[0] == 42); + // Erasing knowledge about memory references should not + // erase knowledge about storage references. + assert(c[0] == 42); + assert(a[0] == 2); + assert(b[0] == 1); + } +} +// ---- +// Warning: (469-486): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_1.sol b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_1.sol new file mode 100644 index 000000000000..6461634e1161 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_1.sol @@ -0,0 +1,45 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + uint[][] array2d; + uint8[] tinyArray; + function f( + uint[] storage a, + uint[] storage b, + uint[][] storage cc, + uint8[][] storage dd, + uint[][][] storage eee + ) internal { + require(a[0] == 2); + require(array[0] == 42); + require(array2d[0][0] == 42); + require(tinyArray[0] == 42); + require(cc[0][0] == 42); + require(dd[0][0] == 42); + require(eee[0][0][0] == 42); + b[0] = 1; + // Fails because b == a is possible. + assert(a[0] == 2); + // Fails because b == array is possible. + assert(array[0] == 42); + // Fails because b == array2d[0] is possible. + assert(array2d[0][0] == 42); + // Should not fail since knowledge is erased only for uint[]. + assert(tinyArray[0] == 42); + // Fails because b == cc[0] is possible. + assert(cc[0][0] == 42); + // Should not fail since knowledge is erased only for uint[]. + assert(dd[0][0] == 42); + // Fails because b == ee[0][0] is possible. + assert(eee[0][0][0] == 42); + assert(b[0] == 1); + } +} +// ---- +// Warning: (489-506): Assertion violation happens here +// Warning: (553-575): Assertion violation happens here +// Warning: (627-654): Assertion violation happens here +// Warning: (795-817): Assertion violation happens here +// Warning: (957-983): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_2.sol b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_2.sol new file mode 100644 index 000000000000..4b134e58e333 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_2.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint[] storage a, uint[] storage b, uint[] memory c) internal { + require(c[0] == 42); + require(a[0] == 2); + b[0] = 1; + // Erasing knowledge about storage references should not + // erase knowledge about memory references. + assert(c[0] == 42); + // Fails because b == a is possible. + assert(a[0] == 2); + assert(b[0] == 1); + } +} +// ---- +// Warning: (347-364): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_3.sol b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_3.sol new file mode 100644 index 000000000000..d70cc18bbf64 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_3.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint[] storage a, uint[] storage b, uint[] memory c) internal { + uint[] memory d = c; + require(c[0] == 42); + require(a[0] == 2); + b[0] = 1; + // Erasing knowledge about storage references should not + // erase knowledge about memory references. + assert(c[0] == 42); + // Erasing knowledge about storage references should not + // erase knowledge about memory references. + assert(d[0] == 42); + // Fails because b == a is possible. + assert(a[0] == 2); + assert(b[0] == 1); + } +} +// ---- +// Warning: (497-514): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_4.sol b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_4.sol new file mode 100644 index 000000000000..13dc6fcc2ba3 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_aliasing_storage_4.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint[] storage a, uint[] storage b) internal { + require(a[0] == 2); + require(b[0] == 42); + array[0] = 1; + // Fails because array == a is possible. + assert(a[0] == 2); + // Fails because array == b is possible. + assert(b[0] == 42); + assert(array[0] == 1); + } +} +// ---- +// Warning: (226-243): Assertion violation happens here +// Warning: (290-308): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_1.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_1.sol new file mode 100644 index 000000000000..39d028effef2 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_1.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint x, uint y) public { + array[x] = 200; + require(x == y); + assert(array[y] > 100); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_1_fail.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_1_fail.sol new file mode 100644 index 000000000000..79864d35ac34 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_1_fail.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint x, uint y) public { + array[x] = 200; + require(x == y); + assert(array[y] > 300); + } +} +// ---- +// Warning: (137-159): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_2.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_2.sol new file mode 100644 index 000000000000..e1a02a797dcc --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_2.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[][] array; + function f(uint x, uint y, uint z, uint t) public view { + require(array[x][y] == 200); + require(x == z && y == t); + assert(array[z][t] > 100); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_2_fail.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_2_fail.sol new file mode 100644 index 000000000000..725e51ba24e1 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_2_fail.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[][] array; + function f(uint x, uint y, uint z, uint t) public view { + require(array[x][y] == 200); + require(x == z && y == t); + assert(array[z][t] > 300); + } +} +// ---- +// Warning: (183-208): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_3.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_3.sol new file mode 100644 index 000000000000..b76f23bedb4b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_3.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[][][] array; + function f(uint x, uint y, uint z, uint t, uint w, uint v) public view { + require(array[x][y][z] == 200); + require(x == t && y == w && z == v); + assert(array[t][w][v] > 100); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_3_fail.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_3_fail.sol new file mode 100644 index 000000000000..3a34ce5dd94f --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_3_fail.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[][][] array; + function f(uint x, uint y, uint z, uint t, uint w, uint v) public view { + require(array[x][y][z] == 200); + require(x == t && y == w && z == v); + assert(array[t][w][v] > 300); + } +} +// ---- +// Warning: (214-242): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_parameter_1.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_parameter_1.sol new file mode 100644 index 000000000000..83d25dcb5de4 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_parameter_1.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint[] memory array, uint x, uint y) public pure { + array[x] = 200; + require(x == y); + assert(array[y] > 100); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_parameter_1_fail.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_parameter_1_fail.sol new file mode 100644 index 000000000000..a9754b611064 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_parameter_1_fail.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint[] memory array, uint x, uint y) public pure { + array[x] = 200; + require(x == y); + assert(array[y] > 300); + } +} +// ---- +// Warning: (148-170): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_literal_1.sol b/test/libsolidity/smtCheckerTests/types/array_literal_1.sol new file mode 100644 index 000000000000..5c0d31b5b8a9 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_literal_1.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint[3] memory array = [uint(1), 2, 3]; + } +} +// ---- +// Warning: (76-96): Unused local variable. +// Warning: (99-114): Assertion checker does not yet implement tuples and inline arrays. +// Warning: (99-114): Internal error: Expression undefined for SMT solver. diff --git a/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_1.sol b/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_1.sol new file mode 100644 index 000000000000..decffbc0bc52 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_1.sol @@ -0,0 +1,24 @@ +pragma experimental SMTChecker; + +contract C +{ + mapping (uint => uint) singleMap; + mapping (uint => uint)[] severalMaps; + mapping (uint => uint8)[] severalMaps8; + mapping (uint => uint)[][] severalMaps3d; + function f(mapping (uint => uint) storage map) internal { + require(severalMaps[0][0] == 42); + require(severalMaps8[0][0] == 42); + require(severalMaps3d[0][0][0] == 42); + map[0] = 2; + // Should fail since map == severalMaps[0] is possible. + assert(severalMaps[0][0] == 42); + // Should not fail since knowledge is erase only for mapping (uint => uint). + assert(severalMaps8[0][0] == 42); + // Should fail since map == severalMaps3d[0][0] is possible. + assert(severalMaps3d[0][0][0] == 42); + } +} +// ---- +// Warning: (451-482): Assertion violation happens here +// Warning: (664-700): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_static_1.sol b/test/libsolidity/smtCheckerTests/types/array_static_1.sol new file mode 100644 index 000000000000..2d7dc7250c66 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_static_1.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[10] array; + function f(uint x, uint y) public { + array[x] = 200; + require(x == y); + assert(array[y] > 100); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/array_static_1_fail.sol b/test/libsolidity/smtCheckerTests/types/array_static_1_fail.sol new file mode 100644 index 000000000000..017df8f297b9 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_static_1_fail.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[10] array; + function f(uint x, uint y) public { + array[x] = 200; + require(x == y); + assert(array[y] > 300); + } +} +// ---- +// Warning: (139-161): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_static_2.sol b/test/libsolidity/smtCheckerTests/types/array_static_2.sol new file mode 100644 index 000000000000..5d524293858b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_static_2.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[10][20] array; + function f(uint x, uint y, uint z, uint t) public view { + require(array[x][y] == 200); + require(x == z && y == t); + assert(array[z][t] > 100); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/array_static_2_fail.sol b/test/libsolidity/smtCheckerTests/types/array_static_2_fail.sol new file mode 100644 index 000000000000..789308c57814 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_static_2_fail.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[10][20] array; + function f(uint x, uint y, uint z, uint t) public view { + require(array[x][y] == 200); + require(x == z && y == t); + assert(array[z][t] > 300); + } +} +// ---- +// Warning: (187-212): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_static_3.sol b/test/libsolidity/smtCheckerTests/types/array_static_3.sol new file mode 100644 index 000000000000..516291219fd3 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_static_3.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[10][20][30] array; + function f(uint x, uint y, uint z, uint t, uint w, uint v) public view { + require(array[x][y][z] == 200); + require(x == t && y == w && z == v); + assert(array[t][w][v] > 100); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/array_static_3_fail.sol b/test/libsolidity/smtCheckerTests/types/array_static_3_fail.sol new file mode 100644 index 000000000000..240a110679da --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/array_static_3_fail.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[10][20][30] array; + function f(uint x, uint y, uint z, uint t, uint w, uint v) public view { + require(array[x][y][z] == 200); + require(x == t && y == w && z == v); + assert(array[t][w][v] > 300); + } +} +// ---- +// Warning: (220-248): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/bytes_1.sol b/test/libsolidity/smtCheckerTests/types/bytes_1.sol index fb95742132db..92a616d4321d 100644 --- a/test/libsolidity/smtCheckerTests/types/bytes_1.sol +++ b/test/libsolidity/smtCheckerTests/types/bytes_1.sol @@ -9,8 +9,3 @@ contract C } // ---- // Warning: (113-127): Unused local variable. -// Warning: (113-127): Assertion checker does not yet support the type of this variable. -// Warning: (58-72): Assertion checker does not yet support the type of this variable. -// Warning: (95-107): Assertion checker does not yet support the type of this variable. -// Warning: (130-131): Internal error: Expression undefined for SMT solver. -// Warning: (130-131): Assertion checker does not yet implement this type. diff --git a/test/libsolidity/smtCheckerTests/types/bytes_2.sol b/test/libsolidity/smtCheckerTests/types/bytes_2.sol new file mode 100644 index 000000000000..dc41aa4234c5 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/bytes_2.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(bytes memory b1, bytes memory b2) public pure { + b1 = b2; + assert(b1[1] == b2[1]); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/bytes_2_fail.sol b/test/libsolidity/smtCheckerTests/types/bytes_2_fail.sol new file mode 100644 index 000000000000..e5f1b3a80af0 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/bytes_2_fail.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(bytes memory b1, bytes memory b2) public pure { + b1 = b2; + assert(b1[1] == b2[2]); + } +} +// ---- +// Warning: (119-141): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/enum_explicit_values.sol b/test/libsolidity/smtCheckerTests/types/enum_explicit_values.sol new file mode 100644 index 000000000000..a474da41fb0c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/enum_explicit_values.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + enum D { Left, Right } + D d; + function f(D _a) public { + require(_a == D.Left); + d = D.Right; + assert(d != _a); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/enum_explicit_values_2.sol b/test/libsolidity/smtCheckerTests/types/enum_explicit_values_2.sol new file mode 100644 index 000000000000..3fd43ac9fdfb --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/enum_explicit_values_2.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C +{ + enum D { Left, Right } + D d; + function f(D _a) public { + require(_a == D.Left); + d = D.Left; + assert(d != _a); + } +} +// ---- +// Warning: (144-159): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/enum_in_library.sol b/test/libsolidity/smtCheckerTests/types/enum_in_library.sol new file mode 100644 index 000000000000..a1648c66e462 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/enum_in_library.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +library L +{ + enum D { Left, Right } +} + +contract C +{ + enum E { Left, Right } + function f(E _d) internal pure { + _d = E.Left; + assert(_d == E.Left); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/enum_in_library_2.sol b/test/libsolidity/smtCheckerTests/types/enum_in_library_2.sol new file mode 100644 index 000000000000..2bd2afbda622 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/enum_in_library_2.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +library L +{ + enum D { Left, Right } +} + +contract C +{ + enum E { Left, Right } + function f(E _d) internal pure { + _d = E.Right; + assert(_d == E.Left); + } +} +// ---- +// Warning: (161-181): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/enum_in_struct.sol b/test/libsolidity/smtCheckerTests/types/enum_in_struct.sol new file mode 100644 index 000000000000..5218683f4d58 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/enum_in_struct.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C +{ + enum D { Left, Right } + struct S { uint x; D d; } + function f(S memory s) internal pure { + s.d = D.Left; + assert(s.d == D.Left); + } +} +// ---- +// Warning: (109-119): Assertion checker does not yet support the type of this variable. +// Warning: (139-142): Assertion checker does not yet support this expression. +// Warning: (139-151): Assertion checker does not yet implement such assignments. +// Warning: (162-165): Assertion checker does not yet support this expression. +// Warning: (155-176): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/enum_storage_eq.sol b/test/libsolidity/smtCheckerTests/types/enum_storage_eq.sol new file mode 100644 index 000000000000..0d8b1362747c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/enum_storage_eq.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + enum D { Left, Right } + D d; + function f(D _d) public { + d = _d; + assert(d != _d); + } +} +// ---- +// Warning: (115-130): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/enum_transitivity.sol b/test/libsolidity/smtCheckerTests/types/enum_transitivity.sol new file mode 100644 index 000000000000..8946aae524a7 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/enum_transitivity.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + enum D { Left, Right } + D d; + function f(D _a, D _b) public view { + require(_a == _b); + require(_a == d); + assert(d == _b); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/mapping_aliasing_2.sol b/test/libsolidity/smtCheckerTests/types/mapping_aliasing_2.sol new file mode 100644 index 000000000000..86af187ad75e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/mapping_aliasing_2.sol @@ -0,0 +1,28 @@ +pragma experimental SMTChecker; + +contract C +{ + mapping (uint => uint) a; + mapping (uint => mapping (uint => uint)) maps; + mapping (uint => mapping (uint => uint8)) maps8; + function f(mapping (uint => uint) storage map1, mapping (uint => uint) storage map2) internal { + require(map1[0] == 2); + require(a[0] == 42); + require(maps[0][0] == 42); + require(maps8[0][0] == 42); + map2[0] = 1; + // Fails because map2 == map1 is possible. + assert(map1[0] == 2); + // Fails because map2 == a is possible. + assert(a[0] == 42); + // Fails because map2 == maps[0] is possible. + assert(maps[0][0] == 42); + // Should not fail since knowledge is erased only for mapping (uint => uint). + assert(maps8[0][0] == 42); + assert(map2[0] == 1); + } +} +// ---- +// Warning: (437-457): Assertion violation happens here +// Warning: (503-521): Assertion violation happens here +// Warning: (573-597): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/mapping_unsupported_key_type_1.sol b/test/libsolidity/smtCheckerTests/types/mapping_unsupported_key_type_1.sol index f4e3a65fe058..d524a0da5951 100644 --- a/test/libsolidity/smtCheckerTests/types/mapping_unsupported_key_type_1.sol +++ b/test/libsolidity/smtCheckerTests/types/mapping_unsupported_key_type_1.sol @@ -9,9 +9,3 @@ contract C } } // ---- -// Warning: (89-104): Assertion checker does not yet support the type of this variable. -// Warning: (129-130): Internal error: Expression undefined for SMT solver. -// Warning: (129-130): Assertion checker does not yet implement this type. -// Warning: (155-156): Internal error: Expression undefined for SMT solver. -// Warning: (155-156): Assertion checker does not yet implement this type. -// Warning: (139-158): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/rational_large_1.sol b/test/libsolidity/smtCheckerTests/types/rational_large_1.sol new file mode 100644 index 000000000000..152b29f0904a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/rational_large_1.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; +contract c { + function f() public pure returns (uint) { + uint x = 8e130%9; + assert(x == 8); + assert(x != 8); + } +} +// ---- +// Warning: (128-142): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/string_1.sol b/test/libsolidity/smtCheckerTests/types/string_1.sol new file mode 100644 index 000000000000..7f03c29da3ca --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/string_1.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(string memory s1, string memory s2) public pure { + assert(bytes(s1).length == bytes(s2).length); + } +} +// ---- +// Warning: (117-133): Assertion checker does not yet support this expression. +// Warning: (137-153): Assertion checker does not yet support this expression. +// Warning: (110-154): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/string_2.sol b/test/libsolidity/smtCheckerTests/types/string_2.sol new file mode 100644 index 000000000000..68f6861b4082 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/string_2.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + string memory s = "Hello World"; + } +} +// ---- +// Warning: (76-91): Unused local variable. +// Warning: (94-107): Assertion checker does not yet support the type of this literal (literal_string "Hello World"). diff --git a/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol index a732b44626bb..1fc58686832d 100644 --- a/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol +++ b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol @@ -4,4 +4,3 @@ contract Test { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (65-82): Calldata arrays with dynamically encoded base types are not yet supported. diff --git a/test/libsolidity/syntaxTests/array/calldata_multi_dynamic_V1.sol b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic_V1.sol new file mode 100644 index 000000000000..0ee5135a6c96 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic_V1.sol @@ -0,0 +1,7 @@ +contract Test { + function f(uint[][] calldata) external { } + function g(uint[][1] calldata) external { } +} +// ---- +// TypeError: (31-48): This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature. +// TypeError: (78-96): This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature. diff --git a/test/libsolidity/syntaxTests/inheritance/wrong_type_base_arguments.sol b/test/libsolidity/syntaxTests/inheritance/wrong_type_base_arguments.sol index aa29af52a8f3..de5eb346f4e3 100644 --- a/test/libsolidity/syntaxTests/inheritance/wrong_type_base_arguments.sol +++ b/test/libsolidity/syntaxTests/inheritance/wrong_type_base_arguments.sol @@ -6,4 +6,4 @@ contract Derived2 is Base { constructor() Base(2) public { } } // ---- -// TypeError: (74-77): Invalid type for argument in constructor call. Invalid implicit conversion from int_const 300 to uint8 requested. +// TypeError: (74-77): Invalid type for argument in constructor call. Invalid implicit conversion from int_const 300 to uint8 requested. Literal is too large to fit in uint8. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/315_fixed_type_invalid_implicit_conversion_size.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/315_fixed_type_invalid_implicit_conversion_size.sol index 79698228631e..f045731ac92e 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/315_fixed_type_invalid_implicit_conversion_size.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/315_fixed_type_invalid_implicit_conversion_size.sol @@ -5,4 +5,4 @@ contract test { } } // ---- -// TypeError: (75-92): Type ufixed128x18 is not implicitly convertible to expected type ufixed248x8. +// TypeError: (75-92): Type ufixed128x18 is not implicitly convertible to expected type ufixed248x8. Too many fractional digits. diff --git a/test/libsolidity/syntaxTests/structs/array_calldata.sol b/test/libsolidity/syntaxTests/structs/array_calldata.sol index 8da097884cf0..9e1071a0e5ab 100644 --- a/test/libsolidity/syntaxTests/structs/array_calldata.sol +++ b/test/libsolidity/syntaxTests/structs/array_calldata.sol @@ -6,4 +6,3 @@ contract Test { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (131-145): Calldata arrays with dynamically encoded base types are not yet supported. diff --git a/test/libsolidity/syntaxTests/structs/calldata_dynamic.sol b/test/libsolidity/syntaxTests/structs/calldata_dynamic.sol new file mode 100644 index 000000000000..34b0affae311 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/calldata_dynamic.sol @@ -0,0 +1,7 @@ +pragma experimental ABIEncoderV2; +contract Test { + struct S { int[] a; } + function f(S calldata) external { } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. diff --git a/test/libsolidity/syntaxTests/types/rational_number_signed_to_unsigned.sol b/test/libsolidity/syntaxTests/types/rational_number_signed_to_unsigned.sol new file mode 100644 index 000000000000..735968952ffc --- /dev/null +++ b/test/libsolidity/syntaxTests/types/rational_number_signed_to_unsigned.sol @@ -0,0 +1,7 @@ +contract c { + function f() public pure { + uint a = -1; + } +} +// ---- +// TypeError: (52-63): Type int_const -1 is not implicitly convertible to expected type uint256. Cannot implicitly convert signed literal to unsigned type. diff --git a/test/libsolidity/syntaxTests/types/rational_number_too_large.sol b/test/libsolidity/syntaxTests/types/rational_number_too_large.sol new file mode 100644 index 000000000000..eb221501035f --- /dev/null +++ b/test/libsolidity/syntaxTests/types/rational_number_too_large.sol @@ -0,0 +1,7 @@ +contract c { + function f() public pure { + uint8 a = 256; + } +} +// ---- +// TypeError: (52-65): Type int_const 256 is not implicitly convertible to expected type uint8. Literal is too large to fit in uint8. diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index 1541828cb441..0e84cd8a4536 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -33,6 +33,45 @@ using namespace dev::solidity::test; using namespace std; using namespace soltest; +namespace +{ + enum class DeclaredAlignment + { + Left, + Right, + None, + }; + + inline bytes alignLeft(bytes _bytes) + { + return std::move(_bytes) + bytes(32 - _bytes.size(), 0); + } + + inline bytes alignRight(bytes _bytes) + { + return bytes(32 - _bytes.size(), 0) + std::move(_bytes); + } + + inline bytes applyAlign(DeclaredAlignment _alignment, ABIType& _abiType, bytes _converted) + { + if (_alignment != DeclaredAlignment::None) + _abiType.alignDeclared = true; + + switch (_alignment) + { + case DeclaredAlignment::Left: + _abiType.align = ABIType::AlignLeft; + return alignLeft(std::move(_converted)); + case DeclaredAlignment::Right: + _abiType.align = ABIType::AlignRight; + return alignRight(std::move(_converted)); + default: + _abiType.align = ABIType::AlignRight; + return alignRight(std::move(_converted)); + } + } +} + vector TestFileParser::parseFunctionCalls() { vector calls; @@ -137,9 +176,16 @@ string TestFileParser::parseFunctionSignature() u256 TestFileParser::parseFunctionCallValue() { - u256 value = convertNumber(parseDecimalNumber()); - expect(Token::Ether); - return value; + try + { + u256 value{parseDecimalNumber()}; + expect(Token::Ether); + return value; + } + catch (std::exception const&) + { + throw Error(Error::Type::ParserError, "Ether value encoding invalid."); + } } FunctionCallArgs TestFileParser::parseFunctionCallArguments() @@ -192,55 +238,90 @@ Parameter TestFileParser::parseParameter() tuple TestFileParser::parseABITypeLiteral() { - try + ABIType abiType{ABIType::None, ABIType::AlignNone, 0}; + DeclaredAlignment alignment{DeclaredAlignment::None}; + bytes result{toBigEndian(u256{0})}; + string rawString; + bool isSigned = false; + + if (accept(Token::Left, true)) + { + rawString += formatToken(Token::Left); + expect(Token::LParen); + rawString += formatToken(Token::LParen); + alignment = DeclaredAlignment::Left; + } + if (accept(Token::Right, true)) { - u256 number{0}; - ABIType abiType{ABIType::None, ABIType::AlignRight, 0}; - string rawString; + rawString += formatToken(Token::Right); + expect(Token::LParen); + rawString += formatToken(Token::LParen); + alignment = DeclaredAlignment::Right; + } - if (accept(Token::Sub)) + try + { + if (accept(Token::Sub, true)) { - abiType = ABIType{ABIType::SignedDec, ABIType::AlignRight, 32}; - expect(Token::Sub); rawString += formatToken(Token::Sub); + isSigned = true; + } + if (accept(Token::Boolean)) + { + if (isSigned) + throw Error(Error::Type::ParserError, "Invalid boolean literal."); + abiType = ABIType{ABIType::Boolean, ABIType::AlignRight, 32}; + string parsed = parseBoolean(); + rawString += parsed; + result = applyAlign(alignment, abiType, convertBoolean(parsed)); + } + else if (accept(Token::HexNumber)) + { + if (isSigned) + throw Error(Error::Type::ParserError, "Invalid hex number literal."); + abiType = ABIType{ABIType::Hex, ABIType::AlignRight, 32}; + string parsed = parseHexNumber(); + rawString += parsed; + result = applyAlign(alignment, abiType, convertHexNumber(parsed)); + } + else if (accept(Token::Hex, true)) + { + if (isSigned) + throw Error(Error::Type::ParserError, "Invalid hex string literal."); + if (alignment != DeclaredAlignment::None) + throw Error(Error::Type::ParserError, "Hex string literals cannot be aligned or padded."); + string parsed = parseHexNumber(); + rawString += parsed; + result = convertHexString(parsed); + abiType = ABIType{ABIType::HexString, ABIType::AlignNone, result.size()}; + } + else if (accept(Token::Number)) + { + auto type = isSigned ? ABIType::SignedDec : ABIType::UnsignedDec; + abiType = ABIType{type, ABIType::AlignRight, 32}; string parsed = parseDecimalNumber(); rawString += parsed; - number = convertNumber(parsed) * -1; + if (isSigned) + parsed = "-" + parsed; + result = applyAlign(alignment, abiType, convertNumber(parsed)); } - else + else if (accept(Token::Failure, true)) { - if (accept(Token::Boolean)) - { - abiType = ABIType{ABIType::Boolean, ABIType::AlignRight, 32}; - string parsed = parseBoolean(); - rawString += parsed; - return make_tuple(toBigEndian(u256{convertBoolean(parsed)}), abiType, rawString); - } - else if (accept(Token::HexNumber)) - { - abiType = ABIType{ABIType::Hex, ABIType::AlignLeft, 32}; - string parsed = parseHexNumber(); - rawString += parsed; - return make_tuple(convertHexNumber(parsed), abiType, rawString); - } - else if (accept(Token::Number)) - { - abiType = ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; - string parsed = parseDecimalNumber(); - rawString += parsed; - number = convertNumber(parsed); - } - else if (accept(Token::Failure, true)) - { - abiType = ABIType{ABIType::Failure, ABIType::AlignRight, 0}; - return make_tuple(bytes{}, abiType, rawString); - } + if (isSigned) + throw Error(Error::Type::ParserError, "Invalid failure literal."); + abiType = ABIType{ABIType::Failure, ABIType::AlignRight, 0}; + result = bytes{}; } - return make_tuple(toBigEndian(number), abiType, rawString); + if (alignment != DeclaredAlignment::None) + { + expect(Token::RParen); + rawString += formatToken(Token::RParen); + } + return make_tuple(result, abiType, rawString); } catch (std::exception const&) { - throw Error(Error::Type::ParserError, "Number encoding invalid."); + throw Error(Error::Type::ParserError, "Literal encoding invalid."); } } @@ -248,10 +329,24 @@ string TestFileParser::parseIdentifierOrTuple() { string identOrTuple; + auto parseArrayDimensions = [&]() + { + while (accept(Token::LBrack)) + { + identOrTuple += formatToken(Token::LBrack); + expect(Token::LBrack); + if (accept(Token::Number)) + identOrTuple += parseDecimalNumber(); + identOrTuple += formatToken(Token::RBrack); + expect(Token::RBrack); + } + }; + if (accept(Token::Identifier)) { identOrTuple = m_scanner.currentLiteral(); expect(Token::Identifier); + parseArrayDimensions(); return identOrTuple; } expect(Token::LParen); @@ -266,6 +361,8 @@ string TestFileParser::parseIdentifierOrTuple() } expect(Token::RParen); identOrTuple += formatToken(Token::RParen); + + parseArrayDimensions(); return identOrTuple; } @@ -298,21 +395,21 @@ string TestFileParser::parseHexNumber() return literal; } -bool TestFileParser::convertBoolean(string const& _literal) +bytes TestFileParser::convertBoolean(string const& _literal) { if (_literal == "true") - return true; + return bytes{true}; else if (_literal == "false") - return false; + return bytes{false}; else throw Error(Error::Type::ParserError, "Boolean literal invalid."); } -u256 TestFileParser::convertNumber(string const& _literal) +bytes TestFileParser::convertNumber(string const& _literal) { try { - return u256{_literal}; + return toCompactBigEndian(u256{_literal}); } catch (std::exception const&) { @@ -330,8 +427,7 @@ bytes TestFileParser::convertHexNumber(string const& _literal) } else { - bytes result = fromHex(_literal); - return result + bytes(32 - result.size(), 0); + return fromHex(_literal); } } catch (std::exception const&) @@ -340,6 +436,21 @@ bytes TestFileParser::convertHexNumber(string const& _literal) } } +bytes TestFileParser::convertHexString(string const& _literal) +{ + try + { + if (_literal.size() % 2) + throw Error(Error::Type::ParserError, "Hex string encoding invalid."); + else + return fromHex(_literal); + } + catch (std::exception const&) + { + throw Error(Error::Type::ParserError, "Hex string encoding invalid."); + } +} + void TestFileParser::Scanner::readStream(istream& _stream) { std::string line; @@ -350,6 +461,8 @@ void TestFileParser::Scanner::readStream(istream& _stream) void TestFileParser::Scanner::scanNextToken() { + using namespace langutil; + // Make code coverage happy. assert(formatToken(Token::NUM_TOKENS) == ""); @@ -357,6 +470,9 @@ void TestFileParser::Scanner::scanNextToken() if (_literal == "true") return TokenDesc{Token::Boolean, _literal}; if (_literal == "false") return TokenDesc{Token::Boolean, _literal}; if (_literal == "ether") return TokenDesc{Token::Ether, _literal}; + if (_literal == "left") return TokenDesc{Token::Left, _literal}; + if (_literal == "right") return TokenDesc{Token::Right, _literal}; + if (_literal == "hex") return TokenDesc{Token::Hex, _literal}; if (_literal == "FAILURE") return TokenDesc{Token::Failure, _literal}; return TokenDesc{Token::Identifier, _literal}; }; @@ -402,13 +518,24 @@ void TestFileParser::Scanner::scanNextToken() case ')': token = selectToken(Token::RParen); break; + case '[': + token = selectToken(Token::LBrack); + break; + case ']': + token = selectToken(Token::RBrack); + break; + case '\"': + advance(); + token = selectToken(Token::HexNumber, scanHexNumber()); + advance(); + break; default: - if (langutil::isIdentifierStart(current())) + if (isIdentifierStart(current())) { TokenDesc detectedToken = detectKeyword(scanIdentifierOrKeyword()); token = selectToken(detectedToken.first, detectedToken.second); } - else if (langutil::isDecimalDigit(current())) + else if (isDecimalDigit(current())) { if (current() == '0' && peek() == 'x') { @@ -419,7 +546,7 @@ void TestFileParser::Scanner::scanNextToken() else token = selectToken(Token::Number, scanDecimalNumber()); } - else if (langutil::isWhiteSpace(current())) + else if (isWhiteSpace(current())) token = selectToken(Token::Whitespace); else if (isEndOfLine()) token = selectToken(Token::EOS); diff --git a/test/libsolidity/util/TestFileParser.h b/test/libsolidity/util/TestFileParser.h index f767ab409542..fb1cc71a2a42 100644 --- a/test/libsolidity/util/TestFileParser.h +++ b/test/libsolidity/util/TestFileParser.h @@ -60,8 +60,11 @@ namespace test T(Identifier, "identifier", 0) \ /* type keywords */ \ K(Ether, "ether", 0) \ + K(Hex, "hex", 0) \ K(Boolean, "boolean", 0) \ /* special keywords */ \ + K(Left, "left", 0) \ + K(Right, "right", 0) \ K(Failure, "FAILURE", 0) \ namespace soltest @@ -106,18 +109,20 @@ struct ABIType SignedDec, Boolean, Hex, + HexString, Failure, None }; enum Align { AlignLeft, - AlignRight + AlignRight, + AlignNone, }; - Type type = ABIType::None; Align align = ABIType::AlignRight; size_t size = 0; + bool alignDeclared = false; }; /** @@ -370,19 +375,22 @@ class TestFileParser /// Parses the current hex number literal. std::string parseHexNumber(); - /// Coverts "true" to `true`, "false" to `false` and throws - /// otherwise. - bool convertBoolean(std::string const& _literal); + /// Tries to convert \param _literal to an unpadded `bytes` + /// representation of the boolean number literal. Throws if conversion fails. + bytes convertBoolean(std::string const& _literal); - /// Tries to convert \param _literal to right-aligned, padded `u256` - /// representation of the decimal number literal. - /// Throws if conversion fails. - u256 convertNumber(std::string const& _literal); + /// Tries to convert \param _literal to an unpadded `bytes` + /// representation of the decimal number literal. Throws if conversion fails. + bytes convertNumber(std::string const& _literal); - /// Tries to convert \param _literal to left-aligned, padded `bytes` + /// Tries to convert \param _literal to an unpadded `bytes` /// representation of the hex literal. Throws if conversion fails. bytes convertHexNumber(std::string const& _literal); + /// Tries to convert \param _literal to left-aligned, unpadded `bytes` + /// representation of the hex string literal. Throws if conversion fails. + bytes convertHexString(std::string const& _literal); + /// A scanner instance Scanner m_scanner; }; diff --git a/test/libsolidity/util/TestFileParserTests.cpp b/test/libsolidity/util/TestFileParserTests.cpp index 66dbb3b85b8e..03325dea2e8c 100644 --- a/test/libsolidity/util/TestFileParserTests.cpp +++ b/test/libsolidity/util/TestFileParserTests.cpp @@ -310,6 +310,40 @@ BOOST_AUTO_TEST_CASE(call_arguments_bool) ); } +BOOST_AUTO_TEST_CASE(call_arguments_hex_string) +{ + char const* source = R"( + // f(bytes): hex"4200ef" -> hex"ab0023" + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 1); + testFunctionCall( + calls.at(0), + Mode::SingleLine, + "f(bytes)", + false, + fromHex("4200ef"), + fromHex("ab0023") + ); +} + +BOOST_AUTO_TEST_CASE(call_arguments_hex_string_lowercase) +{ + char const* source = R"( + // f(bytes): hex"4200ef" -> hex"23ef00" + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 1); + testFunctionCall( + calls.at(0), + Mode::SingleLine, + "f(bytes)", + false, + fromHex("4200EF"), + fromHex("23EF00") + ); +} + BOOST_AUTO_TEST_CASE(call_arguments_tuple) { char const* source = R"( @@ -336,8 +370,8 @@ BOOST_AUTO_TEST_CASE(call_arguments_left_aligned) "f(bytes32,bytes32)", false, fmt::encodeArgs( - u256("0x6161000000000000000000000000000000000000000000000000000000000000"), - u256("0x420000EF00000000000000000000000000000000000000000000000000000000") + fromHex("0x6161"), + fromHex("0x420000EF") ), fmt::encodeArgs(1) ); @@ -347,8 +381,8 @@ BOOST_AUTO_TEST_CASE(call_arguments_left_aligned) "g(bytes32,bytes32)", false, fmt::encodeArgs( - u256("0x0616000000000000000000000000000000000000000000000000000000000000"), - u256("0x0042EF0000000000000000000000000000000000000000000000000000000000") + fromHex("0x0616"), + fromHex("0x0042EF00") ), fmt::encodeArgs(1) ); @@ -460,6 +494,37 @@ BOOST_AUTO_TEST_CASE(call_multiple_arguments_mixed_format) ); } +BOOST_AUTO_TEST_CASE(call_signature_array) +{ + char const* source = R"( + // f(uint256[]) -> + // f(uint256[3]) -> + // f(uint256[3][][], uint8[9]) -> + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 3); + testFunctionCall(calls.at(0), Mode::SingleLine, "f(uint256[])", false); + testFunctionCall(calls.at(1), Mode::SingleLine, "f(uint256[3])", false); + testFunctionCall(calls.at(2), Mode::SingleLine, "f(uint256[3][][],uint8[9])", false); +} + +BOOST_AUTO_TEST_CASE(call_signature_struct_array) +{ + char const* source = R"( + // f((uint256)[]) -> + // f((uint256)[3]) -> + // f((uint256, uint8)[3]) -> + // f((uint256)[3][][], (uint8, bool)[9]) -> + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 4); + testFunctionCall(calls.at(0), Mode::SingleLine, "f((uint256)[])", false); + testFunctionCall(calls.at(1), Mode::SingleLine, "f((uint256)[3])", false); + testFunctionCall(calls.at(2), Mode::SingleLine, "f((uint256,uint8)[3])", false); + testFunctionCall(calls.at(3), Mode::SingleLine, "f((uint256)[3][][],(uint8,bool)[9])", false); + +} + BOOST_AUTO_TEST_CASE(call_signature_valid) { char const* source = R"( @@ -493,6 +558,62 @@ BOOST_AUTO_TEST_CASE(call_raw_arguments) ); } +BOOST_AUTO_TEST_CASE(call_builtin_left_decimal) +{ + char const* source = R"( + // f(): left(1), left(0x20) -> left(-2), left(true) + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 1); + testFunctionCall( + calls.at(0), + Mode::SingleLine, + "f()", + false, + fmt::encodeArgs( + fmt::encode(toCompactBigEndian(u256{1}), false), + fmt::encode(fromHex("0x20"), false) + ), + fmt::encodeArgs( + fmt::encode(toCompactBigEndian(u256{-2}), false), + fmt::encode(bytes{true}, false) + ) + ); +} + +BOOST_AUTO_TEST_CASE(call_builtin_right_decimal) +{ + char const* source = R"( + // f(): right(1), right(0x20) -> right(-2), right(true) + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 1); + testFunctionCall( + calls.at(0), + Mode::SingleLine, + "f()", + false, + fmt::encodeArgs(1, fromHex("0x20")), + fmt::encodeArgs(-2, true) + ); +} + +BOOST_AUTO_TEST_CASE(call_arguments_hex_string_left_align) +{ + char const* source = R"( + // f(bytes): left(hex"4200ef") -> + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + +BOOST_AUTO_TEST_CASE(call_arguments_hex_string_right_align) +{ + char const* source = R"( + // f(bytes): right(hex"4200ef") -> + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + BOOST_AUTO_TEST_CASE(call_newline_invalid) { char const* source = R"( @@ -603,6 +724,30 @@ BOOST_AUTO_TEST_CASE(call_hex_number_invalid) BOOST_REQUIRE_THROW(parse(source), langutil::Error); } +BOOST_AUTO_TEST_CASE(call_signed_bool_invalid) +{ + char const* source = R"( + // f() -> -true + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + +BOOST_AUTO_TEST_CASE(call_signed_failure_invalid) +{ + char const* source = R"( + // f() -> -FAILURE + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + +BOOST_AUTO_TEST_CASE(call_signed_hex_number_invalid) +{ + char const* source = R"( + // f() -> -0x42 + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + BOOST_AUTO_TEST_CASE(call_arguments_colon) { char const* source = R"( diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index c7abcb214259..f10dd855cc57 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -48,7 +48,11 @@ string TestFunctionCall::format(string const& _linePrefix, bool const _renderRes if (!m_call.arguments.rawBytes().empty()) { string output = formatRawParameters(m_call.arguments.parameters, _linePrefix); - _stream << colon << output; + _stream << colon; + if (_singleLine) + _stream << ws; + _stream << output; + } /// Formats comments on the function parameters and the arrow taking @@ -76,13 +80,19 @@ string TestFunctionCall::format(string const& _linePrefix, bool const _renderRes { bytes output = m_call.expectations.rawBytes(); bool const isFailure = m_call.expectations.failure; - result = isFailure ? failure : formatBytesParameters(output, m_call.expectations.result); + result = isFailure ? + failure : + formatRawParameters(m_call.expectations.result); } else { bytes output = m_rawBytes; bool const isFailure = m_failure; - result = isFailure ? failure : formatBytesParameters(output, m_call.expectations.result); + result = isFailure ? + failure : + matchesExpectation() ? + formatRawParameters(m_call.expectations.result) : + formatBytesParameters(output, m_call.expectations.result); } AnsiColorized(_stream, highlight, {dev::formatting::RED_BACKGROUND}) << result; @@ -106,8 +116,6 @@ string TestFunctionCall::format(string const& _linePrefix, bool const _renderRes formatOutput(true); else formatOutput(false); -// _stream << endl; - return _stream.str(); } @@ -131,14 +139,12 @@ string TestFunctionCall::formatBytesParameters(bytes const& _bytes, dev::solidit // be signed. If an unsigned was detected in the expectations, // but the actual result returned a signed, it would be formatted // incorrectly. - soltestAssert(param.abiType.align == ABIType::AlignRight, "Unsigned decimals must be right-aligned."); if (*byteRange.begin() & 0x80) resultStream << u2s(fromBigEndian(byteRange)); else resultStream << fromBigEndian(byteRange); break; case ABIType::SignedDec: - soltestAssert(param.abiType.align == ABIType::AlignRight, "Signed decimals must be right-aligned."); if (*byteRange.begin() & 0x80) resultStream << u2s(fromBigEndian(byteRange)); else @@ -146,21 +152,21 @@ string TestFunctionCall::formatBytesParameters(bytes const& _bytes, dev::solidit break; case ABIType::Boolean: { - soltestAssert(param.abiType.align == ABIType::AlignRight, "Booleans must be right-aligned."); u256 result = fromBigEndian(byteRange); if (result == 0) resultStream << "false"; - else + else if (result == 1) resultStream << "true"; + else + resultStream << result; break; } case ABIType::Hex: - soltestAssert(param.abiType.align == ABIType::AlignLeft, "Hex numbers must be left-aligned."); - byteRange.erase( - std::remove(byteRange.begin(), byteRange.end(), 0), byteRange.end() - ); resultStream << toHex(byteRange, HexPrefix::Add); break; + case ABIType::HexString: + resultStream << "hex\"" << toHex(byteRange) << "\""; + break; case ABIType::Failure: break; case ABIType::None: @@ -180,10 +186,10 @@ string TestFunctionCall::formatRawParameters(dev::solidity::test::ParameterList for (auto const& param: _params) { if (param.format.newline) - resultStream << endl << _linePrefix << "//"; - resultStream << " " << param.rawString; + resultStream << endl << _linePrefix << "// "; + resultStream << param.rawString; if (¶m != &_params.back()) - resultStream << ","; + resultStream << ", "; } return resultStream.str(); } diff --git a/test/libsolidity/util/TestFunctionCallTests.cpp b/test/libsolidity/util/TestFunctionCallTests.cpp index 3a2de4804bb2..66287794639a 100644 --- a/test/libsolidity/util/TestFunctionCallTests.cpp +++ b/test/libsolidity/util/TestFunctionCallTests.cpp @@ -47,6 +47,43 @@ BOOST_AUTO_TEST_CASE(format_unsigned_singleline) TestFunctionCall test{call}; BOOST_REQUIRE_EQUAL(test.format(), "// f(uint8): 1 -> 1"); + + test.setRawBytes(toBigEndian(u256{2})); + test.setFailure(false); + + BOOST_REQUIRE_EQUAL(test.format("", true), "// f(uint8): 1 -> 2"); +} + +BOOST_AUTO_TEST_CASE(format_unsigned_singleline_signed_encoding) +{ + bytes expectedBytes = toBigEndian(u256{1}); + ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; + Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallArgs arguments{vector{param}, string{}}; + FunctionCall call{"f(uint8)", 0, arguments, expectations}; + TestFunctionCall test{call}; + + BOOST_REQUIRE_EQUAL(test.format(), "// f(uint8): 1 -> 1"); + + test.setRawBytes(toBigEndian(u256{-1})); + test.setFailure(false); + + BOOST_REQUIRE_EQUAL(test.format("", true), "// f(uint8): 1 -> -1"); +} + +BOOST_AUTO_TEST_CASE(format_unsigned_multiline) +{ + bytes expectedBytes = toBigEndian(u256{1}); + ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; + Parameter result{expectedBytes, "1", abiType, FormatInfo{}}; + FunctionCallExpectations expectations{vector{result}, false, string{}}; + FunctionCallArgs arguments{vector{}, string{}}; + FunctionCall call{"f(uint8)", 0, arguments, expectations}; + call.displayMode = FunctionCall::DisplayMode::MultiLine; + TestFunctionCall test{call}; + + BOOST_REQUIRE_EQUAL(test.format(), "// f(uint8)\n// -> 1"); } BOOST_AUTO_TEST_CASE(format_multiple_unsigned_singleline) @@ -73,13 +110,18 @@ BOOST_AUTO_TEST_CASE(format_signed_singleline) TestFunctionCall test{call}; BOOST_REQUIRE_EQUAL(test.format(), "// f(int8): -1 -> -1"); + + test.setRawBytes(toBigEndian(u256{-2})); + test.setFailure(false); + + BOOST_REQUIRE_EQUAL(test.format("", true), "// f(int8): -1 -> -2"); } BOOST_AUTO_TEST_CASE(format_hex_singleline) { bytes result = fromHex("0x31"); bytes expectedBytes = result + bytes(32 - result.size(), 0); - ABIType abiType{ABIType::Hex, ABIType::AlignLeft, 32}; + ABIType abiType{ABIType::Hex, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "0x31", abiType, FormatInfo{}}; FunctionCallExpectations expectations{vector{param}, false, string{}}; FunctionCallArgs arguments{vector{param}, string{}}; @@ -87,20 +129,26 @@ BOOST_AUTO_TEST_CASE(format_hex_singleline) TestFunctionCall test{call}; BOOST_REQUIRE_EQUAL(test.format(), "// f(bytes32): 0x31 -> 0x31"); + + bytes actualResult = fromHex("0x32"); + bytes actualBytes = actualResult + bytes(32 - actualResult.size(), 0); + test.setRawBytes(actualBytes); + test.setFailure(false); + + BOOST_REQUIRE_EQUAL(test.format("", true), "// f(bytes32): 0x31 -> 0x3200000000000000000000000000000000000000000000000000000000000000"); } -BOOST_AUTO_TEST_CASE(format_hex_right_align) +BOOST_AUTO_TEST_CASE(format_hex_string_singleline) { - bytes result = fromHex("0x31"); - bytes expectedBytes = result + bytes(32 - result.size(), 0); - ABIType abiType{ABIType::Hex, ABIType::AlignRight, 32}; - Parameter param{expectedBytes, "0x31", abiType, FormatInfo{}}; + bytes expectedBytes = fromHex("4200ef"); + ABIType abiType{ABIType::HexString, ABIType::AlignLeft, 3}; + Parameter param{expectedBytes, "hex\"4200ef\"", abiType, FormatInfo{}}; FunctionCallExpectations expectations{vector{param}, false, string{}}; FunctionCallArgs arguments{vector{param}, string{}}; - FunctionCall call{"f(bytes32)", 0, arguments, expectations}; + FunctionCall call{"f(string)", 0, arguments, expectations}; TestFunctionCall test{call}; - BOOST_REQUIRE_THROW(test.format(), runtime_error); + BOOST_REQUIRE_EQUAL(test.format(), "// f(string): hex\"4200ef\" -> hex\"4200ef\""); } BOOST_AUTO_TEST_CASE(format_bool_true_singleline) @@ -114,6 +162,13 @@ BOOST_AUTO_TEST_CASE(format_bool_true_singleline) TestFunctionCall test{call}; BOOST_REQUIRE_EQUAL(test.format(), "// f(bool): true -> true"); + + bytes actualResult = bytes{false}; + bytes actualBytes = actualResult + bytes(32 - actualResult.size(), 0); + test.setRawBytes(actualBytes); + test.setFailure(false); + + BOOST_REQUIRE_EQUAL(test.format("", true), "// f(bool): true -> false"); } BOOST_AUTO_TEST_CASE(format_bool_false_singleline) @@ -129,48 +184,79 @@ BOOST_AUTO_TEST_CASE(format_bool_false_singleline) BOOST_REQUIRE_EQUAL(test.format(), "// f(bool): false -> false"); } -BOOST_AUTO_TEST_CASE(format_bool_left_align_singleline) +BOOST_AUTO_TEST_CASE(format_bool_left_singleline) { - bytes expectedBytes = toBigEndian(u256{true}); + bytes expectedBytes = toBigEndian(u256{false}); ABIType abiType{ABIType::Boolean, ABIType::AlignLeft, 32}; - Parameter param{expectedBytes, "true", abiType, FormatInfo{}}; + Parameter param{expectedBytes, "left(false)", abiType, FormatInfo{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallArgs arguments{vector{param}, string{}}; + FunctionCall call{"f(bool)", 0, arguments, expectations}; + TestFunctionCall test{call}; + + BOOST_REQUIRE_EQUAL(test.format(), "// f(bool): left(false) -> left(false)"); +} + +BOOST_AUTO_TEST_CASE(format_hex_number_right_singleline) +{ + bytes result = fromHex("0x42"); + bytes expectedBytes = result + bytes(32 - result.size(), 0); + ABIType abiType{ABIType::Hex, ABIType::AlignRight, 32}; + Parameter param{expectedBytes, "right(0x42)", abiType, FormatInfo{}}; FunctionCallExpectations expectations{vector{param}, false, string{}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(bool)", 0, arguments, expectations}; TestFunctionCall test{call}; - BOOST_REQUIRE_THROW(test.format(), runtime_error); + BOOST_REQUIRE_EQUAL(test.format(), "// f(bool): right(0x42) -> right(0x42)"); } BOOST_AUTO_TEST_CASE(format_empty_byte_range) { bytes expectedBytes; - ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; + ABIType abiType{ABIType::None, ABIType::AlignNone, 0}; Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; FunctionCallExpectations expectations{vector{param}, false, string{}}; FunctionCallArgs arguments{vector{}, string{}}; FunctionCall call{"f()", 0, arguments, expectations}; TestFunctionCall test{call}; - BOOST_REQUIRE_EQUAL(test.format(), "// f() -> "); + BOOST_REQUIRE_EQUAL(test.format(), "// f() -> 1"); +} + +BOOST_AUTO_TEST_CASE(format_failure_singleline) +{ + bytes expectedBytes = toBigEndian(u256{1}); + ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; + Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; + FunctionCallExpectations expectations{vector{}, true, string{}}; + FunctionCallArgs arguments{vector{param}, string{}}; + FunctionCall call{"f(uint8)", 0, arguments, expectations}; + TestFunctionCall test{call}; + + BOOST_REQUIRE_EQUAL(test.format(), "// f(uint8): 1 -> FAILURE"); } BOOST_AUTO_TEST_CASE(format_parameter_encoding_too_short) { bytes expectedBytes = toBigEndian(u256{1}) + toBigEndian(u256{1}); - ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; + ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 20}; Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; FunctionCallExpectations expectations{vector{param, param}, false, string{}}; FunctionCallArgs arguments{vector{param, param}, string{}}; FunctionCall call{"f(uint8, uint8)", 0, arguments, expectations}; TestFunctionCall test{call}; - BOOST_REQUIRE_THROW(test.format(), runtime_error); + bytes resultBytes = toBigEndian(u256{1}) + toBigEndian(u256{2}); + test.setRawBytes(resultBytes); + test.setFailure(false); + + BOOST_REQUIRE_THROW(test.format("", true), runtime_error); } BOOST_AUTO_TEST_CASE(format_byte_range_too_short) { - bytes expectedBytes{0}; + bytes expectedBytes = toBigEndian(u256{1}); ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; FunctionCallExpectations expectations{vector{param, param}, false, string{}}; @@ -178,7 +264,11 @@ BOOST_AUTO_TEST_CASE(format_byte_range_too_short) FunctionCall call{"f(uint8, uint8)", 0, arguments, expectations}; TestFunctionCall test{call}; - BOOST_REQUIRE_THROW(test.format(), runtime_error); + bytes resultBytes{0}; + test.setRawBytes(resultBytes); + test.setFailure(false); + + BOOST_REQUIRE_THROW(test.format("", true), runtime_error); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index fa8e593d57eb..9d24ecd918fc 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -295,6 +295,74 @@ BOOST_AUTO_TEST_CASE(if_statement) BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }")); } +BOOST_AUTO_TEST_CASE(for_statement) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_break) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {break} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_break_init) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0 break} iszero(eq(i, 10)) {i := add(i, 1)} {} }", + SyntaxError, + "Keyword break outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_break_post) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) break} {} }", + SyntaxError, + "Keyword break outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_nested_break) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { break } } }", + SyntaxError, + "Keyword break outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {continue} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue_fail_init) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0 continue} iszero(eq(i, 10)) {i := add(i, 1)} {} }", + SyntaxError, + "Keyword continue outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) continue} {} }", + SyntaxError, + "Keyword continue outside for-loop body is not allowed.", + dialect); +} + BOOST_AUTO_TEST_CASE(if_statement_invalid) { CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/bugfix_visit_after_change.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/bugfix_visit_after_change.yul new file mode 100644 index 000000000000..a68984b8bbb3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/bugfix_visit_after_change.yul @@ -0,0 +1,13 @@ +{ + // This tests that a bug is fixed where x := 1 was wrongfully + // taken into account before actually visiting the if statement. + let x := 0 + if x { + x := 1 + } +} +// ---- +// structuralSimplifier +// { +// let x := 0 +// } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match_mixed.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match_mixed.yul new file mode 100644 index 000000000000..8d90ca1f08ee --- /dev/null +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match_mixed.yul @@ -0,0 +1,12 @@ +{ + // Used to cause assert error + let y := 200 + switch 3 + case "" { y := 8 } + case 1 { y := 9 } +} +// ---- +// structuralSimplifier +// { +// let y := 200 +// } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_no_remove_empty_case.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_no_remove_empty_case.yul new file mode 100644 index 000000000000..f6e99cd4802f --- /dev/null +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_no_remove_empty_case.yul @@ -0,0 +1,21 @@ +{ + let y := 200 + switch calldataload(0) + case 0 { } + case 1 { y := 9 } + default { y := 100 } +} +// ---- +// structuralSimplifier +// { +// let y := 200 +// switch calldataload(0) +// case 0 { +// } +// case 1 { +// y := 9 +// } +// default { +// y := 100 +// } +// } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul new file mode 100644 index 000000000000..14043b2d0d46 --- /dev/null +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul @@ -0,0 +1,19 @@ +{ + let y := 200 + switch add(y, 4) + case 0 { } + case 1 { } + default { } + + switch mload(4) + case 0 { } + case 1 { } + default { } +} +// ---- +// structuralSimplifier +// { +// let y := 200 +// pop(add(y, 4)) +// pop(mload(4)) +// } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul new file mode 100644 index 000000000000..f1a09eb8aa56 --- /dev/null +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul @@ -0,0 +1,19 @@ +{ + let y := 200 + switch calldataload(0) + case 0 { } + case 1 { y := 9 } + case 2 { y := 10 } +} +// ---- +// structuralSimplifier +// { +// let y := 200 +// switch calldataload(0) +// case 1 { +// y := 9 +// } +// case 2 { +// y := 10 +// } +// } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul new file mode 100644 index 000000000000..d90a0e1731ae --- /dev/null +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul @@ -0,0 +1,16 @@ +{ + let y := 200 + switch calldataload(0) + case 0 { } + case 1 { y := 9 } + default { } +} +// ---- +// structuralSimplifier +// { +// let y := 200 +// if eq(1, calldataload(0)) +// { +// y := 9 +// } +// } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul new file mode 100644 index 000000000000..158cd013e8f2 --- /dev/null +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul @@ -0,0 +1,19 @@ +{ + let y := 200 + switch calldataload(0) + case 1 { y := 9 } + case 2 { y := 10 } + default { } +} +// ---- +// structuralSimplifier +// { +// let y := 200 +// switch calldataload(0) +// case 1 { +// y := 9 +// } +// case 2 { +// y := 10 +// } +// } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul index a741ac2fa4f2..e015dc1f314d 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul @@ -1,10 +1,10 @@ { - switch 1 case 2 { mstore(0, 0) } + switch calldataload(0) case 2 { mstore(0, 0) } } // ---- // structuralSimplifier // { -// if eq(2, 1) +// if eq(2, calldataload(0)) // { // mstore(0, 0) // } diff --git a/test/tools/ossfuzz/const_opt_ossfuzz.cpp b/test/tools/ossfuzz/const_opt_ossfuzz.cpp index b394d7dafdef..13ceb840851b 100644 --- a/test/tools/ossfuzz/const_opt_ossfuzz.cpp +++ b/test/tools/ossfuzz/const_opt_ossfuzz.cpp @@ -21,7 +21,10 @@ using namespace std; extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) { - string input(reinterpret_cast(_data), _size); - FuzzerUtil::testConstantOptimizer(input, true); + if (_size <= 250) + { + string input(reinterpret_cast(_data), _size); + FuzzerUtil::testConstantOptimizer(input, /*quiet=*/true); + } return 0; } \ No newline at end of file diff --git a/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp index 7e28c3aca613..3a2ac5f279ed 100644 --- a/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp +++ b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp @@ -21,7 +21,10 @@ using namespace std; extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) { - string input(reinterpret_cast(_data), _size); - FuzzerUtil::testCompiler(input, /*optimize=*/false, /*quiet=*/true); + if (_size <= 600) + { + string input(reinterpret_cast(_data), _size); + FuzzerUtil::testCompiler(input, /*optimize=*/false, /*quiet=*/true); + } return 0; } diff --git a/test/tools/ossfuzz/solc_opt_ossfuzz.cpp b/test/tools/ossfuzz/solc_opt_ossfuzz.cpp index 3ad8e5f74f3e..72a59cba40eb 100644 --- a/test/tools/ossfuzz/solc_opt_ossfuzz.cpp +++ b/test/tools/ossfuzz/solc_opt_ossfuzz.cpp @@ -21,7 +21,10 @@ using namespace std; extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) { - string input(reinterpret_cast(_data), _size); - FuzzerUtil::testCompiler(input, /*optimize=*/true, /*quiet=*/true); + if (_size <= 600) + { + string input(reinterpret_cast(_data), _size); + FuzzerUtil::testCompiler(input, /*optimize=*/true, /*quiet=*/true); + } return 0; } diff --git a/test/tools/ossfuzz/yulProtoFuzzer.cpp b/test/tools/ossfuzz/yulProtoFuzzer.cpp index b959bc1d8425..c4416a61eacd 100644 --- a/test/tools/ossfuzz/yulProtoFuzzer.cpp +++ b/test/tools/ossfuzz/yulProtoFuzzer.cpp @@ -33,6 +33,8 @@ using namespace std; DEFINE_BINARY_PROTO_FUZZER(Function const& _input) { string yul_source = functionToString(_input); + if (yul_source.size() > 600) + return; if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index 0d210e0922fa..72e320ef57ae 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -39,6 +39,8 @@ using namespace yul::test; DEFINE_BINARY_PROTO_FUZZER(Function const& _input) { string yul_source = functionToString(_input); + if (yul_source.size() > 600) + return; if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index c092da446a8c..e9b4dbd94450 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -105,12 +105,27 @@ void Interpreter::operator()(ForLoop const& _forLoop) visit(statement); while (evaluate(*_forLoop.condition) != 0) { + m_state.loopState = LoopState::Default; (*this)(_forLoop.body); + if (m_state.loopState == LoopState::Break) + break; + (*this)(_forLoop.post); } + m_state.loopState = LoopState::Default; closeScope(); } +void Interpreter::operator()(Break const&) +{ + m_state.loopState = LoopState::Break; +} + +void Interpreter::operator()(Continue const&) +{ + m_state.loopState = LoopState::Continue; +} + void Interpreter::operator()(Block const& _block) { openScope(); @@ -122,7 +137,14 @@ void Interpreter::operator()(Block const& _block) m_functions[funDef.name] = &funDef; m_scopes.back().insert(funDef.name); } - ASTWalker::operator()(_block); + + for (auto const& statement: _block.statements) + { + visit(statement); + if (m_state.loopState != LoopState::Default) + break; + } + closeScope(); } @@ -155,20 +177,7 @@ void ExpressionEvaluator::operator()(Literal const& _literal) static YulString const trueString("true"); static YulString const falseString("false"); - switch (_literal.kind) - { - case LiteralKind::Boolean: - solAssert(_literal.value == trueString || _literal.value == falseString, ""); - setValue(_literal.value == trueString ? 1 : 0); - break; - case LiteralKind::Number: - setValue(valueOfNumberLiteral(_literal)); - break; - case LiteralKind::String: - solAssert(_literal.value.str().size() <= 32, ""); - setValue(u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft))); - break; - } + setValue(valueOfLiteral(_literal)); } void ExpressionEvaluator::operator()(Identifier const& _identifier) diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 267fe0a4d65a..06cfe939dd34 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -39,6 +39,13 @@ class InterpreterTerminated: dev::Exception { }; +enum class LoopState +{ + Default, + Continue, + Break, +}; + struct InterpreterState { dev::bytes calldata; @@ -65,6 +72,7 @@ struct InterpreterState std::vector trace; /// This is actually an input parameter that more or less limits the runtime. size_t maxTraceSize = 0; + LoopState loopState = LoopState::Default; }; /** @@ -90,6 +98,8 @@ class Interpreter: public ASTWalker void operator()(Switch const& _switch) override; void operator()(FunctionDefinition const&) override; void operator()(ForLoop const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; void operator()(Block const& _block) override; std::vector const& trace() const { return m_state.trace; }