diff options
49 files changed, 1424 insertions, 1355 deletions
diff --git a/Changelog.md b/Changelog.md index 4bf85d5f..3d8701ca 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,16 +5,28 @@ Features: * Assembly: Display auxiliary data in the assembly output. * Assembly: Add ``CREATE2`` (EIP86), ``STATICCALL`` (EIP214), ``RETURNDATASIZE`` and ``RETURNDATACOPY`` (EIP211) instructions. * AST: export all attributes to JSON format. + * C API (``jsonCompiler``): Use the Standard JSON I/O internally. * Inline Assembly: Present proper error message when not supplying enough arguments to a functional instruction. * Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias. * Inline Assembly: ``for`` and ``switch`` statements. * Inline Assembly: function definitions and function calls. + * Type Checker: Warn about copies in storage that might overwrite unexpectedly. + * Code Generator: Added the Whiskers template system. + * Remove obsolete Why3 output. + * Type Checker: Enforce strict UTF-8 validation. Bugfixes: + * Code generator: Use ``REVERT`` instead of ``INVALID`` for generated input validation routines. + * Type Checker: Fix address literals not being treated as compile-time constants. + * Type Checker: Disallow invoking the same modifier multiple times. * Type Checker: Make UTF8-validation a bit more sloppy to include more valid sequences. + * Type Checker: Disallow comparisons between mapping and non-internal function types. + * Type Checker: Do not treat strings that look like addresses as addresses. + * Type Checker: Support valid, but incorrectly rejected UTF-8 sequences. * Fixed crash concerning non-callable types. * Unused variable warnings no longer issued for variables used inside inline assembly. + * Code Generator: Fix ABI encoding of empty literal string. * Inline Assembly: Enforce function arguments when parsing functional instructions. * Fixed segfault with constant function parameters diff --git a/cmake/UseDev.cmake b/cmake/UseDev.cmake index 4461a8a0..68df691a 100644 --- a/cmake/UseDev.cmake +++ b/cmake/UseDev.cmake @@ -10,6 +10,7 @@ function(eth_apply TARGET REQUIRED SUBMODULE) target_link_libraries(${TARGET} ${Boost_RANDOM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_FILESYSTEM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_SYSTEM_LIBRARIES}) + target_link_libraries(${TARGET} ${Boost_REGEX_LIBRARIES}) if (DEFINED MSVC) target_link_libraries(${TARGET} ${Boost_CHRONO_LIBRARIES}) diff --git a/deps b/deps -Subproject b3db8905894eafb74a436b702de78ba235f3a3b +Subproject e5c8316db8d3daa0abc3b5af8545ce330057608 diff --git a/docs/assembly.rst b/docs/assembly.rst index 7ef41483..83643634 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -13,6 +13,8 @@ TODO: Write about how scoping rules of inline assembly are a bit different and the complications that arise when for example using internal functions of libraries. Furthermore, write about the symbols defined by the compiler. +.. _inline-assembly: + Inline Assembly =============== diff --git a/docs/bugs.json b/docs/bugs.json index 1a67d626..a0c0e7c4 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,5 +1,12 @@ [ { + "name": "SkipEmptyStringLiteral", + "summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.", + "description": "If the empty string literal \"\" is used as an argument in a function call, it is skipped by the encoder. This has the effect that the encoding of all arguments following this is shifted left by 32 bytes and thus the function call data is corrupted.", + "fixed": "0.4.12", + "severity": "low" + }, + { "name": "ConstantOptimizerSubtraction", "summary": "In some situations, the optimizer replaces certain numbers in the code with routines that compute different numbers.", "description": "The optimizer tries to represent any number in the bytecode by routines that compute them with less gas. For some special numbers, an incorrect routine is generated. This could allow an attacker to e.g. trick victims about a specific amount of ether, or function calls to call different functions (or none at all).", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 0f7346b4..1be05f3c 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -1,6 +1,7 @@ { "0.1.0": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "OptimizerStaleKnowledgeAboutSHA3", @@ -15,6 +16,7 @@ }, "0.1.1": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "OptimizerStaleKnowledgeAboutSHA3", @@ -29,6 +31,7 @@ }, "0.1.2": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "OptimizerStaleKnowledgeAboutSHA3", @@ -43,6 +46,7 @@ }, "0.1.3": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "OptimizerStaleKnowledgeAboutSHA3", @@ -57,6 +61,7 @@ }, "0.1.4": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "OptimizerStaleKnowledgeAboutSHA3", @@ -71,6 +76,7 @@ }, "0.1.5": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "OptimizerStaleKnowledgeAboutSHA3", @@ -85,6 +91,7 @@ }, "0.1.6": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -100,6 +107,7 @@ }, "0.1.7": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -115,6 +123,7 @@ }, "0.2.0": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -130,6 +139,7 @@ }, "0.2.1": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -145,6 +155,7 @@ }, "0.2.2": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -160,6 +171,7 @@ }, "0.3.0": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -174,6 +186,7 @@ }, "0.3.1": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -187,6 +200,7 @@ }, "0.3.2": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -200,6 +214,7 @@ }, "0.3.3": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -212,6 +227,7 @@ }, "0.3.4": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -224,6 +240,7 @@ }, "0.3.5": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -236,6 +253,7 @@ }, "0.3.6": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -246,6 +264,7 @@ }, "0.4.0": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -256,6 +275,7 @@ }, "0.4.1": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -266,16 +286,20 @@ }, "0.4.10": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], "released": "2017-03-15" }, "0.4.11": { - "bugs": [], + "bugs": [ + "SkipEmptyStringLiteral" + ], "released": "2017-05-03" }, "0.4.2": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage", @@ -285,6 +309,7 @@ }, "0.4.3": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "HighOrderByteCleanStorage" @@ -293,6 +318,7 @@ }, "0.4.4": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored" ], @@ -300,6 +326,7 @@ }, "0.4.5": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored", "OptimizerStateKnowledgeNotResetForJumpdest" @@ -308,6 +335,7 @@ }, "0.4.6": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction", "IdentityPrecompileReturnIgnored" ], @@ -315,18 +343,21 @@ }, "0.4.7": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], "released": "2016-12-15" }, "0.4.8": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], "released": "2017-01-13" }, "0.4.9": { "bugs": [ + "SkipEmptyStringLiteral", "ConstantOptimizerSubtraction" ], "released": "2017-01-31" diff --git a/docs/contributing.rst b/docs/contributing.rst index 1f869dbb..559f9f6a 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -74,3 +74,22 @@ To run a subset of tests, filters can be used: ``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``. Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests. + +Whiskers +======== + +*Whiskers* is a templating system similar to `Moustache <https://mustache.github.io>`_. It is used by the +compiler in various places to aid readability, and thus maintainability and verifiability, of the code. + +The syntax comes with a substantial difference to Moustache: the template markers ``{{`` and ``}}`` are +replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`inline-assembly` +(The symbols ``<`` and ``>`` are invalid in inline assembly, while ``{`` and ``}`` are used to delimit blocks). +Another limitation is that lists are only resolved one depth and they will not recurse. This may change in the future. + +A rough specification is the following: + +Any occurrence of ``<name>`` is replaced by the string-value of the supplied variable ``name`` without any +escaping and without iterated replacements. An area can be delimited by ``<#name>...</name>``. It is replaced +by as many concatenations of its contents as there were sets of variables supplied to the template system, +each time replacing any ``<inner>`` items by their respective value. Top-level variales can also be used +inside such areas.
\ No newline at end of file diff --git a/docs/control-structures.rst b/docs/control-structures.rst index a2d34274..03787c20 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -361,55 +361,72 @@ As a result, the following code is legal, despite being poorly written:: return bar;// returns 5 } -.. index:: ! exception, ! throw +.. index:: ! exception, ! throw, ! assert, ! require, ! revert -Exceptions -========== +Error handling: Assert, Require, Revert and Exceptions +====================================================== + +Solidity uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the +state in the current call (and all its sub-calls) and also flag an error to the caller. +The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception +if the condition is not met. The difference between the two is that ``assert`` should only be used for internal errors +and ``require`` should be used to check external conditions (invalid inputs or errors in external components). +The idea behind that is that analysis tools can check your contract and try to come up with situations and +series of function calls that will reach a failing assertion. If this is possible, this means there is a bug +in your contract you should fix. -There are some cases where exceptions are thrown automatically (see below). You can use the ``throw`` instruction to throw an exception manually. The effect of an exception is that the currently executing call is stopped and reverted (i.e. all changes to the state and balances are undone) and the exception is also "bubbled up" through Solidity function calls (exceptions are ``send`` and the low-level functions ``call``, ``delegatecall`` and ``callcode``, those return ``false`` in case of an exception). +There are two other ways to trigger execptions: The ``revert`` function can be used to flag an error and +revert the current call. In the future it might be possible to also include details about the error +in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``. + +When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send`` +and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case +of an exception instead of "bubbling up". Catching exceptions is not yet possible. -In the following example, we show how ``throw`` can be used to easily revert an Ether transfer and also how to check the return value of ``send``:: +In the following example, you can see how ``require`` can be used to easily check conditions on inputs +and how ``assert`` can be used for internal error checking:: pragma solidity ^0.4.0; contract Sharer { function sendHalf(address addr) payable returns (uint balance) { - if (!addr.send(msg.value / 2)) - throw; // also reverts the transfer to Sharer + require(msg.value % 2 == 0); // Only allow even numbers + uint balanceBeforeTransfer = this.balance; + addr.transfer(msg.value / 2); + // Since transfer throws an exception on failure and + // cannot call back here, there should be no way for us to + // still have half of the money. + assert(this.balance == balanceBeforeTransfer - msg.value / 2); return this.balance; } } -Currently, Solidity automatically generates a runtime exception in the following situations: +An ``assert``-style exception is generated in the following situations: #. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). #. If you access a fixed-length ``bytesN`` at a too large or negative index. -#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. -#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). #. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). #. If you shift by a negative amount. #. If you convert a value too big or negative into an enum type. -#. If you perform an external function call targeting a contract that contains no code. -#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). -#. If your contract receives Ether via a public getter function. #. If you call a zero-initialized variable of internal function type. -#. If a ``.transfer()`` fails. #. If you call ``assert`` with an argument that evaluates to false. -While a user-provided exception is generated in the following situations: +A ``require``-style exception is generated in the following situations: #. Calling ``throw``. #. Calling ``require`` with an argument that evaluates to ``false``. +#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. +#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). +#. If you perform an external function call targeting a contract that contains no code. +#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). +#. If your contract receives Ether via a public getter function. +#. If a ``.transfer()`` fails. -Internally, Solidity performs a revert operation (instruction ``0xfd``) when a user-provided exception is thrown or the condition of -a ``require`` call is not met. In contrast, it performs an invalid operation -(instruction ``0xfe``) if a runtime exception is encountered or the condition of an ``assert`` call is not met. In both cases, this causes -the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect +Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation +(instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes +the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction -(or at least call) without effect. - -If contracts are written so that ``assert`` is only used to test internal conditions and ``require`` -is used in case of malformed input, a formal analysis tool that verifies that the invalid -opcode can never be reached can be used to check for the absence of errors assuming valid inputs. +(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while +``revert``-style exceptions will not consume any gas starting from the Metropolis release.
\ No newline at end of file diff --git a/docs/grammar.txt b/docs/grammar.txt index b38b7ffa..6c041460 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -20,9 +20,12 @@ StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' )? Ident UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';' StructDefinition = 'struct' Identifier '{' ( VariableDeclaration ';' (VariableDeclaration ';')* )? '}' + ModifierDefinition = 'modifier' Identifier ParameterList? Block +ModifierInvocation = Identifier ( '(' ExpressionList? ')' )? + FunctionDefinition = 'function' Identifier? ParameterList - ( FunctionCall | Identifier | 'constant' | 'payable' | 'external' | 'public' | 'internal' | 'private' )* + ( ModifierInvocation | 'constant' | 'payable' | 'external' | 'public' | 'internal' | 'private' )* ( 'returns' ParameterList )? ( ';' | Block ) EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';' @@ -72,8 +75,13 @@ VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expressi IdentifierList = '(' ( Identifier? ',' )* Identifier? ')' // Precedence by order (see github.com/ethereum/solidity/pull/732) -Expression = - ( Expression ('++' | '--') | FunctionCall | IndexAccess | MemberAccess | '(' Expression ')' ) +Expression + = Expression ('++' | '--') + | NewExpression + | IndexAccess + | MemberAccess + | FunctionCall + | '(' Expression ')' | ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression | Expression '**' Expression | Expression ('*' | '/' | '%') Expression @@ -90,18 +98,18 @@ Expression = | Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression | PrimaryExpression -PrimaryExpression = Identifier - | BooleanLiteral +PrimaryExpression = BooleanLiteral | NumberLiteral | HexLiteral | StringLiteral | TupleExpression + | Identifier | ElementaryTypeNameExpression ExpressionList = Expression ( ',' Expression )* NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )* -FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' FunctionCallArguments ')' +FunctionCall = Expression '(' FunctionCallArguments ')' FunctionCallArguments = '{' NameValueList? '}' | ExpressionList? diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 08160dad..9b5ba9f2 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -267,7 +267,7 @@ If there are local modifications, the commit will be postfixed with ``.mod``. These parts are combined as required by Semver, where the Solidity pre-release tag equals to the Semver pre-release and the Solidity commit and platform combined make up the Semver build metadata. -A relase example: ``0.4.8+commit.60cc1668.Emscripten.clang``. +A release example: ``0.4.8+commit.60cc1668.Emscripten.clang``. A pre-release example: ``0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang`` diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 2e0ccf45..182de33a 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -394,12 +394,14 @@ The following is the order of precedence for operators, listed in order of evalu +============+=====================================+============================================+ | *1* | Postfix increment and decrement | ``++``, ``--`` | + +-------------------------------------+--------------------------------------------+ -| | Function-like call | ``<func>(<args...>)`` | +| | New expression | ``new <typename>`` | + +-------------------------------------+--------------------------------------------+ | | Array subscripting | ``<array>[<index>]`` | + +-------------------------------------+--------------------------------------------+ | | Member access | ``<object>.<member>`` | + +-------------------------------------+--------------------------------------------+ +| | Function-like call | ``<func>(<args...>)`` | ++ +-------------------------------------+--------------------------------------------+ | | Parentheses | ``(<statement>)`` | +------------+-------------------------------------+--------------------------------------------+ | *2* | Prefix increment and decrement | ``++``, ``--`` | @@ -462,7 +464,7 @@ Global Variables - ``tx.gasprice`` (``uint``): gas price of the transaction - ``tx.origin`` (``address``): sender of the transaction (full call chain) - ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error) -- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input) +- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component) - ``revert()``: abort execution and revert state changes - ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments - ``sha3(...) returns (bytes32)``: an alias to `keccak256()` diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 779e3819..7d21f065 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -79,13 +79,23 @@ Block and Transaction Properties You can only access the hashes of the most recent 256 blocks, all other values will be zero. -.. index:: assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: assert, revert, require + +Error Handling +-------------- + +``assert(bool condition)``: + throws if the condition is not met - to be used for internal errors. +``require(bool condition)``: + throws if the condition is not met - to be used for errors in inputs or external components. +``revert()``: + abort execution and revert state changes + +.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, Mathematical and Cryptographic Functions ---------------------------------------- -``assert(bool condition)``: - throws if the condition is not met. ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``. ``mulmod(uint x, uint y, uint k) returns (uint)``: @@ -101,8 +111,6 @@ Mathematical and Cryptographic Functions ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover the address associated with the public key from elliptic curve signature or return zero on error (`example usage <https://ethereum.stackexchange.com/q/1777/222>`_) -``revert()``: - abort execution and revert state changes In the above, "tightly packed" means that the arguments are concatenated without padding. This means that the following are all identical:: @@ -122,6 +130,7 @@ This means that, for example, ``keccak256(0) == keccak256(uint8(0))`` and It might be that you run into Out-of-Gas for ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*. The reason for this is that those are implemented as so-called precompiled contracts and these contracts only really exist after they received the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution runs into an Out-of-Gas error. A workaround for this problem is to first send e.g. 1 Wei to each of the contracts before you use them in your actual contracts. This is not an issue on the official or test net. +.. index:: balance, send, transfer, call, callcode, delegatecall .. _address_related: Address Related diff --git a/libdevcore/UTF8.cpp b/libdevcore/UTF8.cpp index 449ccc5d..2ae720ec 100644 --- a/libdevcore/UTF8.cpp +++ b/libdevcore/UTF8.cpp @@ -27,25 +27,74 @@ namespace dev { +namespace +{ -bool validateUTF8(std::string const& _input, size_t& _invalidPosition) +/// Validate byte sequence against Unicode chapter 3 Table 3-7. +bool isWellFormed(unsigned char byte1, unsigned char byte2) +{ + if (byte1 == 0xc0 || byte1 == 0xc1) + return false; + else if (byte1 >= 0xc2 && byte1 <= 0xdf) + return true; + else if (byte1 == 0xe0) + { + if (byte2 < 0xa0) + return false; + else + return true; + } + else if (byte1 >= 0xe1 && byte1 <= 0xec) + return true; + else if (byte1 == 0xed) + { + if (byte2 > 0x9f) + return false; + else + return true; + } + else if (byte1 == 0xee || byte1 == 0xef) + return true; + else if (byte1 == 0xf0) + { + if (byte2 < 0x90) + return false; + else + return true; + } + else if (byte1 >= 0xf1 && byte1 <= 0xf3) + return true; + else if (byte1 == 0xf4) + { + if (byte2 > 0x8f) + return false; + else + return true; + } + /// 0xf5 .. 0xf7 is disallowed + /// Technically anything below 0xc0 or above 0xf7 is + /// not possible to encode using Table 3-6 anyway. + return false; +} + +bool validateUTF8(const unsigned char *_input, size_t _length, size_t& _invalidPosition) { - const size_t length = _input.length(); bool valid = true; size_t i = 0; - for (; i < length; i++) + for (; i < _length; i++) { - if ((unsigned char)_input[i] < 0x80) + // Check for Unicode Chapter 3 Table 3-6 conformity. + if (_input[i] < 0x80) continue; size_t count = 0; - switch(_input[i] & 0xf0) { - case 0xc0: count = 1; break; - case 0xe0: count = 2; break; - case 0xf0: count = 3; break; - default: break; - } + if (_input[i] >= 0xc0 && _input[i] <= 0xdf) + count = 1; + else if (_input[i] >= 0xe0 && _input[i] <= 0xef) + count = 2; + else if (_input[i] >= 0xf0 && _input[i] <= 0xf7) + count = 3; if (count == 0) { @@ -53,7 +102,7 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition) break; } - if ((i + count) >= length) + if ((i + count) >= _length) { valid = false; break; @@ -67,6 +116,13 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition) valid = false; break; } + + // Check for Unicode Chapter 3 Table 3-7 conformity. + if ((j == 0) && !isWellFormed(_input[i - 1], _input[i])) + { + valid = false; + break; + } } } @@ -77,5 +133,11 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition) return false; } +} + +bool validateUTF8(std::string const& _input, size_t& _invalidPosition) +{ + return validateUTF8(reinterpret_cast<unsigned char const*>(_input.c_str()), _input.length(), _invalidPosition); +} } diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp new file mode 100644 index 00000000..4bad8476 --- /dev/null +++ b/libdevcore/Whiskers.cpp @@ -0,0 +1,127 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** @file Whiskers.cpp + * @author Chris <chis@ethereum.org> + * @date 2017 + * + * Moustache-like templates. + */ + +#include <libdevcore/Whiskers.h> + +#include <libdevcore/Assertions.h> + +#include <boost/regex.hpp> + +using namespace std; +using namespace dev; + +Whiskers::Whiskers(string const& _template): +m_template(_template) +{ +} + +Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value) +{ + assertThrow( + m_parameters.count(_parameter) == 0, + WhiskersError, + _parameter + " already set." + ); + assertThrow( + m_listParameters.count(_parameter) == 0, + WhiskersError, + _parameter + " already set as list parameter." + ); + m_parameters[_parameter] = _value; + + return *this; +} + +Whiskers& Whiskers::operator ()( + string const& _listParameter, + vector<map<string, string>> const& _values +) +{ + assertThrow( + m_listParameters.count(_listParameter) == 0, + WhiskersError, + _listParameter + " already set." + ); + assertThrow( + m_parameters.count(_listParameter) == 0, + WhiskersError, + _listParameter + " already set as value parameter." + ); + m_listParameters[_listParameter] = _values; + + return *this; +} + +string Whiskers::render() const +{ + return replace(m_template, m_parameters, m_listParameters); +} + +string Whiskers::replace( + string const& _template, + StringMap const& _parameters, + map<string, vector<StringMap>> const& _listParameters +) +{ + using namespace boost; + static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>"); + return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string + { + string tagName(_match[1]); + if (!tagName.empty()) + { + assertThrow(_parameters.count(tagName), WhiskersError, "Tag " + tagName + " not found."); + return _parameters.at(tagName); + } + else + { + string listName(_match[2]); + string templ(_match[3]); + assertThrow(!listName.empty(), WhiskersError, ""); + assertThrow( + _listParameters.count(listName), + WhiskersError, "List parameter " + listName + " not set." + ); + string replacement; + for (auto const& parameters: _listParameters.at(listName)) + replacement += replace(templ, joinMaps(_parameters, parameters)); + return replacement; + } + }); +} + +Whiskers::StringMap Whiskers::joinMaps( + Whiskers::StringMap const& _a, + Whiskers::StringMap const& _b +) +{ + Whiskers::StringMap ret = _a; + for (auto const& x: _b) + assertThrow( + ret.insert(x).second, + WhiskersError, + "Parameter collision" + ); + return ret; +} + diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h new file mode 100644 index 00000000..21d46af4 --- /dev/null +++ b/libdevcore/Whiskers.h @@ -0,0 +1,87 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** @file Whiskers.h + * @author Chris <chis@ethereum.org> + * @date 2017 + * + * Moustache-like templates. + */ + +#pragma once + +#include <libdevcore/Exceptions.h> + +#include <string> +#include <map> +#include <vector> + +namespace dev +{ + +DEV_SIMPLE_EXCEPTION(WhiskersError); + +/// +/// Moustache-like templates. +/// +/// Usage: +/// std::vector<std::map<std::string, std::string>> listValues(2); +/// listValues[0]["k"] = "key1"; +/// listValues[0]["v"] = "value1"; +/// listValues[1]["k"] = "key2"; +/// listValues[1]["v"] = "value2"; +/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>") +/// ("p1", "HEAD") +/// ("list", listValues) +/// .render(); +/// +/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" +/// +/// Note that lists cannot themselves contain lists - this would be a future feature. +class Whiskers +{ +public: + using StringMap = std::map<std::string, std::string>; + using StringListMap = std::map<std::string, std::vector<StringMap>>; + + explicit Whiskers(std::string const& _template); + + /// Sets a single parameter, <paramName>. + Whiskers& operator()(std::string const& _parameter, std::string const& _value); + /// Sets a list parameter, <#listName> </listName>. + Whiskers& operator()( + std::string const& _listParameter, + std::vector<StringMap> const& _values + ); + + std::string render() const; + +private: + static std::string replace( + std::string const& _template, + StringMap const& _parameters, + StringListMap const& _listParameters = StringListMap() + ); + + /// Joins the two maps throwing an exception if two keys are equal. + static StringMap joinMaps(StringMap const& _a, StringMap const& _b); + + std::string m_template; + StringMap m_parameters; + StringListMap m_listParameters; +}; + +} diff --git a/libjulia/backends/evm/AbstractAssembly.h b/libjulia/backends/evm/AbstractAssembly.h index f667c1a7..cfc9b8a5 100644 --- a/libjulia/backends/evm/AbstractAssembly.h +++ b/libjulia/backends/evm/AbstractAssembly.h @@ -89,6 +89,9 @@ public: /// Return from a subroutine. /// @param _stackDiffAfter the stack adjustment after this instruction. virtual void appendReturnsub(int _returns, int _stackDiffAfter = 0) = 0; + + /// Append the assembled size as a constant. + virtual void appendAssemblySize() = 0; }; enum class IdentifierContext { LValue, RValue }; diff --git a/libjulia/backends/evm/EVMAssembly.cpp b/libjulia/backends/evm/EVMAssembly.cpp index 7ec26957..173d5e93 100644 --- a/libjulia/backends/evm/EVMAssembly.cpp +++ b/libjulia/backends/evm/EVMAssembly.cpp @@ -32,6 +32,8 @@ namespace { /// Size of labels in bytes. Four-byte labels are required by some EVM1.5 instructions. size_t constexpr labelReferenceSize = 4; + +size_t constexpr assemblySizeReferenceSize = 4; } @@ -145,17 +147,19 @@ void EVMAssembly::appendReturnsub(int _returns, int _stackDiffAfter) eth::LinkerObject EVMAssembly::finalize() { + size_t bytecodeSize = m_bytecode.size(); + for (auto const& ref: m_assemblySizePositions) + updateReference(ref, assemblySizeReferenceSize, u256(bytecodeSize)); + for (auto const& ref: m_labelReferences) { size_t referencePos = ref.first; solAssert(m_labelPositions.count(ref.second), ""); size_t labelPos = m_labelPositions.at(ref.second); solAssert(labelPos != size_t(-1), "Undefined but allocated label used."); - solAssert(m_bytecode.size() >= 4 && referencePos <= m_bytecode.size() - 4, ""); - solAssert(uint64_t(labelPos) < (uint64_t(1) << (8 * labelReferenceSize)), ""); - for (size_t i = 0; i < labelReferenceSize; i++) - m_bytecode[referencePos + i] = byte((labelPos >> (8 * (labelReferenceSize - i - 1))) & 0xff); + updateReference(referencePos, labelReferenceSize, u256(labelPos)); } + eth::LinkerObject obj; obj.bytecode = m_bytecode; return obj; @@ -173,3 +177,18 @@ void EVMAssembly::appendLabelReferenceInternal(LabelID _labelId) m_labelReferences[m_bytecode.size()] = _labelId; m_bytecode += bytes(labelReferenceSize); } + +void EVMAssembly::appendAssemblySize() +{ + appendInstruction(solidity::pushInstruction(assemblySizeReferenceSize)); + m_assemblySizePositions.push_back(m_bytecode.size()); + m_bytecode += bytes(assemblySizeReferenceSize); +} + +void EVMAssembly::updateReference(size_t pos, size_t size, u256 value) +{ + solAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, ""); + solAssert(value < (u256(1) << (8 * size)), ""); + for (size_t i = 0; i < size; i++) + m_bytecode[pos + i] = byte((value >> (8 * (size - i - 1))) & 0xff); +} diff --git a/libjulia/backends/evm/EVMAssembly.h b/libjulia/backends/evm/EVMAssembly.h index a2df0cdc..69585822 100644 --- a/libjulia/backends/evm/EVMAssembly.h +++ b/libjulia/backends/evm/EVMAssembly.h @@ -70,6 +70,8 @@ public: /// Return from a subroutine. virtual void appendReturnsub(int _returns, int _stackDiffAfter) override; + /// Append the assembled size as a constant. + virtual void appendAssemblySize() override; /// Resolves references inside the bytecode and returns the linker object. eth::LinkerObject finalize(); @@ -77,6 +79,7 @@ public: private: void setLabelToCurrentPosition(AbstractAssembly::LabelID _labelId); void appendLabelReferenceInternal(AbstractAssembly::LabelID _labelId); + void updateReference(size_t pos, size_t size, u256 value); bool m_evm15 = false; ///< if true, switch to evm1.5 mode LabelID m_nextLabelId = 0; @@ -84,6 +87,7 @@ private: bytes m_bytecode; std::map<LabelID, size_t> m_labelPositions; std::map<size_t, LabelID> m_labelReferences; + std::vector<size_t> m_assemblySizePositions; }; } diff --git a/liblll/All.h b/liblll/All.h deleted file mode 100644 index 7c4192f6..00000000 --- a/liblll/All.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "CodeFragment.h" -#include "Compiler.h" -#include "CompilerState.h" -#include "Parser.h" diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 1329ec9b..7496fe83 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -171,11 +171,23 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) return string(); }; - auto varAddress = [&](string const& n) + auto varAddress = [&](string const& n, bool createMissing = false) { + if (n.empty()) + error<InvalidName>("Empty variable name not allowed"); auto it = _s.vars.find(n); if (it == _s.vars.end()) - error<InvalidName>(std::string("Symbol not found: ") + s); + { + if (createMissing) + { + // Create new variable + bool ok; + tie(it, ok) = _s.vars.insert(make_pair(n, make_pair(_s.stackSize, 32))); + _s.stackSize += 32; + } + else + error<InvalidName>(std::string("Symbol not found: ") + n); + } return it->second.first; }; @@ -208,7 +220,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) for (auto const& i: _t) if (c++ == 2) m_asm.append(CodeFragment(i, _s, false).m_asm); - m_asm.append((u256)varAddress(firstAsString())); + m_asm.append((u256)varAddress(firstAsString(), true)); m_asm.append(Instruction::MSTORE); } else if (us == "GET") diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 4194e1c2..ef8a9345 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -22,6 +22,7 @@ #include <libsolidity/analysis/TypeChecker.h> #include <memory> +#include <boost/algorithm/string/predicate.hpp> #include <boost/range/adaptor/reversed.hpp> #include <libsolidity/ast/AST.h> #include <libsolidity/inlineasm/AsmAnalysis.h> @@ -363,6 +364,35 @@ void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract) m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); } +void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) +{ + TupleType const& lhs = dynamic_cast<TupleType const&>(*type(_assignment.leftHandSide())); + TupleType const& rhs = dynamic_cast<TupleType const&>(*type(_assignment.rightHandSide())); + + bool fillRight = !lhs.components().empty() && (!lhs.components().back() || lhs.components().front()); + size_t storageToStorageCopies = 0; + size_t toStorageCopies = 0; + for (size_t i = 0; i < lhs.components().size(); ++i) + { + ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i].get()); + if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer()) + continue; + size_t rhsPos = fillRight ? i : rhs.components().size() - (lhs.components().size() - i); + solAssert(rhsPos < rhs.components().size(), ""); + toStorageCopies++; + if (rhs.components()[rhsPos]->dataStoredIn(DataLocation::Storage)) + storageToStorageCopies++; + } + if (storageToStorageCopies >= 1 && toStorageCopies >= 2) + m_errorReporter.warning( + _assignment.location(), + "This assignment performs two copies to storage. Since storage copies do not first " + "copy to a temporary location, one of them might be overwritten before the second " + "is executed and thus may have unexpected effects. It is safer to perform the copies " + "separately or assign to storage pointers first." + ); +} + void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) { auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); @@ -466,13 +496,26 @@ bool TypeChecker::visit(FunctionDefinition const& _function) var->accept(*this); } + set<Declaration const*> modifiers; for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers()) + { visitManually( *modifier, _function.isConstructor() ? dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts : vector<ContractDefinition const*>() ); + Declaration const* decl = &dereference(*modifier->name()); + if (modifiers.count(decl)) + { + if (dynamic_cast<ContractDefinition const*>(decl)) + m_errorReporter.declarationError(modifier->location(), "Base constructor already provided."); + else + m_errorReporter.declarationError(modifier->location(), "Modifier already used for this function."); + } + else + modifiers.insert(decl); + } if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) { if (_function.isImplemented()) @@ -1033,6 +1076,8 @@ bool TypeChecker::visit(Assignment const& _assignment) // Sequenced assignments of tuples is not valid, make the result a "void" type. _assignment.annotation().type = make_shared<TupleType>(); expectType(_assignment.rightHandSide(), *tupleType); + + checkDoubleStorageAssignment(_assignment); } else if (t->category() == Type::Category::Mapping) { @@ -1726,10 +1771,7 @@ void TypeChecker::endVisit(Literal const& _literal) if (_literal.looksLikeAddress()) { if (_literal.passesAddressChecksum()) - { _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address); - return; - } else m_errorReporter.warning( _literal.location(), @@ -1737,10 +1779,13 @@ void TypeChecker::endVisit(Literal const& _literal) "If this is not used as an address, please prepend '00'." ); } - _literal.annotation().type = Type::forLiteral(_literal); - _literal.annotation().isPure = true; + if (!_literal.annotation().type) + _literal.annotation().type = Type::forLiteral(_literal); + if (!_literal.annotation().type) m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); + + _literal.annotation().isPure = true; } bool TypeChecker::contractDependenciesAreCyclic( @@ -1800,7 +1845,23 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte _expectedType.toString() + "." ); - } + } + + if ( + type(_expression)->category() == Type::Category::RationalNumber && + _expectedType.category() == Type::Category::FixedBytes + ) + { + auto literal = dynamic_cast<Literal const*>(&_expression); + + if (literal && !boost::starts_with(literal->value(), "0x")) + m_errorReporter.warning( + _expression.location(), + "Decimal literal assigned to bytesXX variable will be left-aligned. " + "Use an explicit conversion to silence this warning." + ); + } + } void TypeChecker::requireLValue(Expression const& _expression) diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 2fa66f97..ee43d13a 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -69,6 +69,9 @@ private: void checkContractExternalTypeClashes(ContractDefinition const& _contract); /// Checks that all requirements for a library are fulfilled if this is a library. void checkLibraryRequirements(ContractDefinition const& _contract); + /// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage. + /// Should only be called if the left hand side is tuple-typed. + void checkDoubleStorageAssignment(Assignment const& _assignment); virtual void endVisit(InheritanceSpecifier const& _inheritance) override; virtual void endVisit(UsingForDirective const& _usingFor) override; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 40dfa348..b929b6fe 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -534,6 +534,8 @@ bool Literal::looksLikeAddress() const { if (subDenomination() != SubDenomination::None) return false; + if (token() != Token::Number) + return false; string lit = value(); return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index bd3346f9..7dc6c4a6 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2248,6 +2248,16 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const return TypePointer(); } +TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +{ + if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) + return TypePointer(); + FunctionType const& other = dynamic_cast<FunctionType const&>(*_other); + if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1) + return commonType(shared_from_this(), _other); + return TypePointer(); +} + string FunctionType::canonicalName(bool) const { solAssert(m_kind == Kind::External, ""); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c4ffc44c..f7a73ab5 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -933,6 +933,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override; virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; @@ -1038,6 +1039,7 @@ public: virtual std::string toString(bool _short) const override; virtual std::string canonicalName(bool _addDataLocation) const override; virtual bool canLiveOutsideStorage() const override { return false; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer encodingType() const override { return std::make_shared<IntegerType>(256); @@ -1116,11 +1118,7 @@ public: explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {} - virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override - { - return TypePointer(); - } - + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -1178,6 +1176,7 @@ public: virtual std::string identifier() const override { return "t_inaccessible"; } virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; } virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index d98efcad..6875bda1 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -26,6 +26,7 @@ #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/Version.h> #include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/parsing/Scanner.h> #include <libsolidity/inlineasm/AsmParser.h> #include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/inlineasm/AsmAnalysis.h> @@ -123,6 +124,7 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + solAssert(m_localVariables.count(&_declaration) == 0, "Variable already present"); m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; } @@ -243,6 +245,20 @@ CompilerContext& CompilerContext::appendConditionalInvalid() return *this; } +CompilerContext& CompilerContext::appendRevert() +{ + return *this << u256(0) << u256(0) << Instruction::REVERT; +} + +CompilerContext& CompilerContext::appendConditionalRevert() +{ + *this << Instruction::ISZERO; + eth::AssemblyItem afterTag = appendConditionalJump(); + appendRevert(); + *this << afterTag; + return *this; +} + void CompilerContext::resetVisitedNodes(ASTNode const* _node) { stack<ASTNode const*> newStack; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 030b35a6..1968c1e1 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -136,11 +136,15 @@ public: /// Appends a JUMP to a new tag and @returns the tag eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack - CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); + CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Appends an INVALID instruction - CompilerContext& appendInvalid(); + CompilerContext& appendInvalid(); /// Appends a conditional INVALID instruction - CompilerContext& appendConditionalInvalid(); + CompilerContext& appendConditionalInvalid(); + /// Appends a REVERT(0, 0) call + CompilerContext& appendRevert(); + /// Appends a conditional REVERT(0, 0) call + CompilerContext& appendConditionalRevert(); /// Appends a JUMP to a specific tag CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 3baaaddf..4edec155 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -128,7 +128,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound m_context << Instruction::DUP1; storeStringData(bytesConstRef(str->value())); if (_padToWordBoundaries) - m_context << u256(((str->value().size() + 31) / 32) * 32); + m_context << u256(max<size_t>(32, ((str->value().size() + 31) / 32) * 32)); else m_context << u256(str->value().size()); m_context << Instruction::ADD; @@ -305,15 +305,9 @@ void CompilerUtils::memoryCopy32() m_context.appendInlineAssembly(R"( { - jumpi(end, eq(len, 0)) - start: - mstore(dst, mload(src)) - jumpi(end, iszero(gt(len, 32))) - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - jump(start) - end: + for { let i := 0 } lt(i, len) { i := add(i, 32) } { + mstore(add(dst, i), mload(add(src, i))) + } } )", { "len", "dst", "src" } @@ -327,21 +321,22 @@ void CompilerUtils::memoryCopy() m_context.appendInlineAssembly(R"( { - // copy 32 bytes at once - start32: - jumpi(end32, lt(len, 32)) - mstore(dst, mload(src)) - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - jump(start32) - end32: - - // copy the remainder (0 < len < 32) - let mask := sub(exp(256, sub(32, len)), 1) - let srcpart := and(mload(src), not(mask)) - let dstpart := and(mload(dst), mask) - mstore(dst, or(srcpart, dstpart)) + // copy 32 bytes at once + for + {} + iszero(lt(len, 32)) + { + dst := add(dst, 32) + src := add(src, 32) + len := sub(len, 32) + } + { mstore(dst, mload(src)) } + + // copy the remainder (0 < len < 32) + let mask := sub(exp(256, sub(32, len)), 1) + let srcpart := and(mload(src), not(mask)) + let dstpart := and(mload(dst), mask) + mstore(dst, or(srcpart, dstpart)) } )", { "len", "dst", "src" } @@ -392,7 +387,13 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) Instruction::OR; } -void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded, bool _chopSignBits) +void CompilerUtils::convertType( + Type const& _typeOnStack, + Type const& _targetType, + bool _cleanupNeeded, + bool _chopSignBits, + bool _asPartOfArgumentDecoding +) { // For a type extension, we need to remove all higher-order bits that we might have ignored in // previous operations. @@ -450,7 +451,10 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalInvalid(); + if (_asPartOfArgumentDecoding) + m_context.appendConditionalRevert(); + else + m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; } break; @@ -985,7 +989,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda m_context << shiftFactor << Instruction::MUL; } if (_fromCalldata) - convertType(_type, _type, true); + convertType(_type, _type, true, false, true); return numBytes; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index a88951bc..0ee053a9 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -110,10 +110,12 @@ public: void zeroInitialiseMemoryArray(ArrayType const& _type); /// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length. + /// Length can be zero, in this case, it copies nothing. /// Stack pre: <size> <target> <source> /// Stack post: void memoryCopy32(); /// Copies data in memory (regions cannot overlap). + /// Length can be zero, in this case, it copies nothing. /// Stack pre: <size> <target> <source> /// Stack post: void memoryCopy(); @@ -135,7 +137,15 @@ public: /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be /// necessary. /// If @a _chopSignBits, the function resets the signed bits out of the width of the signed integer. - void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false, bool _chopSignBits = false); + /// If @a _asPartOfArgumentDecoding is true, failed conversions are flagged via REVERT, + /// otherwise they are flagged with INVALID. + void convertType( + Type const& _typeOnStack, + Type const& _targetType, + bool _cleanupNeeded = false, + bool _chopSignBits = false, + bool _asPartOfArgumentDecoding = false + ); /// Creates a zero-value for the given type and puts it onto the stack. This might allocate /// memory for memory references. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index dc090634..977a2c7c 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -111,7 +111,7 @@ void ContractCompiler::appendCallValueCheck() { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) @@ -276,7 +276,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary()); } else - m_context.appendInvalid(); + m_context.appendRevert(); for (auto const& it: interfaceFunctions) { @@ -368,7 +368,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter // copy to memory // move calldata type up again CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack()); - CompilerUtils(m_context).convertType(*calldataType, arrayType); + CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true); // fetch next pointer again CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack()); } @@ -805,8 +805,7 @@ bool ContractCompiler::visit(Throw const& _throw) { CompilerContext::LocationSetter locationSetter(m_context, _throw); // Do not send back an error detail. - m_context << u256(0) << u256(0); - m_context << Instruction::REVERT; + m_context.appendRevert(); return false; } @@ -879,6 +878,7 @@ void ContractCompiler::appendModifierOrFunctionCode() solAssert(m_currentFunction, ""); unsigned stackSurplus = 0; Block const* codeBlock = nullptr; + vector<VariableDeclaration const*> addedVariables; m_modifierDepth++; @@ -902,6 +902,7 @@ void ContractCompiler::appendModifierOrFunctionCode() for (unsigned i = 0; i < modifier.parameters().size(); ++i) { m_context.addVariable(*modifier.parameters()[i]); + addedVariables.push_back(modifier.parameters()[i].get()); compileExpression( *modifierInvocation->arguments()[i], modifier.parameters()[i]->annotation().type @@ -928,6 +929,8 @@ void ContractCompiler::appendModifierOrFunctionCode() m_returnTags.pop_back(); CompilerUtils(m_context).popStackSlots(stackSurplus); + for (auto var: addedVariables) + m_context.removeVariable(*var); } m_modifierDepth--; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 03bba80c..a65549fd 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -587,7 +587,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::CREATE; // Check if zero (out of stack or not enough balance). m_context << Instruction::DUP1 << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; break; @@ -651,7 +651,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { // Check if zero (out of stack or not enough balance). m_context << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } break; case FunctionType::Kind::Selfdestruct: @@ -660,9 +660,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::SELFDESTRUCT; break; case FunctionType::Kind::Revert: - // memory offset returned - zero length - m_context << u256(0) << u256(0); - m_context << Instruction::REVERT; + m_context.appendRevert(); break; case FunctionType::Kind::SHA3: { @@ -888,9 +886,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) auto success = m_context.appendConditionalJump(); if (function.kind() == FunctionType::Kind::Assert) // condition was not met, flag an error - m_context << Instruction::INVALID; + m_context.appendInvalid(); else - m_context << u256(0) << u256(0) << Instruction::REVERT; + m_context.appendRevert(); // the success branch m_context << success; break; @@ -1368,6 +1366,7 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) { + solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types."); if (_operator == Token::Equal || _operator == Token::NotEqual) { if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type)) @@ -1695,7 +1694,7 @@ void ExpressionCompiler::appendExternalFunctionCall( if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); existenceChecked = true; } @@ -1731,7 +1730,7 @@ void ExpressionCompiler::appendExternalFunctionCall( { //Propagate error condition (if CALL pushes 0 on stack). m_context << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } utils().popStackSlots(remainsSize); diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp deleted file mode 100644 index fecab2de..00000000 --- a/libsolidity/formal/Why3Translator.cpp +++ /dev/null @@ -1,898 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see <http://www.gnu.org/licenses/>. -*/ -/** - * @author Christian <c@ethdev.com> - * @date 2015 - * Component that translates Solidity code into the why3 programming language. - */ - -#include <libsolidity/formal/Why3Translator.h> -#include <boost/algorithm/string/predicate.hpp> - -using namespace std; -using namespace dev; -using namespace dev::solidity; - -bool Why3Translator::process(SourceUnit const& _source) -{ - try - { - if (m_lines.size() != 1 || !m_lines.back().contents.empty()) - fatalError(_source, string("Multiple source units not yet supported")); - appendPreface(); - _source.accept(*this); - } - catch (NoFormalType&) - { - solAssert(false, "There is a call to toFormalType() that does not catch NoFormalType exceptions."); - } - catch (FatalError& /*_e*/) - { - solAssert(m_errorOccured, ""); - } - return !m_errorOccured; -} - -string Why3Translator::translation() const -{ - string result; - for (auto const& line: m_lines) - result += string(line.indentation, '\t') + line.contents + "\n"; - return result; -} - -string Why3Translator::toFormalType(Type const& _type) const -{ - if (_type.category() == Type::Category::Bool) - return "bool"; - else if (auto type = dynamic_cast<IntegerType const*>(&_type)) - { - if (!type->isAddress() && !type->isSigned() && type->numBits() == 256) - return "uint256"; - } - else if (auto type = dynamic_cast<ArrayType const*>(&_type)) - { - if (!type->isByteArray() && type->isDynamicallySized() && type->dataStoredIn(DataLocation::Memory)) - { - // Not catching NoFormalType exception. Let the caller deal with it. - string base = toFormalType(*type->baseType()); - return "array " + base; - } - } - else if (auto mappingType = dynamic_cast<MappingType const*>(&_type)) - { - solAssert(mappingType->keyType(), "A mappingType misses a keyType."); - if (dynamic_cast<IntegerType const*>(&*mappingType->keyType())) - { - //@TODO Use the information from the key type and specify the length of the array as an invariant. - // Also the constructor need to specify the length of the array. - solAssert(mappingType->valueType(), "A mappingType misses a valueType."); - // Not catching NoFormalType exception. Let the caller deal with it. - string valueTypeFormal = toFormalType(*mappingType->valueType()); - return "array " + valueTypeFormal; - } - } - - BOOST_THROW_EXCEPTION(NoFormalType() - << errinfo_noFormalTypeFrom(_type.toString(true))); -} - -void Why3Translator::addLine(string const& _line) -{ - newLine(); - add(_line); - newLine(); -} - -void Why3Translator::add(string const& _str) -{ - m_lines.back().contents += _str; -} - -void Why3Translator::newLine() -{ - if (!m_lines.back().contents.empty()) - m_lines.push_back({"", m_lines.back().indentation}); -} - -void Why3Translator::unindent() -{ - newLine(); - solAssert(m_lines.back().indentation > 0, ""); - m_lines.back().indentation--; -} - -bool Why3Translator::visit(ContractDefinition const& _contract) -{ - if (m_seenContract) - error(_contract, "More than one contract not supported."); - m_seenContract = true; - m_currentContract.contract = &_contract; - if (_contract.isLibrary()) - error(_contract, "Libraries not supported."); - - addLine("module Contract_" + _contract.name()); - indent(); - addLine("use import int.Int"); - addLine("use import ref.Ref"); - addLine("use import map.Map"); - addLine("use import array.Array"); - addLine("use import int.ComputerDivision"); - addLine("use import mach.int.Unsigned"); - addLine("use import UInt256"); - addLine("exception Revert"); - addLine("exception Return"); - - if (_contract.stateVariables().empty()) - addLine("type state = ()"); - else - { - addLine("type state = {"); - indent(); - m_currentContract.stateVariables = _contract.stateVariables(); - for (VariableDeclaration const* variable: m_currentContract.stateVariables) - { - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - string typeName = typeNamePtr ? " \"" + *typeNamePtr + "\"" : ""; - fatalError(*variable, "Type" + typeName + " not supported for state variable."); - } - addLine("mutable _" + variable->name() + ": " + varType); - } - unindent(); - addLine("}"); - } - - addLine("type account = {"); - indent(); - addLine("mutable balance: uint256;"); - addLine("storage: state"); - unindent(); - addLine("}"); - - addLine("val external_call (this: account): bool"); - indent(); - addLine("ensures { result = false -> this = (old this) }"); - addLine("writes { this }"); - addSourceFromDocStrings(m_currentContract.contract->annotation()); - unindent(); - - if (!_contract.baseContracts().empty()) - error(*_contract.baseContracts().front(), "Inheritance not supported."); - if (!_contract.definedStructs().empty()) - error(*_contract.definedStructs().front(), "User-defined types not supported."); - if (!_contract.definedEnums().empty()) - error(*_contract.definedEnums().front(), "User-defined types not supported."); - if (!_contract.events().empty()) - error(*_contract.events().front(), "Events not supported."); - if (!_contract.functionModifiers().empty()) - error(*_contract.functionModifiers().front(), "Modifiers not supported."); - - ASTNode::listAccept(_contract.definedFunctions(), *this); - - return false; -} - -void Why3Translator::endVisit(ContractDefinition const&) -{ - m_currentContract.reset(); - unindent(); - addLine("end"); -} - -bool Why3Translator::visit(FunctionDefinition const& _function) -{ - if (!_function.isImplemented()) - { - error(_function, "Unimplemented functions not supported."); - return false; - } - if (_function.name().empty()) - { - error(_function, "Fallback functions not supported."); - return false; - } - if (!_function.modifiers().empty()) - { - error(_function, "Modifiers not supported."); - return false; - } - - m_localVariables.clear(); - for (auto const& var: _function.parameters()) - m_localVariables[var->name()] = var.get(); - for (auto const& var: _function.returnParameters()) - m_localVariables[var->name()] = var.get(); - for (auto const& var: _function.localVariables()) - m_localVariables[var->name()] = var; - - add("let rec _" + _function.name()); - add(" (this: account)"); - for (auto const& param: _function.parameters()) - { - string paramType; - try - { - paramType = toFormalType(*param->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*param, "Parameter type \"" + (typeName ? *typeName : "") + "\" not supported."); - } - if (param->name().empty()) - error(*param, "Anonymous function parameters not supported."); - add(" (arg_" + param->name() + ": " + paramType + ")"); - } - add(":"); - - indent(); - indent(); - string retString = "("; - for (auto const& retParam: _function.returnParameters()) - { - string paramType; - try - { - paramType = toFormalType(*retParam->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*retParam, "Parameter type " + (typeName ? *typeName : "") + " not supported."); - } - if (retString.size() != 1) - retString += ", "; - retString += paramType; - } - add(retString + ")"); - unindent(); - - addSourceFromDocStrings(_function.annotation()); - if (!m_currentContract.contract) - error(_function, "Only functions inside contracts allowed."); - addSourceFromDocStrings(m_currentContract.contract->annotation()); - - if (_function.isDeclaredConst()) - addLine("ensures { (old this) = this }"); - else - addLine("writes { this }"); - - addLine("="); - - // store the prestate in the case we need to revert - addLine("let prestate = {balance = this.balance; storage = " + copyOfStorage() + "} in "); - - // initialise local variables - for (auto const& variable: _function.parameters()) - addLine("let _" + variable->name() + " = ref arg_" + variable->name() + " in"); - for (auto const& variable: _function.returnParameters()) - { - if (variable->name().empty()) - error(*variable, "Unnamed return variables not yet supported."); - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in return parameter not yet supported."); - } - addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); - } - for (VariableDeclaration const* variable: _function.localVariables()) - { - if (variable->name().empty()) - error(*variable, "Unnamed variables not yet supported."); - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in variable declaration not yet supported."); - } - addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); - } - addLine("try"); - - _function.body().accept(*this); - add(";"); - addLine("raise Return"); - - string retVals; - for (auto const& variable: _function.returnParameters()) - { - if (!retVals.empty()) - retVals += ", "; - retVals += "!_" + variable->name(); - } - addLine("with Return -> (" + retVals + ") |"); - string reversion = " Revert -> this.balance <- prestate.balance; "; - for (auto const* variable: m_currentContract.stateVariables) - reversion += "this.storage._" + variable->name() + " <- prestate.storage._" + variable->name() + "; "; - //@TODO in case of reversion the return values are wrong - we need to change the - // return type to include a bool to signify if an exception was thrown. - reversion += "(" + retVals + ")"; - addLine(reversion); - unindent(); - addLine("end"); - addLine(""); - return false; -} - -void Why3Translator::endVisit(FunctionDefinition const&) -{ - m_localVariables.clear(); -} - -bool Why3Translator::visit(Block const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - add("begin"); - indent(); - for (size_t i = 0; i < _node.statements().size(); ++i) - { - _node.statements()[i]->accept(*this); - if (i != _node.statements().size() - 1) - { - auto it = m_lines.end() - 1; - while (it != m_lines.begin() && it->contents.empty()) - --it; - if (!boost::algorithm::ends_with(it->contents, "begin")) - it->contents += ";"; - } - newLine(); - } - unindent(); - add("end"); - return false; -} - -bool Why3Translator::visit(IfStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - add("if "); - _node.condition().accept(*this); - add(" then"); - visitIndentedUnlessBlock(_node.trueStatement()); - if (_node.falseStatement()) - { - newLine(); - add("else"); - visitIndentedUnlessBlock(*_node.falseStatement()); - } - return false; -} - -bool Why3Translator::visit(WhileStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - // Why3 does not appear to support do-while loops, - // so we will simulate them by performing a while - // loop with the body prepended once. - - if (_node.isDoWhile()) - { - visitIndentedUnlessBlock(_node.body()); - newLine(); - } - - add("while "); - _node.condition().accept(*this); - newLine(); - add("do"); - visitIndentedUnlessBlock(_node.body()); - add("done"); - return false; -} - -bool Why3Translator::visit(Return const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - if (_node.expression()) - { - solAssert(!!_node.annotation().functionReturnParameters, ""); - auto const& params = _node.annotation().functionReturnParameters->parameters(); - if (params.size() != 1) - { - error(_node, "Directly returning tuples not supported. Rather assign to return variable."); - return false; - } - add("begin _" + params.front()->name() + " := "); - _node.expression()->accept(*this); - add("; raise Return end"); - } - else - add("raise Return"); - return false; -} - -bool Why3Translator::visit(Throw const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - add("raise Revert"); - return false; -} - -bool Why3Translator::visit(VariableDeclarationStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - if (_node.declarations().size() != 1) - { - error(_node, "Multiple variables not supported."); - return false; - } - if (_node.initialValue()) - { - add("_" + _node.declarations().front()->name() + " := "); - _node.initialValue()->accept(*this); - } - return false; -} - -bool Why3Translator::visit(ExpressionStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - return true; -} - -bool Why3Translator::visit(Assignment const& _node) -{ - if (_node.assignmentOperator() != Token::Assign) - error(_node, "Compound assignment not supported."); - - _node.leftHandSide().accept(*this); - - add(m_currentLValueIsRef ? " := " : " <- "); - _node.rightHandSide().accept(*this); - - return false; -} - -bool Why3Translator::visit(TupleExpression const& _node) -{ - if (_node.components().size() != 1) - error(_node, "Only tuples with exactly one component supported."); - add("("); - return true; -} - -bool Why3Translator::visit(UnaryOperation const& _unaryOperation) -{ - try - { - toFormalType(*_unaryOperation.annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(_unaryOperation, "Type \"" + (typeNamePtr ? *typeNamePtr : "") + "\" supported in unary operation."); - } - - switch (_unaryOperation.getOperator()) - { - case Token::Not: // ! - add("(not "); - break; - default: - error(_unaryOperation, "Operator not supported."); - break; - } - - _unaryOperation.subExpression().accept(*this); - add(")"); - - return false; -} - -bool Why3Translator::visit(BinaryOperation const& _binaryOperation) -{ - Expression const& leftExpression = _binaryOperation.leftExpression(); - Expression const& rightExpression = _binaryOperation.rightExpression(); - solAssert(!!_binaryOperation.annotation().commonType, ""); - Type const& commonType = *_binaryOperation.annotation().commonType; - Token::Value const c_op = _binaryOperation.getOperator(); - - if (commonType.category() == Type::Category::RationalNumber) - { - auto const& constantNumber = dynamic_cast<RationalNumberType const&>(commonType); - if (constantNumber.isFractional()) - error(_binaryOperation, "Fractional numbers not supported."); - else - add("(of_int " + toString(commonType.literalValue(nullptr)) + ")"); - return false; - } - static const map<Token::Value, char const*> optrans({ - {Token::And, " && "}, - {Token::Or, " || "}, - {Token::BitOr, " lor "}, - {Token::BitXor, " lxor "}, - {Token::BitAnd, " land "}, - {Token::Add, " + "}, - {Token::Sub, " - "}, - {Token::Mul, " * "}, - {Token::Div, " / "}, - {Token::Mod, " mod "}, - {Token::Equal, " = "}, - {Token::NotEqual, " <> "}, - {Token::LessThan, " < "}, - {Token::GreaterThan, " > "}, - {Token::LessThanOrEqual, " <= "}, - {Token::GreaterThanOrEqual, " >= "} - }); - if (!optrans.count(c_op)) - { - error(_binaryOperation, "Operator not supported."); - return true; - } - - add("("); - leftExpression.accept(*this); - add(optrans.at(c_op)); - rightExpression.accept(*this); - add(")"); - - return false; -} - -bool Why3Translator::visit(FunctionCall const& _node) -{ - if (_node.annotation().kind == FunctionCallKind::TypeConversion || _node.annotation().kind == FunctionCallKind::StructConstructorCall) - { - error(_node, "Only ordinary function calls supported."); - return true; - } - FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type); - switch (function.kind()) - { - case FunctionType::Kind::AddMod: - case FunctionType::Kind::MulMod: - { - //@todo require that third parameter is not zero - add("(of_int (mod (Int.("); - add(function.kind() == FunctionType::Kind::AddMod ? "+" : "*"); - add(") (to_int "); - _node.arguments().at(0)->accept(*this); - add(") (to_int "); - _node.arguments().at(1)->accept(*this); - add(")) (to_int "); - _node.arguments().at(2)->accept(*this); - add(")))"); - return false; - } - case FunctionType::Kind::Internal: - { - if (!_node.names().empty()) - { - error(_node, "Function calls with named arguments not supported."); - return true; - } - - //@TODO check type conversions - - add("("); - _node.expression().accept(*this); - add(" state"); - for (auto const& arg: _node.arguments()) - { - add(" "); - arg->accept(*this); - } - add(")"); - return false; - } - case FunctionType::Kind::Bare: - { - if (!_node.arguments().empty()) - { - error(_node, "Function calls with named arguments not supported."); - return true; - } - - add("("); - indent(); - add("let amount = 0 in "); - _node.expression().accept(*this); - addLine("if amount <= this.balance then"); - indent(); - addLine("let balance_precall = this.balance in"); - addLine("begin"); - indent(); - addLine("this.balance <- this.balance - amount;"); - addLine("if not (external_call this) then begin this.balance = balance_precall; false end else true"); - unindent(); - addLine("end"); - unindent(); - addLine("else false"); - - unindent(); - add(")"); - return false; - } - case FunctionType::Kind::SetValue: - { - add("let amount = "); - solAssert(_node.arguments().size() == 1, ""); - _node.arguments()[0]->accept(*this); - add(" in "); - return false; - } - default: - error(_node, "Only internal function calls supported."); - return true; - } -} - -bool Why3Translator::visit(MemberAccess const& _node) -{ - if ( - _node.expression().annotation().type->category() == Type::Category::Array && - _node.memberName() == "length" && - !_node.annotation().lValueRequested - ) - { - add("(of_int "); - _node.expression().accept(*this); - add(".length"); - add(")"); - } - else if ( - _node.memberName() == "call" && - *_node.expression().annotation().type == IntegerType(160, IntegerType::Modifier::Address) - ) - { - // Do nothing, do not even visit the address because this will be an external call - //@TODO ensure that the expression itself does not have side-effects - return false; - } - else - error(_node, "Member access: Only call and array length supported."); - return false; -} - -bool Why3Translator::visit(IndexAccess const& _node) -{ - auto baseType = dynamic_cast<ArrayType const*>(_node.baseExpression().annotation().type.get()); - if (!baseType) - { - error(_node, "Index access only supported for arrays."); - return true; - } - if (_node.annotation().lValueRequested) - { - error(_node, "Assignment to array elements not supported."); - return true; - } - add("("); - _node.baseExpression().accept(*this); - add("[to_int "); - _node.indexExpression()->accept(*this); - add("]"); - add(")"); - - return false; -} - -bool Why3Translator::visit(Identifier const& _identifier) -{ - Declaration const* declaration = _identifier.annotation().referencedDeclaration; - if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) - add("_" + functionDef->name()); - else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) - { - bool isStateVar = isStateVariable(variable); - bool lvalue = _identifier.annotation().lValueRequested; - if (isStateVar) - add("this.storage."); - else if (!lvalue) - add("!("); - add("_" + variable->name()); - if (!isStateVar && !lvalue) - add(")"); - m_currentLValueIsRef = !isStateVar; - } - else - error(_identifier, "Not supported."); - return false; -} - -bool Why3Translator::visit(Literal const& _literal) -{ - TypePointer type = _literal.annotation().type; - switch (type->category()) - { - case Type::Category::Bool: - if (type->literalValue(&_literal) == 0) - add("false"); - else - add("true"); - break; - case Type::Category::RationalNumber: - { - auto const& constantNumber = dynamic_cast<RationalNumberType const&>(*type); - if (constantNumber.isFractional()) - error(_literal, "Fractional numbers not supported."); - else - add("(of_int " + toString(type->literalValue(&_literal)) + ")"); - break; - } - default: - error(_literal, "Not supported."); - } - return false; -} - -bool Why3Translator::visit(PragmaDirective const& _pragma) -{ - if (_pragma.tokens().empty()) - error(_pragma, "Not supported"); - else if (_pragma.literals().empty()) - error(_pragma, "Not supported"); - else if (_pragma.literals()[0] != "solidity") - error(_pragma, "Not supported"); - else if (_pragma.tokens()[0] != Token::Identifier) - error(_pragma, "A literal 'solidity' is not an identifier. Strange"); - - return false; -} - -bool Why3Translator::isStateVariable(VariableDeclaration const* _var) const -{ - return contains(m_currentContract.stateVariables, _var); -} - -bool Why3Translator::isStateVariable(string const& _name) const -{ - for (auto const& var: m_currentContract.stateVariables) - if (var->name() == _name) - return true; - return false; -} - -bool Why3Translator::isLocalVariable(VariableDeclaration const* _var) const -{ - for (auto const& var: m_localVariables) - if (var.second == _var) - return true; - return false; -} - -bool Why3Translator::isLocalVariable(string const& _name) const -{ - return m_localVariables.count(_name); -} - -string Why3Translator::copyOfStorage() const -{ - if (m_currentContract.stateVariables.empty()) - return "()"; - string ret = "{"; - bool first = true; - for (auto const* variable: m_currentContract.stateVariables) - { - if (first) - first = false; - else - ret += "; "; - ret += "_" + variable->name() + " = this.storage._" + variable->name(); - } - return ret + "}"; -} - -void Why3Translator::visitIndentedUnlessBlock(Statement const& _statement) -{ - bool isBlock = !!dynamic_cast<Block const*>(&_statement); - if (isBlock) - newLine(); - else - indent(); - _statement.accept(*this); - if (isBlock) - newLine(); - else - unindent(); -} - -void Why3Translator::addSourceFromDocStrings(DocumentedAnnotation const& _annotation) -{ - auto why3Range = _annotation.docTags.equal_range("why3"); - for (auto i = why3Range.first; i != why3Range.second; ++i) - addLine(transformVariableReferences(i->second.content)); -} - -string Why3Translator::transformVariableReferences(string const& _annotation) -{ - string ret; - auto pos = _annotation.begin(); - while (true) - { - auto hash = find(pos, _annotation.end(), '#'); - ret.append(pos, hash); - if (hash == _annotation.end()) - break; - - auto hashEnd = find_if(hash + 1, _annotation.end(), [](char _c) - { - return - (_c != '_' && _c != '$') && - !('a' <= _c && _c <= 'z') && - !('A' <= _c && _c <= 'Z') && - !('0' <= _c && _c <= '9'); - }); - string varName(hash + 1, hashEnd); - if (isLocalVariable(varName)) - ret += "(!_" + varName + ")"; - else if (isStateVariable(varName)) - ret += "(this.storage._" + varName + ")"; - //@todo return variables - else - ret.append(hash, hashEnd); - - pos = hashEnd; - } - return ret; -} - -void Why3Translator::appendPreface() -{ - m_lines.push_back(Line{R"( -module UInt256 - use import mach.int.Unsigned - type uint256 - constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - clone export mach.int.Unsigned with - type t = uint256, - constant max = max_uint256 -end - -module Address - use import mach.int.Unsigned - type address - constant max_address: int = 0xffffffffffffffffffffffffffffffffffffffff (* 160 bit = 40 f's *) - clone export mach.int.Unsigned with - type t = address, - constant max = max_address -end - )", 0}); -} - -void Why3Translator::error(ASTNode const& _source, std::string const& _description) -{ - m_errorOccured = true; - m_errorReporter.why3TranslatorError(_source, _description); -} -void Why3Translator::fatalError(ASTNode const& _source, std::string const& _description) -{ - m_errorOccured = true; - m_errorReporter.fatalWhy3TranslatorError(_source, _description); -} - diff --git a/libsolidity/formal/Why3Translator.h b/libsolidity/formal/Why3Translator.h deleted file mode 100644 index b48317be..00000000 --- a/libsolidity/formal/Why3Translator.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see <http://www.gnu.org/licenses/>. -*/ -/** - * @author Christian <c@ethdev.com> - * @date 2015 - * Component that translates Solidity code into the why3 programming language. - */ - -#pragma once - -#include <libsolidity/ast/ASTVisitor.h> -#include <libsolidity/interface/ErrorReporter.h> -#include <string> - -namespace dev -{ -namespace solidity -{ - -class SourceUnit; - -/** - * Simple translator from Solidity to Why3. - * - * @todo detect side effects in sub-expressions and limit them to one per statement. #1043 - * @todo `x = y = z` - * @todo implicit and explicit type conversion - */ -class Why3Translator: private ASTConstVisitor -{ -public: - Why3Translator(ErrorReporter& _errorReporter): m_lines(std::vector<Line>{{std::string(), 0}}), m_errorReporter(_errorReporter) {} - - /// Appends formalisation of the given source unit to the output. - /// @returns false on error. - bool process(SourceUnit const& _source); - - std::string translation() const; - -private: - /// Appends imports and constants use throughout the formal code. - void appendPreface(); - - /// @returns a string representation of the corresponding formal type or throws NoFormalType exception. - std::string toFormalType(Type const& _type) const; - using errinfo_noFormalTypeFrom = boost::error_info<struct tag_noFormalTypeFrom, std::string /* name of the type that cannot be translated */ >; - struct NoFormalType: virtual Exception {}; - - void error(ASTNode const& _source, std::string const& _description); - void fatalError(ASTNode const& _source, std::string const& _description); - - void indent() { newLine(); m_lines.back().indentation++; } - void unindent(); - void addLine(std::string const& _line); - void add(std::string const& _str); - void newLine(); - void appendSemicolon(); - - virtual bool visit(SourceUnit const&) override { return true; } - virtual bool visit(ContractDefinition const& _contract) override; - virtual void endVisit(ContractDefinition const& _contract) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual void endVisit(FunctionDefinition const& _function) override; - virtual bool visit(Block const&) override; - virtual bool visit(IfStatement const& _node) override; - virtual bool visit(WhileStatement const& _node) override; - virtual bool visit(Return const& _node) override; - virtual bool visit(Throw const& _node) override; - virtual bool visit(VariableDeclarationStatement const& _node) override; - virtual bool visit(ExpressionStatement const&) override; - virtual bool visit(Assignment const& _node) override; - virtual bool visit(TupleExpression const& _node) override; - virtual void endVisit(TupleExpression const&) override { add(")"); } - virtual bool visit(UnaryOperation const& _node) override; - virtual bool visit(BinaryOperation const& _node) override; - virtual bool visit(FunctionCall const& _node) override; - virtual bool visit(MemberAccess const& _node) override; - virtual bool visit(IndexAccess const& _node) override; - virtual bool visit(Identifier const& _node) override; - virtual bool visit(Literal const& _node) override; - virtual bool visit(PragmaDirective const& _node) override; - - virtual bool visitNode(ASTNode const& _node) override - { - m_errorReporter.why3TranslatorError(_node, "Code not supported for formal verification."); - return false; - } - - bool isStateVariable(VariableDeclaration const* _var) const; - bool isStateVariable(std::string const& _name) const; - bool isLocalVariable(VariableDeclaration const* _var) const; - bool isLocalVariable(std::string const& _name) const; - - /// @returns a string representing an expression that is a copy of this.storage - std::string copyOfStorage() const; - - /// Visits the given statement and indents it unless it is a block - /// (which does its own indentation). - void visitIndentedUnlessBlock(Statement const& _statement); - - void addSourceFromDocStrings(DocumentedAnnotation const& _annotation); - /// Transforms substring like `#varName` and `#stateVarName` to code that evaluates to their value. - std::string transformVariableReferences(std::string const& _annotation); - - /// True if we have already seen a contract. For now, only a single contract - /// is supported. - bool m_seenContract = false; - bool m_errorOccured = false; - - /// Metadata relating to the current contract - struct ContractMetadata - { - ContractDefinition const* contract = nullptr; - std::vector<VariableDeclaration const*> stateVariables; - - void reset() { contract = nullptr; stateVariables.clear(); } - }; - - ContractMetadata m_currentContract; - bool m_currentLValueIsRef = false; - std::map<std::string, VariableDeclaration const*> m_localVariables; - - struct Line - { - std::string contents; - unsigned indentation; - }; - std::vector<Line> m_lines; - ErrorReporter& m_errorReporter; -}; - -} -} diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 3c7c62c6..2bbd1b70 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -121,6 +121,11 @@ public: solAssert(false, "RETURNSUB not implemented for EVM 1.0"); } + virtual void appendAssemblySize() override + { + m_assembly.appendProgramSize(); + } + private: LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const { diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 8be2c8dd..e2507821 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -40,7 +40,6 @@ #include <libsolidity/interface/ABI.h> #include <libsolidity/interface/Natspec.h> #include <libsolidity/interface/GasEstimator.h> -#include <libsolidity/formal/Why3Translator.h> #include <libevmasm/Exceptions.h> @@ -316,20 +315,6 @@ void CompilerStack::link() } } -bool CompilerStack::prepareFormalAnalysis(ErrorReporter* _errorReporter) -{ - if (!_errorReporter) - _errorReporter = &m_errorReporter; - Why3Translator translator(*_errorReporter); - for (Source const* source: m_sourceOrder) - if (!translator.process(*source->ast)) - return false; - - m_formalTranslation = translator.translation(); - - return true; -} - eth::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const { Contract const& currentContract = contract(_contractName); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index f7435f0e..c51ae9c9 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -129,12 +129,6 @@ public: /// @returns false on error. bool compile(std::string const& _sourceCode, bool _optimize = false, unsigned _runs = 200); - /// Tries to translate all source files into a language suitable for formal analysis. - /// @param _errors list to store errors - defaults to the internal error list. - /// @returns false on error. - bool prepareFormalAnalysis(ErrorReporter* _errorReporter = nullptr); - std::string const& formalTranslation() const { return m_formalTranslation; } - /// @returns the assembled object for a contract. eth::LinkerObject const& object(std::string const& _contractName = "") const; /// @returns the runtime object for the contract. @@ -290,7 +284,6 @@ private: std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; std::vector<Source const*> m_sourceOrder; std::map<std::string const, Contract> m_contracts; - std::string m_formalTranslation; ErrorList m_errorList; ErrorReporter m_errorReporter; bool m_metadataLiteralSources = false; diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index 9987b82c..5657c2c0 100644 --- a/libsolidity/parsing/ParserBase.cpp +++ b/libsolidity/parsing/ParserBase.cpp @@ -43,6 +43,26 @@ int ParserBase::endPosition() const return m_scanner->currentLocation().end; } +Token::Value ParserBase::currentToken() const +{ + return m_scanner->currentToken(); +} + +Token::Value ParserBase::peekNextToken() const +{ + return m_scanner->peekNextToken(); +} + +std::string ParserBase::currentLiteral() const +{ + return m_scanner->currentLiteral(); +} + +Token::Value ParserBase::advance() +{ + return m_scanner->next(); +} + void ParserBase::expectToken(Token::Value _value) { Token::Value tok = m_scanner->currentToken(); diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h index ae56cead..5b03ab5e 100644 --- a/libsolidity/parsing/ParserBase.h +++ b/libsolidity/parsing/ParserBase.h @@ -23,7 +23,6 @@ #pragma once #include <memory> -#include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Token.h> namespace dev @@ -51,10 +50,10 @@ protected: ///@name Helper functions /// If current token value is not _value, throw exception otherwise advance token. void expectToken(Token::Value _value); - Token::Value currentToken() const { return m_scanner->currentToken(); } - Token::Value peekNextToken() const { return m_scanner->peekNextToken(); } - std::string currentLiteral() const { return m_scanner->currentLiteral(); } - Token::Value advance() { return m_scanner->next(); } + Token::Value currentToken() const; + Token::Value peekNextToken() const; + std::string currentLiteral() const; + Token::Value advance(); ///@} /// Creates a @ref ParserError and annotates it with the current position and the diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 0dbedd3c..d0134113 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -37,7 +37,6 @@ #include <libsolidity/interface/SourceReferenceFormatter.h> #include <libsolidity/interface/GasEstimator.h> #include <libsolidity/interface/AssemblyStack.h> -#include <libsolidity/formal/Why3Translator.h> #include <libevmasm/Instruction.h> #include <libevmasm/GasMeter.h> @@ -393,17 +392,6 @@ void CommandLineInterface::handleGasEstimation(string const& _contract) } } -void CommandLineInterface::handleFormal() -{ - if (!m_args.count(g_argFormal)) - return; - - if (m_args.count(g_argOutputDir)) - createFile("solidity.mlw", m_compiler->formalTranslation()); - else - cout << "Formal version:" << endl << m_compiler->formalTranslation() << endl; -} - void CommandLineInterface::readInputFilesAndConfigureRemappings() { bool addStdin = false; @@ -790,10 +778,6 @@ bool CommandLineInterface::processInput() unsigned runs = m_args[g_argOptimizeRuns].as<unsigned>(); bool successful = m_compiler->compile(optimize, runs, m_libraries); - if (successful && m_args.count(g_argFormal)) - if (!m_compiler->prepareFormalAnalysis()) - successful = false; - for (auto const& error: m_compiler->errors()) SourceReferenceFormatter::printExceptionInformation( cerr, @@ -1185,7 +1169,8 @@ void CommandLineInterface::outputCompilationResults() handleNatspec(DocumentationType::NatspecUser, contract); } // end of contracts iteration - handleFormal(); + if (m_args.count(g_argFormal)) + cerr << "Support for the Why3 output was removed." << endl; } } diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index 5165f984..1d31ea30 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -21,24 +21,9 @@ */ #include <string> -#include <functional> -#include <iostream> -#include <json/json.h> #include <libdevcore/Common.h> -#include <libdevcore/CommonData.h> -#include <libdevcore/CommonIO.h> #include <libdevcore/JSON.h> -#include <libevmasm/Instruction.h> -#include <libevmasm/GasMeter.h> -#include <libsolidity/parsing/Scanner.h> -#include <libsolidity/parsing/Parser.h> -#include <libsolidity/ast/ASTPrinter.h> -#include <libsolidity/analysis/NameAndTypeResolver.h> -#include <libsolidity/interface/Exceptions.h> -#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/StandardCompiler.h> -#include <libsolidity/interface/SourceReferenceFormatter.h> -#include <libsolidity/ast/ASTJsonConverter.h> #include <libsolidity/interface/Version.h> #include "license.h" @@ -109,9 +94,8 @@ Json::Value gasToJson(Json::Value const& _value) return Json::Value(Json::LargestUInt(value)); } -Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) +Json::Value translateGasEstimates(Json::Value const& estimates) { - Json::Value estimates = _compiler.gasEstimates(_contract); Json::Value output(Json::objectValue); if (estimates["creation"].isObject()) @@ -131,121 +115,106 @@ Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback) { - Json::Value output(Json::objectValue); - Json::Value errors(Json::arrayValue); - CompilerStack compiler(wrapReadCallback(_readCallback)); - auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return compiler.scanner(_sourceName); }; - bool success = false; - try - { - compiler.addSources(_sources); - bool succ = compiler.compile(_optimize); - for (auto const& error: compiler.errors()) - { - auto err = dynamic_pointer_cast<Error const>(error); - errors.append(SourceReferenceFormatter::formatExceptionInformation( - *error, - (err->type() == Error::Type::Warning) ? "Warning" : "Error", - scannerFromSourceName - )); - } - success = succ; // keep success false on exception - } - catch (Error const& error) - { - errors.append(SourceReferenceFormatter::formatExceptionInformation(error, error.typeName(), scannerFromSourceName)); - } - catch (CompilerError const& exception) - { - errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); - } - catch (InternalCompilerError const& exception) - { - errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Internal compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); - } - catch (UnimplementedFeatureError const& exception) - { - errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Unimplemented feature (" + exception.lineInfo() + ")", scannerFromSourceName)); - } - catch (Exception const& exception) + /// create new JSON input format + Json::Value input = Json::objectValue; + input["language"] = "Solidity"; + input["sources"] = Json::objectValue; + for (auto const& source: _sources) { - errors.append("Exception during compilation: " + boost::diagnostic_information(exception)); + input["sources"][source.first] = Json::objectValue; + input["sources"][source.first]["content"] = source.second; } - catch (...) + input["settings"] = Json::objectValue; + input["settings"]["optimizer"] = Json::objectValue; + input["settings"]["optimizer"]["enabled"] = _optimize; + input["settings"]["optimizer"]["runs"] = 200; + + StandardCompiler compiler(wrapReadCallback(_readCallback)); + Json::Value ret = compiler.compile(input); + + /// transform JSON to match the old format + // { + // "errors": [ "Error 1", "Error 2" ], + // "sourceList": [ "sourcename1", "sourcename2" ], + // "sources": { + // "sourcename1": { + // "AST": {} + // } + // }, + // "contracts": { + // "Contract1": { + // "interface": "[...abi...]", + // "bytecode": "ff0011...", + // "runtimeBytecode": "ff0011", + // "opcodes": "PUSH 1 POP STOP", + // "metadata": "{...metadata...}", + // "functionHashes": { + // "test(uint256)": "11ff2233" + // }, + // "gasEstimates": { + // "creation": [ 224, 42000 ], + // "external": { + // "11ff2233": null, + // "3322ff11": 1234 + // }, + // "internal": { + // } + // }, + // "srcmap" = "0:1:2", + // "srcmapRuntime" = "0:1:2", + // "assembly" = {} + // } + // }, + // "formal": { + // "errors": [ "Error 1" ], + // "why3": "why3 source" + // } + // } + Json::Value output = Json::objectValue; + + if (ret.isMember("errors")) { - errors.append("Unknown exception during compilation."); + output["errors"] = Json::arrayValue; + for (auto const& error: ret["errors"]) + output["errors"].append( + !error["formattedMessage"].empty() ? error["formattedMessage"] : error["message"] + ); } - if (errors.size() > 0) - output["errors"] = errors; + output["sourceList"] = Json::arrayValue; + for (auto const& source: _sources) + output["sourceList"].append(source.first); - if (success) + if (ret.isMember("sources")) { - try + output["sources"] = Json::objectValue; + for (auto const& sourceName: ret["sources"].getMemberNames()) { - output["contracts"] = Json::Value(Json::objectValue); - for (string const& contractName: compiler.contractNames()) - { - Json::Value contractData(Json::objectValue); - contractData["interface"] = dev::jsonCompactPrint(compiler.contractABI(contractName)); - contractData["bytecode"] = compiler.object(contractName).toHex(); - contractData["runtimeBytecode"] = compiler.runtimeObject(contractName).toHex(); - contractData["opcodes"] = solidity::disassemble(compiler.object(contractName).bytecode); - contractData["metadata"] = compiler.onChainMetadata(contractName); - contractData["functionHashes"] = compiler.methodIdentifiers(contractName); - contractData["gasEstimates"] = estimateGas(compiler, contractName); - auto sourceMap = compiler.sourceMapping(contractName); - contractData["srcmap"] = sourceMap ? *sourceMap : ""; - auto runtimeSourceMap = compiler.runtimeSourceMapping(contractName); - contractData["srcmapRuntime"] = runtimeSourceMap ? *runtimeSourceMap : ""; - ostringstream unused; - contractData["assembly"] = compiler.streamAssembly(unused, contractName, _sources, true); - output["contracts"][contractName] = contractData; - } - } - catch (...) - { - output["errors"].append("Unknown exception while generating contract data output."); + output["sources"][sourceName] = Json::objectValue; + output["sources"][sourceName]["AST"] = ret["sources"][sourceName]["legacyAST"]; } + } - try - { - // Do not taint the internal error list - ErrorList formalErrors; - ErrorReporter errorReporter(formalErrors); - if (compiler.prepareFormalAnalysis(&errorReporter)) - output["formal"]["why3"] = compiler.formalTranslation(); - if (!errorReporter.errors().empty()) + if (ret.isMember("contracts")) + { + output["contracts"] = Json::objectValue; + for (auto const& sourceName: ret["contracts"].getMemberNames()) + for (auto const& contractName: ret["contracts"][sourceName].getMemberNames()) { - Json::Value errors(Json::arrayValue); - for (auto const& error: errorReporter.errors()) - errors.append(SourceReferenceFormatter::formatExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error", - scannerFromSourceName - )); - output["formal"]["errors"] = errors; + Json::Value contractInput = ret["contracts"][sourceName][contractName]; + Json::Value contractOutput = Json::objectValue; + contractOutput["interface"] = dev::jsonCompactPrint(contractInput["abi"]); + contractOutput["metadata"] = contractInput["metadata"]; + contractOutput["functionHashes"] = contractInput["evm"]["methodIdentifiers"]; + contractOutput["gasEstimates"] = translateGasEstimates(contractInput["evm"]["gasEstimates"]); + contractOutput["assembly"] = contractInput["evm"]["legacyAssembly"]; + contractOutput["bytecode"] = contractInput["evm"]["bytecode"]["object"]; + contractOutput["opcodes"] = contractInput["evm"]["bytecode"]["opcodes"]; + contractOutput["srcmap"] = contractInput["evm"]["bytecode"]["sourceMap"]; + contractOutput["runtimeBytecode"] = contractInput["evm"]["deployedBytecode"]["object"]; + contractOutput["srcmapRuntime"] = contractInput["evm"]["deployedBytecode"]["sourceMap"]; + output["contracts"][sourceName + ":" + contractName] = contractOutput; } - } - catch (...) - { - output["errors"].append("Unknown exception while generating formal method output."); - } - - try - { - // Indices into this array are used to abbreviate source names in source locations. - output["sourceList"] = Json::Value(Json::arrayValue); - for (auto const& source: compiler.sourceNames()) - output["sourceList"].append(source); - output["sources"] = Json::Value(Json::objectValue); - for (auto const& source: compiler.sourceNames()) - output["sources"][source]["AST"] = ASTJsonConverter(true, compiler.sourceIndices()).toJson(compiler.ast(source)); - } - catch (...) - { - output["errors"].append("Unknown exception while generating source name output."); - } } try diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index 053d880f..cf99755f 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -152,7 +152,6 @@ void testCompiler() "Exception during compilation", "Unknown exception during compilation", "Unknown exception while generating contract data output", - "Unknown exception while generating formal method output", "Unknown exception while generating source name output", "Unknown error while generating JSON" }); diff --git a/test/libdevcore/MiniMoustache.cpp b/test/libdevcore/MiniMoustache.cpp new file mode 100644 index 00000000..84149173 --- /dev/null +++ b/test/libdevcore/MiniMoustache.cpp @@ -0,0 +1,127 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for the mini moustache class. + */ + +#include <libdevcore/Whiskers.h> + +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(WhiskersTest) + +BOOST_AUTO_TEST_CASE(no_templates) +{ + string templ = "this text does not contain templates"; + BOOST_CHECK_EQUAL(Whiskers(templ).render(), templ); +} + +BOOST_AUTO_TEST_CASE(basic_replacement) +{ + string templ = "a <b> x <c> -> <d>."; + string result = Whiskers(templ) + ("b", "BE") + ("c", "CE") + ("d", "DE") + .render(); + BOOST_CHECK_EQUAL(result, "a BE x CE -> DE."); +} + +BOOST_AUTO_TEST_CASE(tag_unavailable) +{ + string templ = "<b>"; + Whiskers m(templ); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(complicated_replacement) +{ + string templ = "a <b> x <complicated> \n <nes<ted>>."; + string result = Whiskers(templ) + ("b", "BE") + ("complicated", "CO<M>PL") + ("nes<ted", "NEST") + .render(); + BOOST_CHECK_EQUAL(result, "a BE x CO<M>PL \n NEST>."); +} + +BOOST_AUTO_TEST_CASE(non_existing_list) +{ + string templ = "a <#b></b>"; + Whiskers m(templ); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(empty_list) +{ + string templ = "a <#b></b>x"; + string result = Whiskers(templ)("b", vector<Whiskers::StringMap>{}).render(); + BOOST_CHECK_EQUAL(result, "a x"); +} + +BOOST_AUTO_TEST_CASE(list) +{ + string templ = "a<#b>( <g> - <h> )</b>x"; + vector<map<string, string>> list(2); + list[0]["g"] = "GE"; + list[0]["h"] = "H"; + list[1]["g"] = "2GE"; + list[1]["h"] = "2H"; + string result = Whiskers(templ)("b", list).render(); + BOOST_CHECK_EQUAL(result, "a( GE - H )( 2GE - 2H )x"); +} + +BOOST_AUTO_TEST_CASE(recursive_list) +{ + // Check that templates resulting from lists are not expanded again + string templ = "a<#b> 1<g>3 </b><x>"; + vector<map<string, string>> list(1); + list[0]["g"] = "<x>"; + string result = Whiskers(templ)("x", "X")("b", list).render(); + BOOST_CHECK_EQUAL(result, "a 1<x>3 X"); +} + +BOOST_AUTO_TEST_CASE(list_can_access_upper) +{ + string templ = "<#b>(<a>)</b>"; + vector<map<string, string>> list(2); + Whiskers m(templ); + string result = m("a", "A")("b", list).render(); + BOOST_CHECK_EQUAL(result, "(A)(A)"); +} + +BOOST_AUTO_TEST_CASE(parameter_collision) +{ + string templ = "a <#b></b>"; + vector<map<string, string>> list(1); + list[0]["a"] = "x"; + Whiskers m(templ); + m("a", "X")("b", list); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/libdevcore/UTF8.cpp b/test/libdevcore/UTF8.cpp new file mode 100644 index 00000000..719ada72 --- /dev/null +++ b/test/libdevcore/UTF8.cpp @@ -0,0 +1,216 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for UTF-8 validation. + */ + +#include <libdevcore/CommonData.h> +#include <libdevcore/UTF8.h> + +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(UTF8) + +namespace { + +bool isValidUTF8(string const& _value) +{ + size_t pos; + return validateUTF8(asString(fromHex(_value)), pos); +} + +bool isInvalidUTF8(string const& _value, size_t _expectedPos) +{ + size_t pos; + if (validateUTF8(asString(fromHex(_value)), pos)) + return false; + if (pos != _expectedPos) + return false; + return true; +} + +} + +BOOST_AUTO_TEST_CASE(valid) +{ + BOOST_CHECK(isValidUTF8("00")); + BOOST_CHECK(isValidUTF8("20")); + BOOST_CHECK(isValidUTF8("7f")); + BOOST_CHECK(isValidUTF8("c281")); + BOOST_CHECK(isValidUTF8("df81")); + BOOST_CHECK(isValidUTF8("e0a081")); + BOOST_CHECK(isValidUTF8("e18081")); + BOOST_CHECK(isValidUTF8("ec8081")); + BOOST_CHECK(isValidUTF8("ed8081")); + BOOST_CHECK(isValidUTF8("ee8081")); + BOOST_CHECK(isValidUTF8("ef8081")); + BOOST_CHECK(isValidUTF8("f0908081")); + BOOST_CHECK(isValidUTF8("f3808081")); + BOOST_CHECK(isValidUTF8("f2808081")); + BOOST_CHECK(isValidUTF8("f3808081")); + BOOST_CHECK(isValidUTF8("f48e8081")); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + // anything between 0x80 and 0xc0 is disallowed + BOOST_CHECK(isInvalidUTF8("80", 0)); // invalid per table 3.6 + BOOST_CHECK(isInvalidUTF8("a0", 0)); // invalid per table 3.6 + BOOST_CHECK(isInvalidUTF8("c0", 0)); // invalid per table 3.7 + BOOST_CHECK(isInvalidUTF8("c1", 0)); // invalid per table 3.7 + BOOST_CHECK(isInvalidUTF8("c2", 0)); // too short (position is reported as the first byte) + BOOST_CHECK(isInvalidUTF8("e08081", 2)); // e0 must be followed by >= a0 + BOOST_CHECK(isInvalidUTF8("e180", 0)); // too short + BOOST_CHECK(isInvalidUTF8("ec80", 0)); // too short + BOOST_CHECK(isInvalidUTF8("f08f8001", 2)); // f0 must be followed by >= 90 + BOOST_CHECK(isInvalidUTF8("f18080", 0)); // too short + BOOST_CHECK(isInvalidUTF8("f4908081", 2)); // f4 must be followed by < 90 + // anything above 0xf7 is disallowed + BOOST_CHECK(isInvalidUTF8("f8", 0)); // invalid per table 3.7 + BOOST_CHECK(isInvalidUTF8("f9", 0)); // invalid per table 3.7 +} + +BOOST_AUTO_TEST_CASE(corpus) +{ + string source = R"( +κόσμε + +hélló + +Ā ā Ă ă Ą ą + +ƀ Ɓ Ƃ ƃ Ƅ ƅ + +ɐ ɑ ɒ ɓ ɔ ɕ + +ʰ ʱ ʲ ʳ ʴ ʵ + +̀ ́ ̂ ̃ ̄ ̅ + +ϩ Ϫ ϫ Ϭ ϭ Ϯ + +Ё Ђ Ѓ Є Ѕ І + +Ա Բ Գ Դ Ե Զ + + ק ר ש ת װ ױ + +ځ ڂ ڃ ڄ څ چ + +ऑ ऒ ओ औ क ख + +ও ঔ ক খ গ ঘ + +ਘ ਙ ਚ ਛ ਜ ਝ + +ઓ ઔ ક ખ ગ ઘ + +ଗ ଘ ଙ ଚ ଛ ଜ + +ஔ க ங ச ஜ ஞ + +ఎ ఏ ఐ ఒ ఓ ఔ + +ಓ ಔ ಕ ಖ ಗ ಘ + +ഐ ഒ ഓ ഔ ക + +ฒ ณ ด ต ถ ท + +ມ ຢ ຣ ລ ວ ສ + +༄ ༅ ༆ ༇ ༈ ༉ + +Ⴑ Ⴒ Ⴓ Ⴔ Ⴕ Ⴖ + +ᄌ ᄍ ᄎ ᄏ ᄐ + +Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ + +ἐ ἑ ἒ ἓ ἔ ἕ + +₠ ₡ ₢ ₣ ₤ ₥ + +⃐ ⃑ ⃒ ⃓ ⃔ ⃕ ⃖ ⃗ ⃘ ⃙ ⃚ + +ℋ ℌ ℍ ℎ ℏ ℐ ℑ + +⅓ ⅔ ⅕ ⅖ ⅗ + +∬ ∭ ∮ ∯ ∰ + +⌖ ⌗ ⌘ ⌙ ⌚ ⌛ + +␀ ␁ ␂ ␃ ␄ ␅ + +⑀ ⑁ ⑂ ⑃ ⑄ + +① ② ③ ④ ⑤ + +╘ ╙ ╚ ╛ ╜ ╝ + +▁ ▂ ▃ ▄ ▅ ▆ + +▤ ▥ ▦ ▧ ▨ + +♔ ♕ ♖ ♗ ♘ ♙ + +✈ ✉ ✌ ✍ ✎ + +ぁ あ ぃ い ぅ + +ァ ア ィ イ ゥ + +ㄅ ㄆ ㄇ ㄈ ㄉ + +ㄱ ㄲ ㄳ ㄴ ㄵ + +㆚ ㆛ ㆜ ㆝ ㆞ + +㈀ ㈁ ㈂ ㈃ ㈄ + +㌀ ㌁ ㌂ ㌃ ㌄ + +乺 乻 乼 乽 乾 + +걺 걻 걼 걽 걾 + +豈 更 車 賈 滑 + +שּׁ שּׂ אַ אָ אּ + +ﮄ ﮅ ﮆ ﮇ ﮈ ﮉ + + ﺵ ﺶ ﺷ ﺸ + +「 」 、 ・ ヲ ァ ィ ゥ + )"; + size_t pos; + BOOST_CHECK(validateUTF8(source, pos)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp index 02d024a4..3037b14b 100644 --- a/test/liblll/EndToEndTest.cpp +++ b/test/liblll/EndToEndTest.cpp @@ -57,6 +57,26 @@ BOOST_AUTO_TEST_CASE(panic) BOOST_REQUIRE(m_output.empty()); } +BOOST_AUTO_TEST_CASE(variables) +{ + char const* sourceCode = R"( + (returnlll + (seq + (set 'x 1) + (set 'y 2) + ;; this should equal to 3 + (set 'z (add (get 'x) (get 'y))) + ;; overwriting it here + (set 'y 4) + ;; each variable has a 32 byte slot, starting from memory location 0x80 + ;; variable addresses can also be retrieved by x or (ref 'x) + (set 'k (add (add (ref 'x) (ref 'y)) z)) + (return (add (add (get 'x) (add (get 'y) (get 'z))) (get 'k))))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs(u256(488))); +} + BOOST_AUTO_TEST_CASE(when) { char const* sourceCode = R"( diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index e52f4d50..99a2996e 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -119,8 +119,8 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr<string const> n = make_shared<string>(""); AssemblyItems items = compileContract(sourceCode); vector<SourceLocation> locations = - vector<SourceLocation>(17, SourceLocation(2, 75, n)) + - vector<SourceLocation>(30, SourceLocation(20, 72, n)) + + vector<SourceLocation>(19, SourceLocation(2, 75, n)) + + vector<SourceLocation>(32, SourceLocation(20, 72, n)) + vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector<SourceLocation>(2, SourceLocation(58, 67, n)) + vector<SourceLocation>(3, SourceLocation(20, 72, n)); diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp index f5154395..aa690f0b 100644 --- a/test/libsolidity/JSONCompiler.cpp +++ b/test/libsolidity/JSONCompiler.cpp @@ -73,28 +73,36 @@ BOOST_AUTO_TEST_CASE(basic_compilation) Json::Value contract = result["contracts"]["fileA:A"]; BOOST_CHECK(contract.isObject()); BOOST_CHECK(contract["interface"].isString()); - BOOST_CHECK(contract["interface"].asString() == "[]"); + BOOST_CHECK_EQUAL(contract["interface"].asString(), "[]"); BOOST_CHECK(contract["bytecode"].isString()); - BOOST_CHECK(dev::test::bytecodeSansMetadata(contract["bytecode"].asString()) == - "60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00"); + BOOST_CHECK_EQUAL( + dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), + "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00" + ); BOOST_CHECK(contract["runtimeBytecode"].isString()); - BOOST_CHECK(dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()) == - "60606040525bfe00"); + BOOST_CHECK_EQUAL( + dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()), + "60606040525b600080fd00" + ); BOOST_CHECK(contract["functionHashes"].isObject()); BOOST_CHECK(contract["gasEstimates"].isObject()); - BOOST_CHECK(dev::jsonCompactPrint(contract["gasEstimates"]) == - "{\"creation\":[62,10200],\"external\":{},\"internal\":{}}"); + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(contract["gasEstimates"]), + "{\"creation\":[62,10800],\"external\":{},\"internal\":{}}" + ); BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); BOOST_CHECK(result["sources"].isObject()); BOOST_CHECK(result["sources"]["fileA"].isObject()); BOOST_CHECK(result["sources"]["fileA"]["AST"].isObject()); - BOOST_CHECK(dev::jsonCompactPrint(result["sources"]["fileA"]["AST"]) == + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(result["sources"]["fileA"]["AST"]), "{\"attributes\":{\"absolutePath\":\"fileA\",\"exportedSymbols\":{\"A\":[1]}}," "\"children\":[{\"attributes\":{\"baseContracts\":[null],\"contractDependencies\":[null]," "\"contractKind\":\"contract\",\"documentation\":null,\"fullyImplemented\":true,\"linearizedBaseContracts\":[1]," "\"name\":\"A\",\"nodes\":[null],\"scope\":2},\"id\":1,\"name\":\"ContractDefinition\"," - "\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}"); + "\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}" + ); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index ba507e0c..ffee5e36 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4483,6 +4483,38 @@ BOOST_AUTO_TEST_CASE(array_copy_including_mapping) BOOST_CHECK(storageEmpty(m_contractAddress)); } +BOOST_AUTO_TEST_CASE(swap_in_storage_overwrite) +{ + // This tests a swap in storage which does not work as one + // might expect because we do not have temporary storage. + // (x, y) = (y, x) is the same as + // y = x; + // x = y; + char const* sourceCode = R"( + contract c { + struct S { uint a; uint b; } + S public x; + S public y; + function set() { + x.a = 1; x.b = 2; + y.a = 3; y.b = 4; + } + function swap() { + (x, y) = (y, x); + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0), u256(0))); + BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(0), u256(0))); + BOOST_CHECK(callContractFunction("set()") == encodeArgs()); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1), u256(2))); + BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(3), u256(4))); + BOOST_CHECK(callContractFunction("swap()") == encodeArgs()); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1), u256(2))); + BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(1), u256(2))); +} + BOOST_AUTO_TEST_CASE(pass_dynamic_arguments_to_the_base) { char const* sourceCode = R"( @@ -9469,6 +9501,29 @@ BOOST_AUTO_TEST_CASE(revert) BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(42))); } +BOOST_AUTO_TEST_CASE(literal_empty_string) +{ + char const* sourceCode = R"( + contract C { + bytes32 public x; + uint public a; + function f(bytes32 _x, uint _a) { + x = _x; + a = _a; + } + function g() { + this.f("", 2); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("g()") == encodeArgs()); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(2))); +} + BOOST_AUTO_TEST_CASE(scientific_notation) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index a1428972..c8a04539 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -554,6 +554,51 @@ BOOST_AUTO_TEST_CASE(comparison_bitop_precedence) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(comparison_of_function_types) +{ + char const* text = R"( + contract C { + function f() returns (bool ret) { + return this.f < this.f; + } + } + )"; + CHECK_ERROR(text, TypeError, "Operator < not compatible"); + text = R"( + contract C { + function f() returns (bool ret) { + return f < f; + } + } + )"; + CHECK_ERROR(text, TypeError, "Operator < not compatible"); + text = R"( + contract C { + function f() returns (bool ret) { + return f == f; + } + function g() returns (bool ret) { + return f != f; + } + } + )"; + CHECK_SUCCESS(text); +} + +BOOST_AUTO_TEST_CASE(comparison_of_mapping_types) +{ + char const* text = R"( + contract C { + mapping(uint => uint) x; + function f() returns (bool ret) { + var y = x; + return x == y; + } + } + )"; + CHECK_ERROR(text, TypeError, "Operator == not compatible"); +} + BOOST_AUTO_TEST_CASE(function_no_implementation) { ASTPointer<SourceUnit> sourceUnit; @@ -1049,6 +1094,28 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(function_modifier_double_invocation) +{ + char const* text = R"( + contract B { + function f(uint x) mod(x) mod(2) { } + modifier mod(uint a) { if (a > 0) _; } + } + )"; + CHECK_ERROR(text, DeclarationError, "Modifier already used for this function"); +} + +BOOST_AUTO_TEST_CASE(base_constructor_double_invocation) +{ + char const* text = R"( + contract C { function C(uint a) {} } + contract B is C { + function B() C(2) C(2) {} + } + )"; + CHECK_ERROR(text, DeclarationError, "Base constructor already provided"); +} + BOOST_AUTO_TEST_CASE(legal_modifier_override) { char const* text = R"( @@ -2183,6 +2250,36 @@ BOOST_AUTO_TEST_CASE(test_byte_is_alias_of_byte1) ETH_TEST_REQUIRE_NO_THROW(parseAndAnalyse(text), "Type resolving failed"); } +BOOST_AUTO_TEST_CASE(warns_assigning_decimal_to_bytesxx) +{ + char const* text = R"( + contract Foo { + bytes32 a = 7; + } + )"; + CHECK_WARNING(text, "Decimal literal assigned to bytesXX variable will be left-aligned."); +} + +BOOST_AUTO_TEST_CASE(does_not_warn_assigning_hex_number_to_bytesxx) +{ + char const* text = R"( + contract Foo { + bytes32 a = 0x1234; + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(explicit_conversion_from_decimal_to_bytesxx) +{ + char const* text = R"( + contract Foo { + bytes32 a = bytes32(7); + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(assigning_value_to_const_variable) { char const* text = R"( @@ -2284,6 +2381,16 @@ BOOST_AUTO_TEST_CASE(constant_struct) CHECK_ERROR(text, TypeError, "implemented"); } +BOOST_AUTO_TEST_CASE(address_is_constant) +{ + char const* text = R"( + contract C { + address constant x = 0x1212121212121212121212121212121212121212; + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(uninitialized_const_variable) { char const* text = R"( @@ -3708,12 +3815,12 @@ BOOST_AUTO_TEST_CASE(conditional_with_all_types) byte[2] memory a; byte[2] memory b; var k = true ? a : b; - k[0] = 0; //Avoid unused var warning + k[0] = byte(0); //Avoid unused var warning bytes memory e; bytes memory f; var l = true ? e : f; - l[0] = 0; // Avoid unused var warning + l[0] = byte(0); // Avoid unused var warning // fixed bytes bytes2 c; @@ -5445,6 +5552,25 @@ BOOST_AUTO_TEST_CASE(invalid_address_length) CHECK_WARNING(text, "checksum"); } +BOOST_AUTO_TEST_CASE(address_test_for_bug_in_implementation) +{ + // A previous implementation claimed the string would be an address + char const* text = R"( + contract AddrString { + address public test = "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"; + } + )"; + CHECK_ERROR(text, TypeError, "is not implicitly convertible to expected type address"); + text = R"( + contract AddrString { + function f() returns (address) { + return "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"; + } + } + )"; + CHECK_ERROR(text, TypeError, "is not implicitly convertible to expected type"); +} + BOOST_AUTO_TEST_CASE(early_exit_on_fatal_errors) { // This tests a crash that occured because we did not stop for fatal errors. @@ -5710,6 +5836,80 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop) success(text); } +BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies) +{ + char const* text = R"( + contract C { + struct S { uint a; uint b; } + S x; S y; + function f() { + (x, y) = (y, x); + } + } + )"; + CHECK_WARNING(text, "This assignment performs two copies to storage."); +} + +BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_right) +{ + char const* text = R"( + contract C { + struct S { uint a; uint b; } + S x; S y; + function f() { + (x, y, ) = (y, x, 1, 2); + } + } + )"; + CHECK_WARNING(text, "This assignment performs two copies to storage."); +} + +BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_left) +{ + char const* text = R"( + contract C { + struct S { uint a; uint b; } + S x; S y; + function f() { + (,x, y) = (1, 2, y, x); + } + } + )"; + CHECK_WARNING(text, "This assignment performs two copies to storage."); +} + +BOOST_AUTO_TEST_CASE(nowarn_swap_memory) +{ + char const* text = R"( + contract C { + struct S { uint a; uint b; } + function f() { + S memory x; + S memory y; + (x, y) = (y, x); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(nowarn_swap_storage_pointers) +{ + char const* text = R"( + contract C { + struct S { uint a; uint b; } + S x; S y; + function f() { + S storage x_local = x; + S storage y_local = y; + S storage z_local = x; + (x, y_local, x_local, z_local) = (y, x_local, y_local, y); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(warn_unused_local) { char const* text = R"( diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 35644a4d..be13d46b 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -189,37 +189,43 @@ BOOST_AUTO_TEST_CASE(basic_compilation) Json::Value contract = getContractResult(result, "fileA", "A"); BOOST_CHECK(contract.isObject()); BOOST_CHECK(contract["abi"].isArray()); - BOOST_CHECK(dev::jsonCompactPrint(contract["abi"]) == "[]"); + BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["abi"]), "[]"); BOOST_CHECK(contract["devdoc"].isObject()); - BOOST_CHECK(dev::jsonCompactPrint(contract["devdoc"]) == "{\"methods\":{}}"); + BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["devdoc"]), "{\"methods\":{}}"); BOOST_CHECK(contract["userdoc"].isObject()); - BOOST_CHECK(dev::jsonCompactPrint(contract["userdoc"]) == "{\"methods\":{}}"); + BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["userdoc"]), "{\"methods\":{}}"); BOOST_CHECK(contract["evm"].isObject()); /// @TODO check evm.methodIdentifiers, legacyAssembly, bytecode, deployedBytecode BOOST_CHECK(contract["evm"]["bytecode"].isObject()); BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString()); - BOOST_CHECK(dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()) == - "60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00"); + BOOST_CHECK_EQUAL( + dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()), + "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00" + ); BOOST_CHECK(contract["evm"]["assembly"].isString()); BOOST_CHECK(contract["evm"]["assembly"].asString().find( " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x60)\n jumpi(tag_1, iszero(callvalue))\n" - " invalid\ntag_1:\ntag_2:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n" + " 0x0\n dup1\n revert\ntag_1:\ntag_2:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n" " return\nstop\n\nsub_0: assembly {\n /* \"fileA\":0:14 contract A { } */\n" - " mstore(0x40, 0x60)\n tag_1:\n invalid\n\n" - " auxdata: 0xa165627a7a72305820") != std::string::npos); + " mstore(0x40, 0x60)\n tag_1:\n 0x0\n dup1\n revert\n\n" + " auxdata: 0xa165627a7a7230582") == 0); BOOST_CHECK(contract["evm"]["gasEstimates"].isObject()); - BOOST_CHECK(dev::jsonCompactPrint(contract["evm"]["gasEstimates"]) == - "{\"creation\":{\"codeDepositCost\":\"10200\",\"executionCost\":\"62\",\"totalCost\":\"10262\"}}"); + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(contract["evm"]["gasEstimates"]), + "{\"creation\":{\"codeDepositCost\":\"10800\",\"executionCost\":\"62\",\"totalCost\":\"10862\"}}" + ); BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); BOOST_CHECK(result["sources"].isObject()); BOOST_CHECK(result["sources"]["fileA"].isObject()); BOOST_CHECK(result["sources"]["fileA"]["legacyAST"].isObject()); - BOOST_CHECK(dev::jsonCompactPrint(result["sources"]["fileA"]["legacyAST"]) == + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(result["sources"]["fileA"]["legacyAST"]), "{\"attributes\":{\"absolutePath\":\"fileA\",\"exportedSymbols\":{\"A\":[1]}},\"children\":" "[{\"attributes\":{\"baseContracts\":[null],\"contractDependencies\":[null],\"contractKind\":\"contract\"," "\"documentation\":null,\"fullyImplemented\":true,\"linearizedBaseContracts\":[1],\"name\":\"A\",\"nodes\":[null],\"scope\":2}," - "\"id\":1,\"name\":\"ContractDefinition\",\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}"); + "\"id\":1,\"name\":\"ContractDefinition\",\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}" + ); } BOOST_AUTO_TEST_SUITE_END() |