diff options
author | wbt <wbt@users.noreply.github.com> | 2018-01-23 22:44:37 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-23 22:44:37 +0800 |
commit | 761eae2499b172334e5ea3e75573af99fae2a975 (patch) | |
tree | 3ec324015e8d97e9b7ca43cb4dcc3c46b1b3bf31 | |
parent | 7c69d88f937324b64ed8825f9e611e417421434b (diff) | |
parent | e5def2da3d9b6cffbff42e2c9e1941831d2fe5ea (diff) | |
download | dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar.gz dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar.zst dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.zip |
Update from official repo
115 files changed, 5062 insertions, 825 deletions
diff --git a/.travis.yml b/.travis.yml index a2301c42..708d3620 100644 --- a/.travis.yml +++ b/.travis.yml @@ -184,10 +184,8 @@ before_script: script: - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh) - - test $? == 0 || SOLC_STOREBYTECODE=Off - test $SOLC_DOCS != On || (scripts/docs.sh) - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) - - test $? == 0 || SOLC_STOREBYTECODE=Off - test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh) deploy: diff --git a/CMakeLists.txt b/CMakeLists.txt index 24bea3b3..8993f372 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.19") +set(PROJECT_VERSION "0.4.20") project(solidity VERSION ${PROJECT_VERSION}) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) @@ -43,9 +43,10 @@ configure_project(TESTS) add_subdirectory(libdevcore) add_subdirectory(libevmasm) add_subdirectory(libsolidity) -add_subdirectory(solc) +add_subdirectory(libsolc) if (NOT EMSCRIPTEN) + add_subdirectory(solc) add_subdirectory(liblll) add_subdirectory(lllc) endif() diff --git a/Changelog.md b/Changelog.md index 27e72838..1c279dfb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,11 +1,32 @@ -### 0.4.19 (unreleased) +### 0.4.20 (unreleased) Features: - * Allow constant variables to be used as array length + * Commandline interface: Support strict mode of assembly with the ``--strict--assembly`` switch. + * Limit the number of warnings raised for creating abstract contracts. + * Inline Assembly: Issue warning for using jump labels (already existed for jump instructions). + * Inline Assembly: Support some restricted tokens (return, byte, address) as identifiers in Julia mode. + * SMT Checker: If-else branch conditions are taken into account in the SMT encoding of the program + variables. + +Bugfixes: + * Parser: Disallow event declarations with no parameter list. + * Standard JSON: Populate the ``sourceLocation`` field in the error list. + * Standard JSON: Properly support contract and library file names containing a colon (such as URLs). + * Type Checker: Suggest the experimental ABI encoder if using ``struct``s as function parameters + (instead of an internal compiler error). + * Type Checker: Improve error message for wrong struct initialization. + +### 0.4.19 (2017-11-30) + +Features: + * Code Generator: New ABI decoder which supports structs and arbitrarily nested + arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``). + * General: Allow constant variables to be used as array length. + * Inline Assembly: ``if`` statement. + * Standard JSON: Support the ``outputSelection`` field for selective compilation of target artifacts. * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). - * Inline Assembly: ``if`` statement. Bugfixes: diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index c93ce25b..b81e35a6 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -190,9 +190,9 @@ Given the contract: pragma solidity ^0.4.0; contract Foo { - function bar(bytes3[2] xy) {} - function baz(uint32 x, bool y) returns (bool r) { r = x > 32 || y; } - function sam(bytes name, bool z, uint[] data) {} + function bar(bytes3[2] xy) public {} + function baz(uint32 x, bool y) public returns (bool r) { r = x > 32 || y; } + function sam(bytes name, bool z, uint[] data) public {} } @@ -288,6 +288,8 @@ In effect, a log entry using this ABI is described as: - ``topics[n]``: ``EVENT_INDEXED_ARGS[n - 1]`` (``EVENT_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are indexed); - ``data``: ``abi_serialise(EVENT_NON_INDEXED_ARGS)`` (``EVENT_NON_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are not indexed, ``abi_serialise`` is the ABI serialisation function used for returning a series of typed values from a function, as described above). +For all fixed-length Solidity types, the ``EVENT_INDEXED_ARGS`` array contains the 32-byte encoded value directly. However, for *types of dynamic length*, which include ``string``, ``bytes``, and arrays, ``EVENT_INDEXED_ARGS`` will contain the *Keccak hash* of the encoded value, rather than the encoded value directly. This allows applications to efficiently query for values of dynamic-length types (by setting the hash of the encoded value as the topic), but leaves applications unable to decode indexed values they have not queried for. For dynamic-length types, application developers face a trade-off between fast search for predetermined values (if the argument is indexed) and legibility of arbitrary values (which requires that the arguments not be indexed). Developers may overcome this tradeoff and achieve both efficient search and arbitrary legibility by defining events with two arguments — one indexed, one not — intended to hold the same value. + JSON ==== @@ -333,10 +335,10 @@ For example, pragma solidity ^0.4.0; contract Test { - function Test(){ b = 0x12345678901234567890123456789012; } + function Test() public { b = 0x12345678901234567890123456789012; } event Event(uint indexed a, bytes32 b); event Event2(uint indexed a, bytes32 b); - function foo(uint a) { Event(a, b); } + function foo(uint a) public { Event(a, b); } bytes32 b; } @@ -377,10 +379,14 @@ As an example, the code :: + pragma solidity ^0.4.19; + pragma experimental ABIEncoderV2; + contract Test { struct S { uint a; uint[] b; T[] c; } struct T { uint x; uint y; } - function f(S s, T t, uint a) { } + function f(S s, T t, uint a) public { } + function g() public returns (S s, T t, uint a) {} } would result in the JSON: diff --git a/docs/assembly.rst b/docs/assembly.rst index c233985b..825892b3 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -23,7 +23,7 @@ arising when writing manual assembly by the following features: * functional-style opcodes: ``mul(1, add(2, 3))`` instead of ``push1 3 push1 2 add push1 1 mul`` * assembly-local variables: ``let x := add(2, 3) let y := mload(0x40) x := add(x, y)`` -* access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }`` +* access to external variables: ``function f(uint x) public { assembly { x := sub(x, 1) } }`` * labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`` * loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` * if statements: ``if slt(x, 0) { x := sub(0, x) }`` @@ -54,7 +54,7 @@ idea is that assembly libraries will be used to enhance the language in such way pragma solidity ^0.4.0; library GetCode { - function at(address _addr) returns (bytes o_code) { + function at(address _addr) public returns (bytes o_code) { assembly { // retrieve the size of the code, this needs assembly let size := extcodesize(_addr) @@ -83,7 +83,7 @@ you really know what you are doing. library VectorSum { // This function is less efficient because the optimizer currently fails to // remove the bounds checks in array access. - function sumSolidity(uint[] _data) returns (uint o_sum) { + function sumSolidity(uint[] _data) public returns (uint o_sum) { for (uint i = 0; i < _data.length; ++i) o_sum += _data[i]; } @@ -91,7 +91,7 @@ you really know what you are doing. // We know that we only access the array in bounds, so we can avoid the check. // 0x20 needs to be added to an array because the first slot contains the // array length. - function sumAsm(uint[] _data) returns (uint o_sum) { + function sumAsm(uint[] _data) public returns (uint o_sum) { for (uint i = 0; i < _data.length; ++i) { assembly { o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20)))) @@ -100,7 +100,7 @@ you really know what you are doing. } // Same as above, but accomplish the entire code within inline assembly. - function sumPureAsm(uint[] _data) returns (uint o_sum) { + function sumPureAsm(uint[] _data) public returns (uint o_sum) { assembly { // Load the length (first 32 bytes) let len := mload(_data) @@ -388,7 +388,7 @@ changes during the call, and thus references to local variables will be wrong. contract C { uint b; - function f(uint x) returns (uint r) { + function f(uint x) public returns (uint r) { assembly { r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero } @@ -447,31 +447,6 @@ will have a wrong impression about the stack height at label ``two``: three: } -This problem can be fixed by manually adjusting the stack height for the -assembler - you can provide a stack height delta that is added -to the stack height just prior to the label. -Note that you will not have to care about these things if you just use -loops and assembly-level functions. - -As an example how this can be done in extreme cases, please see the following. - -.. code:: - - { - let x := 8 - jump(two) - 0 // This code is unreachable but will adjust the stack height correctly - one: - x := 9 // Now x can be accessed properly. - jump(three) - pop // Similar negative correction. - two: - 7 // push something onto the stack - jump(one) - three: - pop // We have to pop the manually pushed value here again. - } - Declaring Assembly-Local Variables ---------------------------------- @@ -487,7 +462,7 @@ be just ``0``, but it can also be a complex functional-style expression. pragma solidity ^0.4.0; contract C { - function f(uint x) returns (uint b) { + function f(uint x) public returns (uint b) { assembly { let v := add(x, 1) mstore(0x80, v) @@ -599,7 +574,7 @@ Simply leave the initialization and post-iteration parts empty. x := add(x, mload(i)) i := add(i, 0x20) } - } + } Functions --------- @@ -738,7 +713,7 @@ We consider the runtime bytecode of the following Solidity program:: pragma solidity ^0.4.0; contract C { - function f(uint x) returns (uint y) { + function f(uint x) public returns (uint y) { y = 1; for (uint i = 0; i < x; i++) y = 2 * y; diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index cca45428..3a8ff9a1 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -397,6 +397,10 @@ "bugs": [], "released": "2017-10-18" }, + "0.4.19": { + "bugs": [], + "released": "2017-11-30" + }, "0.4.2": { "bugs": [ "ZeroFunctionSelector", diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index 52319be0..7e09f534 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -36,12 +36,12 @@ become the new richest. mapping (address => uint) pendingWithdrawals; - function WithdrawalContract() payable { + function WithdrawalContract() public payable { richest = msg.sender; mostSent = msg.value; } - function becomeRichest() payable returns (bool) { + function becomeRichest() public payable returns (bool) { if (msg.value > mostSent) { pendingWithdrawals[richest] += msg.value; richest = msg.sender; @@ -52,7 +52,7 @@ become the new richest. } } - function withdraw() { + function withdraw() public { uint amount = pendingWithdrawals[msg.sender]; // Remember to zero the pending refund before // sending to prevent re-entrancy attacks @@ -71,12 +71,12 @@ This is as opposed to the more intuitive sending pattern: address public richest; uint public mostSent; - function SendContract() payable { + function SendContract() public payable { richest = msg.sender; mostSent = msg.value; } - function becomeRichest() payable returns (bool) { + function becomeRichest() public payable returns (bool) { if (msg.value > mostSent) { // This line can cause problems (explained below). richest.transfer(msg.value); @@ -157,6 +157,7 @@ restrictions highly readable. /// Make `_newOwner` the new owner of this /// contract. function changeOwner(address _newOwner) + public onlyBy(owner) { owner = _newOwner; @@ -171,6 +172,7 @@ restrictions highly readable. /// May only be called 6 weeks after /// the contract has been created. function disown() + public onlyBy(owner) onlyAfter(creationTime + 6 weeks) { @@ -191,6 +193,7 @@ restrictions highly readable. } function forceOwnerChange(address _newOwner) + public costs(200 ether) { owner = _newOwner; @@ -310,6 +313,7 @@ function finishes. // Order of the modifiers matters here! function bid() + public payable timedTransitions atStage(Stages.AcceptingBlindedBids) @@ -318,6 +322,7 @@ function finishes. } function reveal() + public timedTransitions atStage(Stages.RevealBids) { @@ -332,6 +337,7 @@ function finishes. } function g() + public timedTransitions atStage(Stages.AnotherStage) transitionNext @@ -339,6 +345,7 @@ function finishes. } function h() + public timedTransitions atStage(Stages.AreWeDoneYet) transitionNext @@ -346,6 +353,7 @@ function finishes. } function i() + public timedTransitions atStage(Stages.Finished) { diff --git a/docs/contracts.rst b/docs/contracts.rst index ef1e1be6..fcc26b24 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -40,7 +40,7 @@ This means that cyclic creation dependencies are impossible. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract OwnedToken { // TokenCreator is a contract type that is defined below. @@ -52,7 +52,7 @@ This means that cyclic creation dependencies are impossible. // This is the constructor which registers the // creator and the assigned name. - function OwnedToken(bytes32 _name) { + function OwnedToken(bytes32 _name) public { // State variables are accessed via their name // and not via e.g. this.owner. This also applies // to functions and especially in the constructors, @@ -67,7 +67,7 @@ This means that cyclic creation dependencies are impossible. name = _name; } - function changeName(bytes32 newName) { + function changeName(bytes32 newName) public { // Only the creator can alter the name -- // the comparison is possible since contracts // are implicitly convertible to addresses. @@ -75,7 +75,7 @@ This means that cyclic creation dependencies are impossible. name = newName; } - function transfer(address newOwner) { + function transfer(address newOwner) public { // Only the current owner can transfer the token. if (msg.sender != owner) return; // We also want to ask the creator if the transfer @@ -90,25 +90,27 @@ This means that cyclic creation dependencies are impossible. contract TokenCreator { function createToken(bytes32 name) + public returns (OwnedToken tokenAddress) { // Create a new Token contract and return its address. // From the JavaScript side, the return type is simply - // "address", as this is the closest type available in + // `address`, as this is the closest type available in // the ABI. return new OwnedToken(name); } - function changeName(OwnedToken tokenAddress, bytes32 name) { - // Again, the external type of "tokenAddress" is - // simply "address". + function changeName(OwnedToken tokenAddress, bytes32 name) public { + // Again, the external type of `tokenAddress` is + // simply `address`. tokenAddress.changeName(name); } - function isTokenTransferOK( - address currentOwner, - address newOwner - ) returns (bool ok) { + function isTokenTransferOK(address currentOwner, address newOwner) + public + view + returns (bool ok) + { // Check some arbitrary condition. address tokenAddress = msg.sender; return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff); @@ -171,10 +173,10 @@ return parameter list for functions. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { - function f(uint a) private returns (uint b) { return a + 1; } + function f(uint a) private pure returns (uint b) { return a + 1; } function setData(uint a) internal { data = a; } uint public data; } @@ -193,25 +195,25 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value uint private data; function f(uint a) private returns(uint b) { return a + 1; } - function setData(uint a) { data = a; } + function setData(uint a) public { data = a; } function getData() public returns(uint) { return data; } function compute(uint a, uint b) internal returns (uint) { return a+b; } } contract D { - function readData() { + function readData() public { C c = new C(); - uint local = c.f(7); // error: member "f" is not visible + uint local = c.f(7); // error: member `f` is not visible c.setData(3); local = c.getData(); - local = c.compute(3, 5); // error: member "compute" is not visible + local = c.compute(3, 5); // error: member `compute` is not visible } } contract E is C { - function g() { + function g() public { C c = new C(); - uint val = compute(3, 5); // acces to internal member (from derivated to parent contract) + uint val = compute(3, 5); // access to internal member (from derived to parent contract) } } @@ -238,7 +240,7 @@ be done at declaration. contract Caller { C c = new C(); - function f() { + function f() public { uint local = c.data(); } } @@ -254,7 +256,7 @@ it is evaluated as a state variable. If it is accessed externally contract C { uint public data; - function x() { + function x() public { data = 3; // internal access uint val = this.data(); // external access } @@ -277,7 +279,7 @@ The next example is a bit more complex: It will generate a function of the following form:: - function data(uint arg1, bool arg2, uint arg3) returns (uint a, bytes3 b) { + function data(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) { a = data[arg1][arg2][arg3].a; b = data[arg1][arg2][arg3].b; } @@ -302,13 +304,13 @@ inheritable properties of contracts and may be overridden by derived contracts. pragma solidity ^0.4.11; contract owned { - function owned() { owner = msg.sender; } + function owned() public { owner = msg.sender; } address owner; // This contract only defines a modifier but does not use - // it - it will be used in derived contracts. + // it: it will be used in derived contracts. // The function body is inserted where the special symbol - // "_;" in the definition of a modifier appears. + // `_;` in the definition of a modifier appears. // This means that if the owner calls this function, the // function is executed and otherwise, an exception is // thrown. @@ -319,11 +321,11 @@ inheritable properties of contracts and may be overridden by derived contracts. } contract mortal is owned { - // This contract inherits the "onlyOwner"-modifier from - // "owned" and applies it to the "close"-function, which - // causes that calls to "close" only have an effect if + // This contract inherits the `onlyOwner` modifier from + // `owned` and applies it to the `close` function, which + // causes that calls to `close` only have an effect if // they are made by the stored owner. - function close() onlyOwner { + function close() public onlyOwner { selfdestruct(owner); } } @@ -341,16 +343,16 @@ inheritable properties of contracts and may be overridden by derived contracts. mapping (address => bool) registeredAddresses; uint price; - function Register(uint initialPrice) { price = initialPrice; } + function Register(uint initialPrice) public { price = initialPrice; } // It is important to also provide the - // "payable" keyword here, otherwise the function will + // `payable` keyword here, otherwise the function will // automatically reject all Ether sent to it. - function register() payable costs(price) { + function register() public payable costs(price) { registeredAddresses[msg.sender] = true; } - function changePrice(uint _price) onlyOwner { + function changePrice(uint _price) public onlyOwner { price = _price; } } @@ -365,10 +367,10 @@ inheritable properties of contracts and may be overridden by derived contracts. } /// This function is protected by a mutex, which means that - /// reentrant calls from within msg.sender.call cannot call f again. + /// reentrant calls from within `msg.sender.call` cannot call `f` again. /// The `return 7` statement assigns 7 to the return value but still /// executes the statement `locked = false` in the modifier. - function f() noReentrancy returns (uint) { + function f() public noReentrancy returns (uint) { require(msg.sender.call()); return 7; } @@ -426,19 +428,27 @@ value types and strings. bytes32 constant myHash = keccak256("abc"); } +.. index:: ! functions + +.. _functions: + +********* +Functions +********* + +.. index:: ! view function, function;view .. _view-functions: -************** View Functions -************** +============== Functions can be declared ``view`` in which case they promise not to modify the state. The following statements are considered modifying the state: #. Writing to state variables. -#. :ref:`Emitting events. <events>`. +#. :ref:`Emitting events <events>`. #. :ref:`Creating other contracts <creating-contracts>`. #. Using ``selfdestruct``. #. Sending Ether via calls. @@ -451,7 +461,7 @@ The following statements are considered modifying the state: pragma solidity ^0.4.16; contract C { - function f(uint a, uint b) view returns (uint) { + function f(uint a, uint b) public view returns (uint) { return a * (b + 42) + now; } } @@ -465,11 +475,12 @@ The following statements are considered modifying the state: .. warning:: The compiler does not enforce yet that a ``view`` method is not modifying state. +.. index:: ! pure function, function;pure + .. _pure-functions: -************** Pure Functions -************** +============== Functions can be declared ``pure`` in which case they promise not to read from or modify the state. @@ -486,7 +497,7 @@ In addition to the list of state modifying statements explained above, the follo pragma solidity ^0.4.16; contract C { - function f(uint a, uint b) pure returns (uint) { + function f(uint a, uint b) public pure returns (uint) { return a * (b + 42); } } @@ -498,9 +509,8 @@ In addition to the list of state modifying statements explained above, the follo .. _fallback-function: -***************** Fallback Function -***************** +================= A contract can have exactly one unnamed function. This function cannot have arguments and cannot return anything. @@ -551,9 +561,9 @@ Please ensure you test your fallback function thoroughly to ensure the execution // This function is called for all messages sent to // this contract (there is no other function). // Sending Ether to this contract will cause an exception, - // because the fallback function does not have the "payable" + // because the fallback function does not have the `payable` // modifier. - function() { x = 1; } + function() public { x = 1; } uint x; } @@ -561,11 +571,11 @@ Please ensure you test your fallback function thoroughly to ensure the execution // This contract keeps all Ether sent to it with no way // to get it back. contract Sink { - function() payable { } + function() public payable { } } contract Caller { - function callTest(Test test) { + function callTest(Test test) public { test.call(0xabcdef01); // hash does not exist // results in test.x becoming == 1. @@ -577,6 +587,85 @@ Please ensure you test your fallback function thoroughly to ensure the execution } } +.. index:: ! overload + +.. _overload-function: + +Function Overloading +==================== + +A Contract can have multiple functions of the same name but with different arguments. +This also applies to inherited functions. The following example shows overloading of the +``f`` function in the scope of contract ``A``. + +:: + + pragma solidity ^0.4.16; + + contract A { + function f(uint _in) public pure returns (uint out) { + out = 1; + } + + function f(uint _in, bytes32 _key) public pure returns (uint out) { + out = 2; + } + } + +Overloaded functions are also present in the external interface. It is an error if two +externally visible functions differ by their Solidity types but not by their external types. + +:: + + // This will not compile + pragma solidity ^0.4.16; + + contract A { + function f(B _in) public pure returns (B out) { + out = _in; + } + + function f(address _in) public pure returns (address out) { + out = _in; + } + } + + contract B { + } + + +Both ``f`` function overloads above end up accepting the address type for the ABI although +they are considered different inside Solidity. + +Overload resolution and Argument matching +----------------------------------------- + +Overloaded functions are selected by matching the function declarations in the current scope +to the arguments supplied in the function call. Functions are selected as overload candidates +if all arguments can be implicitly converted to the expected types. If there is not exactly one +candidate, resolution fails. + +.. note:: + Return parameters are not taken into account for overload resolution. + +:: + + pragma solidity ^0.4.16; + + contract A { + function f(uint8 _in) public pure returns (uint8 out) { + out = _in; + } + + function f(uint256 _in) public pure returns (uint256 out) { + out = _in; + } + } + +Calling ``f(50)`` would create a type error since ``250`` can be implicitly converted both to ``uint8`` +and ``uint256`` types. On another hand ``f(256)`` would resolve to ``f(uint256)`` overload as ``256`` cannot be implicitly +converted to ``uint8``. + .. index:: ! event .. _events: @@ -634,7 +723,7 @@ All non-indexed arguments will be stored in the data part of the log. uint _value ); - function deposit(bytes32 _id) payable { + function deposit(bytes32 _id) public payable { // Any call to this function (even deeply nested) can // be detected from the JavaScript API by filtering // for `Deposit` to be called. @@ -655,7 +744,7 @@ The use in the JavaScript API would be as follows: // watch for changes event.watch(function(error, result){ // result will contain various information - // including the argumets given to the Deposit + // including the argumets given to the `Deposit` // call. if (!error) console.log(result); @@ -683,7 +772,7 @@ as topics. The event call above can be performed in the same way as pragma solidity ^0.4.10; contract C { - function f() { + function f() public payable { bytes32 _id = 0x420042; log3( bytes32(msg.value), @@ -727,14 +816,14 @@ Details are given in the following example. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract owned { function owned() { owner = msg.sender; } address owner; } - // Use "is" to derive from another contract. Derived + // Use `is` to derive from another contract. Derived // contracts can access all non-private members including // internal functions and state variables. These cannot be // accessed externally via `this`, though. @@ -749,20 +838,20 @@ Details are given in the following example. // without body. If a contract does not implement all // functions it can only be used as an interface. contract Config { - function lookup(uint id) returns (address adr); + function lookup(uint id) public returns (address adr); } contract NameReg { - function register(bytes32 name); - function unregister(); + function register(bytes32 name) public; + function unregister() public; } - // Multiple inheritance is possible. Note that "owned" is - // also a base class of "mortal", yet there is only a single - // instance of "owned" (as for virtual inheritance in C++). + // Multiple inheritance is possible. Note that `owned` is + // also a base class of `mortal`, yet there is only a single + // instance of `owned` (as for virtual inheritance in C++). contract named is owned, mortal { function named(bytes32 name) { - Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); + Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); NameReg(config.lookup(1)).register(name); } @@ -771,9 +860,9 @@ Details are given in the following example. // types of output parameters, that causes an error. // Both local and message-based function calls take these overrides // into account. - function kill() { + function kill() public { if (msg.sender == owner) { - Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); + Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); NameReg(config.lookup(1)).unregister(); // It is still possible to call a specific // overridden function. @@ -786,11 +875,11 @@ Details are given in the following example. // provided in the header (or modifier-invocation-style at // the constructor of the derived contract (see below)). contract PriceFeed is owned, mortal, named("GoldFeed") { - function updateInfo(uint newInfo) { + function updateInfo(uint newInfo) public { if (msg.sender == owner) info = newInfo; } - function get() constant returns(uint r) { return info; } + function get() public view returns(uint r) { return info; } uint info; } @@ -802,22 +891,22 @@ seen in the following example:: pragma solidity ^0.4.0; contract owned { - function owned() { owner = msg.sender; } + function owned() public { owner = msg.sender; } address owner; } contract mortal is owned { - function kill() { + function kill() public { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { - function kill() { /* do cleanup 1 */ mortal.kill(); } + function kill() public { /* do cleanup 1 */ mortal.kill(); } } contract Base2 is mortal { - function kill() { /* do cleanup 2 */ mortal.kill(); } + function kill() public { /* do cleanup 2 */ mortal.kill(); } } contract Final is Base1, Base2 { @@ -831,30 +920,30 @@ derived override, but this function will bypass pragma solidity ^0.4.0; contract owned { - function owned() { owner = msg.sender; } + function owned() public { owner = msg.sender; } address owner; } contract mortal is owned { - function kill() { + function kill() public { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { - function kill() { /* do cleanup 1 */ super.kill(); } + function kill() public { /* do cleanup 1 */ super.kill(); } } contract Base2 is mortal { - function kill() { /* do cleanup 2 */ super.kill(); } + function kill() public { /* do cleanup 2 */ super.kill(); } } contract Final is Base2, Base1 { } If ``Base1`` calls a function of ``super``, it does not simply -call this function on one of its base contracts. Rather, it +call this function on one of its base contracts. Rather, it calls this function on the next base contract in the final inheritance graph, so it will call ``Base2.kill()`` (note that the final inheritance sequence is -- starting with the most @@ -876,11 +965,11 @@ the base constructors. This can be done in two ways:: contract Base { uint x; - function Base(uint _x) { x = _x; } + function Base(uint _x) public { x = _x; } } contract Derived is Base(7) { - function Derived(uint _y) Base(_y * _y) { + function Derived(uint _y) Base(_y * _y) public { } } @@ -945,7 +1034,7 @@ Contract functions can lack an implementation as in the following example (note pragma solidity ^0.4.0; contract Feline { - function utterance() returns (bytes32); + function utterance() public returns (bytes32); } Such contracts cannot be compiled (even if they contain @@ -955,11 +1044,11 @@ but they can be used as base contracts:: pragma solidity ^0.4.0; contract Feline { - function utterance() returns (bytes32); + function utterance() public returns (bytes32); } contract Cat is Feline { - function utterance() returns (bytes32) { return "miaow"; } + function utterance() public returns (bytes32) { return "miaow"; } } If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract. @@ -990,7 +1079,7 @@ Interfaces are denoted by their own keyword: pragma solidity ^0.4.11; interface Token { - function transfer(address recipient, uint amount); + function transfer(address recipient, uint amount) public; } Contracts can inherit interfaces as they would inherit other contracts. @@ -1033,7 +1122,7 @@ more advanced example to implement a set). :: - pragma solidity ^0.4.11; + pragma solidity ^0.4.16; library Set { // We define a new struct datatype that will be used to @@ -1044,9 +1133,10 @@ more advanced example to implement a set). // reference" and thus only its storage address and not // its contents is passed as part of the call. This is a // special feature of library functions. It is idiomatic - // to call the first parameter 'self', if the function can + // to call the first parameter `self`, if the function can // be seen as a method of that object. function insert(Data storage self, uint value) + public returns (bool) { if (self.flags[value]) @@ -1056,6 +1146,7 @@ more advanced example to implement a set). } function remove(Data storage self, uint value) + public returns (bool) { if (!self.flags[value]) @@ -1065,6 +1156,8 @@ more advanced example to implement a set). } function contains(Data storage self, uint value) + public + view returns (bool) { return self.flags[value]; @@ -1074,7 +1167,7 @@ more advanced example to implement a set). contract C { Set.Data knownValues; - function register(uint value) { + function register(uint value) public { // The library functions can be called without a // specific instance of the library, since the // "instance" will be the current contract. @@ -1084,7 +1177,7 @@ more advanced example to implement a set). } Of course, you do not have to follow this way to use -libraries - they can also be used without defining struct +libraries: they can also be used without defining struct data types. Functions also work without any storage reference parameters, and they can have multiple storage reference parameters and in any position. @@ -1103,19 +1196,19 @@ custom types without the overhead of external function calls: :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; library BigInt { struct bigint { uint[] limbs; } - function fromUint(uint x) internal returns (bigint r) { + function fromUint(uint x) internal pure returns (bigint r) { r.limbs = new uint[](1); r.limbs[0] = x; } - function add(bigint _a, bigint _b) internal returns (bigint r) { + function add(bigint _a, bigint _b) internal pure returns (bigint r) { r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length)); uint carry = 0; for (uint i = 0; i < r.limbs.length; ++i) { @@ -1137,11 +1230,11 @@ custom types without the overhead of external function calls: } } - function limb(bigint _a, uint _limb) internal returns (uint) { + function limb(bigint _a, uint _limb) internal pure returns (uint) { return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; } - function max(uint a, uint b) private returns (uint) { + function max(uint a, uint b) private pure returns (uint) { return a > b ? a : b; } } @@ -1149,7 +1242,7 @@ custom types without the overhead of external function calls: contract C { using BigInt for BigInt.bigint; - function f() { + function f() public pure { var x = BigInt.fromUint(7); var y = BigInt.fromUint(uint(-1)); var z = x.add(y); @@ -1207,13 +1300,14 @@ available without having to add further code. Let us rewrite the set example from the :ref:`libraries` in this way:: - pragma solidity ^0.4.11; + pragma solidity ^0.4.16; // This is the same code as before, just without comments library Set { struct Data { mapping(uint => bool) flags; } function insert(Data storage self, uint value) + public returns (bool) { if (self.flags[value]) @@ -1223,6 +1317,7 @@ Let us rewrite the set example from the } function remove(Data storage self, uint value) + public returns (bool) { if (!self.flags[value]) @@ -1232,6 +1327,8 @@ Let us rewrite the set example from the } function contains(Data storage self, uint value) + public + view returns (bool) { return self.flags[value]; @@ -1242,21 +1339,25 @@ Let us rewrite the set example from the using Set for Set.Data; // this is the crucial change Set.Data knownValues; - function register(uint value) { + function register(uint value) public { // Here, all variables of type Set.Data have // corresponding member functions. // The following function call is identical to - // Set.insert(knownValues, value) + // `Set.insert(knownValues, value)` require(knownValues.insert(value)); } } It is also possible to extend elementary types in that way:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; library Search { - function indexOf(uint[] storage self, uint value) returns (uint) { + function indexOf(uint[] storage self, uint value) + public + view + returns (uint) + { for (uint i = 0; i < self.length; i++) if (self[i] == value) return i; return uint(-1); @@ -1267,11 +1368,11 @@ It is also possible to extend elementary types in that way:: using Search for uint[]; uint[] data; - function append(uint value) { + function append(uint value) public { data.push(value); } - function replace(uint _old, uint _new) { + function replace(uint _old, uint _new) public { // This performs the library function call uint index = data.indexOf(_old); if (index == uint(-1)) diff --git a/docs/contributing.rst b/docs/contributing.rst index 0f7c3e72..a5efba8b 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -50,8 +50,10 @@ and instead, ``git rebase`` your branch. Additionally, if you are writing a new feature, please ensure you write appropriate Boost test cases and place them under ``test/``. -However, if you are making a larger change, please consult with the Gitter -channel, first. +However, if you are making a larger change, please consult with the `Solidity Development Gitter channel +<https://gitter.im/ethereum/solidity-dev>`_ (different from the one mentioned above, this on is +focused on compiler and language development instead of language use) first. + Finally, please make sure you respect the `coding standards <https://raw.githubusercontent.com/ethereum/cpp-ethereum/develop/CodingStandards.txt>`_ diff --git a/docs/control-structures.rst b/docs/control-structures.rst index bcb597cf..1a8ee25b 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -20,10 +20,10 @@ For example, suppose we want our contract to accept one kind of external calls with two integers, we would write something like:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract Simple { - function taker(uint _a, uint _b) { + function taker(uint _a, uint _b) public pure { // do something with _a and _b. } } @@ -36,10 +36,14 @@ The output parameters can be declared with the same syntax after the the sum and the product of the two given integers, then we would write:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract Simple { - function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) { + function arithmetics(uint _a, uint _b) + public + pure + returns (uint o_sum, uint o_product) + { o_sum = _a + _b; o_product = _a * _b; } @@ -95,11 +99,11 @@ Internal Function Calls Functions of the current contract can be called directly ("internally"), also recursively, as seen in this nonsensical example:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { - function g(uint a) returns (uint ret) { return f(); } - function f() returns (uint ret) { return g(7) + f(); } + function g(uint a) public pure returns (uint ret) { return f(); } + function f() internal pure returns (uint ret) { return g(7) + f(); } } These function calls are translated into simple jumps inside the EVM. This has @@ -125,13 +129,13 @@ the gas can be specified with special options ``.value()`` and ``.gas()``, respe pragma solidity ^0.4.0; contract InfoFeed { - function info() payable returns (uint ret) { return 42; } + function info() public payable returns (uint ret) { return 42; } } contract Consumer { InfoFeed feed; - function setFeed(address addr) { feed = InfoFeed(addr); } - function callFeed() { feed.info.value(10).gas(800)(); } + function setFeed(address addr) public { feed = InfoFeed(addr); } + function callFeed() public { feed.info.value(10).gas(800)(); } } The modifier ``payable`` has to be used for ``info``, because otherwise, the `.value()` @@ -180,11 +184,11 @@ parameters from the function declaration, but can be in arbitrary order. pragma solidity ^0.4.0; contract C { - function f(uint key, uint value) { + function f(uint key, uint value) public { // ... } - function g() { + function g() public { // named arguments f({value: 2, key: 3}); } @@ -198,11 +202,11 @@ Those parameters will still be present on the stack, but they are inaccessible. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { // omitted name for parameter - function func(uint k, uint) returns(uint) { + function func(uint k, uint) public pure returns(uint) { return k; } } @@ -225,7 +229,7 @@ creation-dependencies are not possible. contract D { uint x; - function D(uint a) payable { + function D(uint a) public payable { x = a; } } @@ -233,11 +237,11 @@ creation-dependencies are not possible. contract C { D d = new D(4); // will be executed as part of C's constructor - function createD(uint arg) { + function createD(uint arg) public { D newD = new D(arg); } - function createAndEndowD(uint arg, uint amount) payable { + function createAndEndowD(uint arg, uint amount) public payable { // Send ether along with the creation D newD = (new D).value(amount)(arg); } @@ -270,16 +274,16 @@ Destructuring Assignments and Returning Multiple Values Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose size is a constant at compile-time. Those tuples can be used to return multiple values at the same time and also assign them to multiple variables (or LValues in general) at the same time:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { uint[] data; - function f() returns (uint, bool, uint) { + function f() public pure returns (uint, bool, uint) { return (7, true, 2); } - function g() { + function g() public { // Declares and assigns the variables. Specifying the type explicitly is not possible. var (x, b, y) = f(); // Assigns to a pre-existing variable. @@ -291,6 +295,7 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di // the rest of the values are discarded. (data.length,) = f(); // Sets the length to 7 // The same can be done on the left side. + // If the tuple begins in an empty component, the beginning values are discarded. (,data[3]) = f(); // Sets data[3] to 2 // Components can only be left out at the left-hand-side of assignments, with // one exception: @@ -326,10 +331,10 @@ As a result, the following code is illegal and cause the compiler to throw an er // This will not compile - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract ScopingErrors { - function scoping() { + function scoping() public { uint i = 0; while (i++ < 1) { @@ -341,7 +346,7 @@ As a result, the following code is illegal and cause the compiler to throw an er } } - function minimalScoping() { + function minimalScoping() public { { uint same2 = 0; } @@ -351,7 +356,7 @@ As a result, the following code is illegal and cause the compiler to throw an er } } - function forLoopScoping() { + function forLoopScoping() public { for (uint same3 = 0; same3 < 1; same3++) { } @@ -364,9 +369,9 @@ In addition to this, if a variable is declared, it will be initialized at the be As a result, the following code is legal, despite being poorly written:: pragma solidity ^0.4.0; - + contract C { - function foo() returns (uint) { + function foo() public pure returns (uint) { // baz is implicitly initialized as 0 uint bar = 5; if (true) { @@ -412,7 +417,7 @@ 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) { + function sendHalf(address addr) public payable returns (uint balance) { require(msg.value % 2 == 0); // Only allow even numbers uint balanceBeforeTransfer = this.balance; addr.transfer(msg.value / 2); diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index 5c427c69..7c934041 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -111,10 +111,10 @@ array in the return statement. Pretty cool, huh? Example:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { - function f() returns (uint8[5]) { + function f() public pure returns (uint8[5]) { string[4] memory adaArr = ["This", "is", "an", "array"]; return ([1, 2, 3, 4, 5]); } @@ -190,11 +190,11 @@ you should always convert it to a ``bytes`` first:: contract C { string s; - function append(byte c) { + function append(byte c) public { bytes(s).push(c); } - function set(uint i, byte c) { + function set(uint i, byte c) public { bytes(s)[i] = c; } } @@ -232,12 +232,14 @@ situation. If you do not want to throw, you can return a pair:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { uint[] counters; function getCounter(uint index) + public + view returns (uint counter, bool error) { if (index >= counters.length) return (0, true); @@ -245,7 +247,7 @@ If you do not want to throw, you can return a pair:: return (counters[index], false); } - function checkCounter(uint index) { + function checkCounter(uint index) public view { var (counter, error) = getCounter(index); if (error) { // ... @@ -316,11 +318,11 @@ Example:: uint[] data1; uint[] data2; - function appendOne() { + function appendOne() public { append(data1); } - function appendTwo() { + function appendTwo() public { append(data2); } @@ -349,7 +351,7 @@ be created in memory, although it will be created in storage:: uint someVariable; uint[] data; - function f() { + function f() public { uint[] x; x.push(2); data = x; @@ -375,7 +377,7 @@ The correct way to do this is the following:: uint someVariable; uint[] data; - function f() { + function f() public { uint[] x = data; x.push(2); } @@ -431,14 +433,14 @@ What happens to a ``struct``'s mapping when copying over a ``struct``? This is a very interesting question. Suppose that we have a contract field set up like such:: - struct user { + struct User { mapping(string => string) comments; } - function somefunction { - user user1; + function somefunction public { + User user1; user1.comments["Hello"] = "World"; - user user2 = user1; + User user2 = user1; } In this case, the mapping of the struct being copied over into the userList is ignored as there is no "list of mapped keys". @@ -456,13 +458,13 @@ In this example:: pragma solidity ^0.4.0; contract B { - function B() payable {} + function B() public payable {} } contract A { address child; - function test() { + function test() public { child = (new B).value(10)(); //construct a new B with 10 wei } } @@ -501,17 +503,17 @@ Can a contract pass an array (static size) or string or ``bytes`` (dynamic size) Sure. Take care that if you cross the memory / storage boundary, independent copies will be created:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { uint[20] x; - function f() { + function f() public { g(x); h(x); } - function g(uint[20] y) internal { + function g(uint[20] y) internal pure { y[2] = 3; } diff --git a/docs/grammar.txt b/docs/grammar.txt index ce3fd3ad..e700c946 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -27,14 +27,19 @@ ModifierInvocation = Identifier ( '(' ExpressionList? ')' )? FunctionDefinition = 'function' Identifier? ParameterList ( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )* ( 'returns' ParameterList )? ( ';' | Block ) -EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';' +EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';' EnumValue = Identifier EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}' -IndexedParameterList = '(' ( TypeName 'indexed'? Identifier? (',' TypeName 'indexed'? Identifier?)* )? ')' -ParameterList = '(' ( TypeName Identifier? (',' TypeName Identifier?)* )? ')' -TypeNameList = '(' ( TypeName (',' TypeName )* )? ')' +ParameterList = '(' ( Parameter (',' Parameter)* )? ')' +Parameter = TypeName StorageLocation? Identifier? + +EventParameterList = '(' ( EventParameter (',' EventParameter )* )? ')' +EventParameter = TypeName 'indexed'? Identifier? + +FunctionTypeParameterList = '(' ( FunctionTypeParameter (',' FunctionTypeParameter )* )? ')' +FunctionTypeParameter = TypeName StorageLocation? // semantic restriction: mappings and structs (recursively) containing mappings // are not allowed in argument lists @@ -50,8 +55,8 @@ UserDefinedTypeName = Identifier ( '.' Identifier )* Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' ArrayTypeName = TypeName '[' Expression? ']' -FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | StateMutability )* - ( 'returns' TypeNameList )? +FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )* + ( 'returns' FunctionTypeParameterList )? StorageLocation = 'memory' | 'storage' StateMutability = 'pure' | 'constant' | 'view' | 'payable' diff --git a/docs/index.rst b/docs/index.rst index 3c617d36..0a49681e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -70,7 +70,7 @@ Available Solidity Integrations Configurable Solidty linter for Atom using Solium as a base. * `Solium <https://github.com/duaraghav8/Solium/>`_ - A commandline linter for Solidity which strictly follows the rules prescribed by the `Solidity Style Guide <http://solidity.readthedocs.io/en/latest/style-guide.html>`_. + Linter to identify and fix style and security issues in Solidity. * `Solhint <https://github.com/protofire/solhint>`_ Solidity linter that provides security, style guide and best practice rules for smart contract validation. diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index b660cf02..8f30f199 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -2,9 +2,9 @@ .. _installing-solidity: -################### -Installing Solidity -################### +################################ +Installing the Solidity Compiler +################################ Versioning ========== @@ -18,30 +18,38 @@ will use the latest release. Remix ===== -If you just want to try Solidity for small contracts, you -can try `Remix <https://remix.ethereum.org/>`_ -which does not need any installation. If you want to use it -without connection to the Internet, you can go to -https://github.com/ethereum/browser-solidity/tree/gh-pages and -download the .ZIP file as explained on that page. +*We recommend Remix for small contracts and for quickly learning Solidity.* + +`Access Remix online <https://remix.ethereum.org/>`_, you don't need to install anything. +If you want to use it without connection to the Internet, go to +https://github.com/ethereum/browser-solidity/tree/gh-pages and download the .ZIP file as +explained on that page. + +Further options on this page detail installing commandline Solidity compiler software +on your computer. Choose a commandline compiler if you are working on a larger contract +or if you require more compilation options. npm / Node.js ============= -This is probably the most portable and most convenient way to install Solidity locally. +Use `npm` for a convenient and portable way to install `solcjs`, a Solidity compiler. The +`solcjs` program has less features than all options further down this page. Our +`Using the compiler <using-the-compiler.html>` documentation assumes you are using +the full-featured compiler, `solc`. So if you install `solcjs` from `npm` then you will +stop reading the documentation here and then continue to <https://github.com/ethereum/solc-js>, -A platform-independent JavaScript library is provided by compiling the C++ source -into JavaScript using Emscripten. It can be used in projects directly (such as Remix). +Note: The `solc-js <https://github.com/ethereum/solc-js>` project is derived from the C++ +`solc` by using Emscripten. `solc-js` can be used in JavaScript projects directly (such as Remix). Please refer to the `solc-js <https://github.com/ethereum/solc-js>`_ repository for instructions. -It also contains a commandline tool called `solcjs`, which can be installed via npm: - .. code:: bash npm install -g solc .. note:: + The commandline is named `solcjs`. + The comandline options of `solcjs` are not compatible with `solc` and tools (such as `geth`) expecting the behaviour of `solc` will not work with `solcjs`. @@ -63,7 +71,7 @@ output directories. Binary Packages =============== -Binary packages of Solidity available at +Binary packages of Solidity are available at `solidity/releases <https://github.com/ethereum/solidity/releases>`_. We also have PPAs for Ubuntu. For the latest stable version. diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index aedc0c09..6425dcaa 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -21,11 +21,11 @@ Storage contract SimpleStorage { uint storedData; - function set(uint x) { + function set(uint x) public { storedData = x; } - function get() constant returns (uint) { + function get() public constant returns (uint) { return storedData; } } @@ -94,16 +94,16 @@ registering with username and password - all you need is an Ethereum keypair. // This is the constructor whose code is // run only when the contract is created. - function Coin() { + function Coin() public { minter = msg.sender; } - function mint(address receiver, uint amount) { + function mint(address receiver, uint amount) public { if (msg.sender != minter) return; balances[receiver] += amount; } - function send(address receiver, uint amount) { + function send(address receiver, uint amount) public { if (balances[msg.sender] < amount) return; balances[msg.sender] -= amount; balances[receiver] += amount; @@ -145,7 +145,7 @@ like this one. The :ref:`getter function<getter-functions>` created by the ``pub is a bit more complex in this case. It roughly looks like the following:: - function balances(address _account) returns (uint) { + function balances(address _account) public view returns (uint) { return balances[_account]; } diff --git a/docs/julia.rst b/docs/julia.rst index 309e6b36..9e961a9d 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -14,6 +14,13 @@ It can already be used for "inline assembly" inside Solidity and future versions of the Solidity compiler will even use JULIA as intermediate language. It should also be easy to build high-level optimizer stages for JULIA. +.. note:: + + Note that the flavour used for "inline assembly" does not have types + (everything is ``u256``) and the built-in functions are identical + to the EVM opcodes. Please resort to the inline assembly documentation + for details. + The core components of JULIA are functions, blocks, variables, literals, for-loops, if-statements, switch-statements, expressions and assignments to variables. diff --git a/docs/metadata.rst b/docs/metadata.rst index dbde87e8..5e37219e 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -131,6 +131,8 @@ user interface for the contract. Furthermore, Mist can use the userdoc to display a confirmation message to the user whenever they interact with the contract. +Additional information about Ethereum Natural Specification (NatSpec) can be found `here <https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format>`_. + Usage for Source Code Verification ================================== diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 337a3d3f..49fd7ea4 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -62,17 +62,34 @@ complete contract): /// Mapping of ether shares of the contract. mapping(address => uint) shares; /// Withdraw your share. - function withdraw() { + function withdraw() public { if (msg.sender.send(shares[msg.sender])) shares[msg.sender] = 0; } } The problem is not too serious here because of the limited gas as part -of ``send``, but it still exposes a weakness: Ether transfer always -includes code execution, so the recipient could be a contract that calls +of ``send``, but it still exposes a weakness: Ether transfer can always +include code execution, so the recipient could be a contract that calls back into ``withdraw``. This would let it get multiple refunds and -basically retrieve all the Ether in the contract. +basically retrieve all the Ether in the contract. In particular, the +following contract will allow an attacker to refund multiple times +as it uses ``call`` which forwards all remaining gas by default: + +:: + + pragma solidity ^0.4.0; + + // THIS CONTRACT CONTAINS A BUG - DO NOT USE + contract Fund { + /// Mapping of ether shares of the contract. + mapping(address => uint) shares; + /// Withdraw your share. + function withdraw() public { + if (msg.sender.call.value(shares[msg.sender])()) + shares[msg.sender] = 0; + } + } To avoid re-entrancy, you can use the Checks-Effects-Interactions pattern as outlined further below: @@ -85,7 +102,7 @@ outlined further below: /// Mapping of ether shares of the contract. mapping(address => uint) shares; /// Withdraw your share. - function withdraw() { + function withdraw() public { var share = shares[msg.sender]; shares[msg.sender] = 0; msg.sender.transfer(share); @@ -113,7 +130,7 @@ Sending and Receiving Ether - Neither contracts nor "external accounts" are currently able to prevent that someone sends them Ether. Contracts can react on and reject a regular transfer, but there are ways to move Ether without creating a message call. One way is to simply "mine to" - the contract address and the second way is using ``selfdestruct(x)``. + the contract address and the second way is using ``selfdestruct(x)``. - If a contract receives Ether (without a function being called), the fallback function is executed. If it does not have a fallback function, the Ether will be rejected (by throwing an exception). @@ -169,11 +186,11 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like contract TxUserWallet { address owner; - function TxUserWallet() { + function TxUserWallet() public { owner = msg.sender; } - function transferTo(address dest, uint amount) { + function transferTo(address dest, uint amount) public { require(tx.origin == owner); dest.transfer(amount); } @@ -186,17 +203,17 @@ Now someone tricks you into sending ether to the address of this attack wallet: pragma solidity ^0.4.11; interface TxUserWallet { - function transferTo(address dest, uint amount); + function transferTo(address dest, uint amount) public; } contract TxAttackWallet { address owner; - function TxAttackWallet() { + function TxAttackWallet() public { owner = msg.sender; } - function() { + function() public { TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); } } diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 9489665e..b663083c 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -36,7 +36,7 @@ of votes. :: - pragma solidity ^0.4.11; + pragma solidity ^0.4.16; /// @title Voting with delegation. contract Ballot { @@ -66,7 +66,7 @@ of votes. Proposal[] public proposals; /// Create a new ballot to choose one of `proposalNames`. - function Ballot(bytes32[] proposalNames) { + function Ballot(bytes32[] proposalNames) public { chairperson = msg.sender; voters[chairperson].weight = 1; @@ -86,7 +86,7 @@ of votes. // Give `voter` the right to vote on this ballot. // May only be called by `chairperson`. - function giveRightToVote(address voter) { + function giveRightToVote(address voter) public { // If the argument of `require` evaluates to `false`, // it terminates and reverts all changes to // the state and to Ether balances. It is often @@ -99,7 +99,7 @@ of votes. } /// Delegate your vote to the voter `to`. - function delegate(address to) { + function delegate(address to) public { // assigns reference Voter storage sender = voters[msg.sender]; require(!sender.voted); @@ -140,7 +140,7 @@ of votes. /// Give your vote (including votes delegated to you) /// to proposal `proposals[proposal].name`. - function vote(uint proposal) { + function vote(uint proposal) public { Voter storage sender = voters[msg.sender]; require(!sender.voted); sender.voted = true; @@ -154,7 +154,7 @@ of votes. /// @dev Computes the winning proposal taking all /// previous votes into account. - function winningProposal() constant + function winningProposal() public view returns (uint winningProposal) { uint winningVoteCount = 0; @@ -169,7 +169,7 @@ of votes. // Calls winningProposal() function to get the index // of the winner contained in the proposals array and then // returns the name of the winner - function winnerName() constant + function winnerName() public view returns (bytes32 winnerName) { winnerName = proposals[winningProposal()].name; @@ -248,7 +248,7 @@ activate themselves. function SimpleAuction( uint _biddingTime, address _beneficiary - ) { + ) public { beneficiary = _beneficiary; auctionEnd = now + _biddingTime; } @@ -257,7 +257,7 @@ activate themselves. /// together with this transaction. /// The value will only be refunded if the /// auction is not won. - function bid() payable { + function bid() public payable { // No arguments are necessary, all // information is already part of // the transaction. The keyword payable @@ -286,7 +286,7 @@ activate themselves. } /// Withdraw a bid that was overbid. - function withdraw() returns (bool) { + function withdraw() public returns (bool) { uint amount = pendingReturns[msg.sender]; if (amount > 0) { // It is important to set this to zero because the recipient @@ -305,7 +305,7 @@ activate themselves. /// End the auction and send the highest bid /// to the beneficiary. - function auctionEnd() { + function auctionEnd() public { // It is a good guideline to structure functions that interact // with other contracts (i.e. they call functions or send Ether) // into three phases: @@ -405,7 +405,7 @@ high or low invalid bids. uint _biddingTime, uint _revealTime, address _beneficiary - ) { + ) public { beneficiary = _beneficiary; biddingEnd = now + _biddingTime; revealEnd = biddingEnd + _revealTime; @@ -421,6 +421,7 @@ high or low invalid bids. /// still make the required deposit. The same address can /// place multiple bids. function bid(bytes32 _blindedBid) + public payable onlyBefore(biddingEnd) { @@ -438,6 +439,7 @@ high or low invalid bids. bool[] _fake, bytes32[] _secret ) + public onlyAfter(biddingEnd) onlyBefore(revealEnd) { @@ -487,7 +489,7 @@ high or low invalid bids. } /// Withdraw a bid that was overbid. - function withdraw() { + function withdraw() public { uint amount = pendingReturns[msg.sender]; if (amount > 0) { // It is important to set this to zero because the recipient @@ -503,6 +505,7 @@ high or low invalid bids. /// End the auction and send the highest bid /// to the beneficiary. function auctionEnd() + public onlyAfter(revealEnd) { require(!ended); @@ -533,7 +536,7 @@ Safe Remote Purchase // Ensure that `msg.value` is an even number. // Division will truncate if it is an odd number. // Check via multiplication that it wasn't an odd number. - function Purchase() payable { + function Purchase() public payable { seller = msg.sender; value = msg.value / 2; require((2 * value) == msg.value); @@ -567,6 +570,7 @@ Safe Remote Purchase /// Can only be called by the seller before /// the contract is locked. function abort() + public onlySeller inState(State.Created) { @@ -580,6 +584,7 @@ Safe Remote Purchase /// The ether will be locked until confirmReceived /// is called. function confirmPurchase() + public inState(State.Created) condition(msg.value == (2 * value)) payable @@ -592,6 +597,7 @@ Safe Remote Purchase /// Confirm that you (the buyer) received the item. /// This will release the locked ether. function confirmReceived() + public onlyBuyer inState(State.Locked) { diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 0b554800..a9a7ed52 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -8,7 +8,7 @@ Structure of a Contract Contracts in Solidity are similar to classes in object-oriented languages. Each contract can contain declarations of :ref:`structure-state-variables`, :ref:`structure-functions`, -:ref:`structure-function-modifiers`, :ref:`structure-events`, :ref:`structure-structs-types` and :ref:`structure-enum-types`. +:ref:`structure-function-modifiers`, :ref:`structure-events`, :ref:`structure-struct-types` and :ref:`structure-enum-types`. Furthermore, contracts can inherit from other contracts. .. _structure-state-variables: @@ -43,7 +43,7 @@ Functions are the executable units of code within a contract. pragma solidity ^0.4.0; contract SimpleAuction { - function bid() payable { // Function + function bid() public payable { // Function // ... } } @@ -72,7 +72,7 @@ Function modifiers can be used to amend the semantics of functions in a declarat _; } - function abort() onlySeller { // Modifier usage + function abort() public onlySeller { // Modifier usage // ... } } @@ -91,7 +91,7 @@ Events are convenience interfaces with the EVM logging facilities. contract SimpleAuction { event HighestBidIncreased(address bidder, uint amount); // Event - function bid() payable { + function bid() public payable { // ... HighestBidIncreased(msg.sender, msg.value); // Triggering event } @@ -100,9 +100,9 @@ Events are convenience interfaces with the EVM logging facilities. See :ref:`events` in contracts section for information on how events are declared and can be used from within a dapp. -.. _structure-structs-types: +.. _structure-struct-types: -Structs Types +Struct Types ============= Structs are custom defined types that can group several variables (see diff --git a/docs/style-guide.rst b/docs/style-guide.rst index a438b3d0..4c0d44f0 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -86,17 +86,17 @@ Blank lines may be omitted between groups of related one-liners (such as stub fu Yes:: contract A { - function spam(); - function ham(); + function spam() public; + function ham() public; } contract B is A { - function spam() { + function spam() public { ... } - function ham() { + function ham() public { ... } } @@ -104,10 +104,10 @@ Yes:: No:: contract A { - function spam() { + function spam() public { ... } - function ham() { + function ham() public { ... } } @@ -169,26 +169,26 @@ Within a grouping, place the ``constant`` functions last. Yes:: contract A { - function A() { + function A() public { ... } - - function() { + + function() public { ... } - + // External functions // ... - + // External functions that are constant // ... - + // Public functions // ... - + // Internal functions // ... - + // Private functions // ... } @@ -196,7 +196,7 @@ Yes:: No:: contract A { - + // External functions // ... @@ -206,16 +206,16 @@ No:: // Public functions // ... - function A() { + function A() public { ... } - - function() { + + function() public { ... } // Internal functions - // ... + // ... } Whitespace in Expressions @@ -235,17 +235,17 @@ No:: Exception:: - function singleLine() { spam(); } + function singleLine() public { spam(); } Immediately before a comma, semicolon: Yes:: - function spam(uint i, Coin coin); + function spam(uint i, Coin coin) public; No:: - function spam(uint i , Coin coin) ; + function spam(uint i , Coin coin) public ; More than one space around an assignment or other operator to align with another: @@ -266,13 +266,13 @@ Don't include a whitespace in the fallback function: Yes:: - function() { + function() public { ... } No:: - - function () { + + function () public { ... } @@ -395,30 +395,30 @@ The opening brace should be preceeded by a single space. Yes:: - function increment(uint x) returns (uint) { + function increment(uint x) public pure returns (uint) { return x + 1; } - function increment(uint x) public onlyowner returns (uint) { + function increment(uint x) public pure onlyowner returns (uint) { return x + 1; } No:: - function increment(uint x) returns (uint) + function increment(uint x) public pure returns (uint) { return x + 1; } - function increment(uint x) returns (uint){ + function increment(uint x) public pure returns (uint){ return x + 1; } - function increment(uint x) returns (uint) { + function increment(uint x) public pure returns (uint) { return x + 1; } - function increment(uint x) returns (uint) { + function increment(uint x) public pure returns (uint) { return x + 1;} The visibility modifiers for a function should come before any custom @@ -450,14 +450,16 @@ Yes:: address d, address e, address f - ) { + ) + public + { doSomething(); } No:: function thisFunctionHasLotsOfArguments(address a, address b, address c, - address d, address e, address f) { + address d, address e, address f) public { doSomething(); } @@ -466,7 +468,7 @@ No:: address c, address d, address e, - address f) { + address f) public { doSomething(); } @@ -476,12 +478,12 @@ No:: address c, address d, address e, - address f) { + address f) public { doSomething(); } If a long function declaration has modifiers, then each modifier should be -dropped to it's own line. +dropped to its own line. Yes:: @@ -542,6 +544,7 @@ Yes:: B(param1) C(param2, param3) D(param4) + public { // do something with param5 } @@ -554,6 +557,7 @@ No:: B(param1) C(param2, param3) D(param4) + public { // do something with param5 } @@ -563,7 +567,8 @@ No:: function A(uint param1, uint param2, uint param3, uint param4, uint param5) B(param1) C(param2, param3) - D(param4) { + D(param4) + public { // do something with param5 } } @@ -572,7 +577,7 @@ When declaring short functions with a single statement, it is permissible to do Permissible:: - function shortFunction() { doSomething(); } + function shortFunction() public { doSomething(); } These guidelines for function declarations are intended to improve readability. Authors should use their best judgement as this guide does not try to cover all @@ -699,6 +704,12 @@ Contract and Library Names Contracts and libraries should be named using the CapWords style. Examples: ``SimpleToken``, ``SmartBank``, ``CertificateHashRepository``, ``Player``. +Struct Names +========================== + +Structs should be named using the CapWords style. Examples: ``MyCoin``, ``Position``, ``PositionXY``. + + Event Names =========== @@ -708,7 +719,7 @@ Events should be named using the CapWords style. Examples: ``Deposit``, ``Transf Function Names ============== -Functions should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``. +Functions other than constructors should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``. Function Argument Names @@ -739,6 +750,12 @@ Modifier Names Use mixedCase. Examples: ``onlyBy``, ``onlyAfter``, ``onlyDuringThePreSale``. +Enums +===== + +Enums, in the style of simple type declarations, should be named using the CapWords style. Examples: ``TokenGroup``, ``Frame``, ``HashStyle``, ``CharacterLocation``. + + Avoiding Naming Collisions ========================== diff --git a/docs/types.rst b/docs/types.rst index c716b95e..98009b3f 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -79,8 +79,8 @@ Fixed Point Numbers Fixed point numbers are not fully supported by Solidity yet. They can be declared, but cannot be assigned to or from. -``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represent the number of bits taken by -the type and ``N`` represent how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive. +``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represents the number of bits taken by +the type and ``N`` represents how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive. ``ufixed`` and ``fixed`` are aliases for ``ufixed128x19`` and ``fixed128x19``, respectively. Operators: @@ -331,14 +331,14 @@ check the value ranges at runtime and a failure causes an exception. Enums need :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } ActionChoices choice; ActionChoices constant defaultChoice = ActionChoices.GoStraight; - function setGoStraight() { + function setGoStraight() public { choice = ActionChoices.GoStraight; } @@ -347,11 +347,11 @@ check the value ranges at runtime and a failure causes an exception. Enums need // for all matters external to Solidity. The integer type used is just // large enough to hold all enum values, i.e. if you have more values, // `uint16` will be used and so on. - function getChoice() returns (ActionChoices) { + function getChoice() public view returns (ActionChoices) { return choice; } - function getDefaultChoice() returns (uint) { + function getDefaultChoice() public pure returns (uint) { return uint(defaultChoice); } } @@ -409,23 +409,24 @@ just use ``f``, if you want to use its external form, use ``this.f``. Additionally, public (or external) functions also have a special member called ``selector``, which returns the :ref:`ABI function selector <abi_function_selector>`:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract Selector { - function f() returns (bytes4) { + function f() public view returns (bytes4) { return this.f.selector; } } Example that shows how to use internal function types:: - pragma solidity ^0.4.5; + pragma solidity ^0.4.16; library ArrayUtils { // internal functions can be used in internal library functions because // they will be part of the same code context - function map(uint[] memory self, function (uint) returns (uint) f) + function map(uint[] memory self, function (uint) pure returns (uint) f) internal + pure returns (uint[] memory r) { r = new uint[](self.length); @@ -435,9 +436,10 @@ Example that shows how to use internal function types:: } function reduce( uint[] memory self, - function (uint, uint) returns (uint) f + function (uint, uint) pure returns (uint) f ) internal + pure returns (uint r) { r = self[0]; @@ -445,23 +447,23 @@ Example that shows how to use internal function types:: r = f(r, self[i]); } } - function range(uint length) internal returns (uint[] memory r) { + function range(uint length) internal pure returns (uint[] memory r) { r = new uint[](length); for (uint i = 0; i < r.length; i++) { r[i] = i; } } } - + contract Pyramid { using ArrayUtils for *; - function pyramid(uint l) returns (uint) { + function pyramid(uint l) public pure returns (uint) { return ArrayUtils.range(l).map(square).reduce(sum); } - function square(uint x) internal returns (uint) { + function square(uint x) internal pure returns (uint) { return x * x; } - function sum(uint x, uint y) internal returns (uint) { + function sum(uint x, uint y) internal pure returns (uint) { return x + y; } } @@ -477,11 +479,11 @@ Another example that uses external function types:: } Request[] requests; event NewRequest(uint); - function query(bytes data, function(bytes memory) external callback) { + function query(bytes data, function(bytes memory) external callback) public { requests.push(Request(data, callback)); NewRequest(requests.length - 1); } - function reply(uint requestID, bytes response) { + function reply(uint requestID, bytes response) public { // Here goes the check that the reply comes from a trusted source requests[requestID].callback(response); } @@ -492,7 +494,7 @@ Another example that uses external function types:: function buySomething() { oracle.query("USD", this.oracleResponse); } - function oracleResponse(bytes response) { + function oracleResponse(bytes response) public { require(msg.sender == address(oracle)); // Use the data } @@ -543,7 +545,7 @@ memory-stored reference type do not create a copy. uint[] x; // the data location of x is storage // the data location of memoryArray is memory - function f(uint[] memoryArray) { + function f(uint[] memoryArray) public { x = memoryArray; // works, copies the whole array to storage var y = x; // works, assigns a pointer, data location of y is storage y[7]; // fine, returns the 8th element @@ -560,7 +562,7 @@ memory-stored reference type do not create a copy. } function g(uint[] storage storageArray) internal {} - function h(uint[] memoryArray) {} + function h(uint[] memoryArray) public {} } Summary @@ -620,10 +622,10 @@ the ``.length`` member. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { - function f(uint len) { + function f(uint len) public pure { uint[] memory a = new uint[](7); bytes memory b = new bytes(len); // Here we have a.length == 7 and b.length == len @@ -641,13 +643,13 @@ assigned to a variable right away. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { - function f() { + function f() public pure { g([uint(1), 2, 3]); } - function g(uint[3] _data) { + function g(uint[3] _data) public pure { // ... } } @@ -667,7 +669,7 @@ possible: pragma solidity ^0.4.0; contract C { - function f() { + function f() public { // The next line creates a type error because uint[3] memory // cannot be converted to uint[] memory. uint[] x = [uint(1), 3, 4]; @@ -703,7 +705,7 @@ Members :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract ArrayContract { uint[2**20] m_aLotOfIntegers; @@ -712,23 +714,23 @@ Members bool[2][] m_pairsOfFlags; // newPairs is stored in memory - the default for function arguments - function setAllFlagPairs(bool[2][] newPairs) { + function setAllFlagPairs(bool[2][] newPairs) public { // assignment to a storage array replaces the complete array m_pairsOfFlags = newPairs; } - function setFlagPair(uint index, bool flagA, bool flagB) { + function setFlagPair(uint index, bool flagA, bool flagB) public { // access to a non-existing index will throw an exception m_pairsOfFlags[index][0] = flagA; m_pairsOfFlags[index][1] = flagB; } - function changeFlagArraySize(uint newSize) { + function changeFlagArraySize(uint newSize) public { // if the new size is smaller, removed array elements will be cleared m_pairsOfFlags.length = newSize; } - function clear() { + function clear() public { // these clear the arrays completely delete m_pairsOfFlags; delete m_aLotOfIntegers; @@ -738,20 +740,20 @@ Members bytes m_byteData; - function byteArrays(bytes data) { + function byteArrays(bytes data) public { // byte arrays ("bytes") are different as they are stored without padding, // but can be treated identical to "uint8[]" m_byteData = data; m_byteData.length += 7; - m_byteData[3] = 8; + m_byteData[3] = byte(8); delete m_byteData[2]; } - function addFlag(bool[2] flag) returns (uint) { + function addFlag(bool[2] flag) public returns (uint) { return m_pairsOfFlags.push(flag); } - function createMemoryArray(uint size) returns (bytes) { + function createMemoryArray(uint size) public pure returns (bytes) { // Dynamic memory arrays are created using `new`: uint[2][] memory arrayOfPairs = new uint[2][](size); // Create a dynamic byte array: @@ -795,13 +797,13 @@ shown in the following example: uint numCampaigns; mapping (uint => Campaign) campaigns; - function newCampaign(address beneficiary, uint goal) returns (uint campaignID) { + function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) { campaignID = numCampaigns++; // campaignID is return variable // Creates new struct and saves in storage. We leave out the mapping type. campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); } - function contribute(uint campaignID) payable { + function contribute(uint campaignID) public payable { Campaign storage c = campaigns[campaignID]; // Creates a new temporary memory struct, initialised with the given values // and copies it over to storage. @@ -810,7 +812,7 @@ shown in the following example: c.amount += msg.value; } - function checkGoalReached(uint campaignID) returns (bool reached) { + function checkGoalReached(uint campaignID) public returns (bool reached) { Campaign storage c = campaigns[campaignID]; if (c.amount < c.fundingGoal) return false; @@ -872,13 +874,13 @@ for each ``_KeyType``, recursively. contract MappingExample { mapping(address => uint) public balances; - function update(uint newBalance) { + function update(uint newBalance) public { balances[msg.sender] = newBalance; } } contract MappingUser { - function f() returns (uint) { + function f() public returns (uint) { MappingExample m = new MappingExample(); m.update(100); return m.balances(this); @@ -916,11 +918,11 @@ It is important to note that ``delete a`` really behaves like an assignment to ` uint data; uint[] dataArray; - function f() { + function f() public { uint x = data; delete x; // sets x to 0, does not affect data delete data; // sets data to 0, does not affect x which still holds a copy - uint[] y = dataArray; + uint[] storage y = dataArray; delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also // y is affected which is an alias to the storage object // On the other hand: "delete y" is not valid, as assignments to local variables diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 8261bdde..ce58cf56 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -34,7 +34,7 @@ library has to be updated by an external oracle. These suffixes cannot be applied to variables. If you want to interpret some input variable in e.g. days, you can do it in the following way:: - function f(uint start, uint daysAfter) { + function f(uint start, uint daysAfter) public { if (now >= start + daysAfter * 1 days) { // ... } diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 7f82df70..42cc807a 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -138,7 +138,7 @@ Input Description // ewasm.wasm - eWASM binary format (not supported atm) // // Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every - // target part of that output. + // target part of that output. Additionally, `*` can be used as a wildcard to request everything. // outputSelection: { // Enable the metadata and bytecode outputs of every single contract. @@ -147,11 +147,11 @@ Input Description }, // Enable the abi and opcodes output of MyContract defined in file def. "def": { - "MyContract": [ "abi", "evm.opcodes" ] + "MyContract": [ "abi", "evm.bytecode.opcodes" ] }, // Enable the source map output of every single contract. "*": { - "*": [ "evm.sourceMap" ] + "*": [ "evm.bytecode.sourceMap" ] }, // Enable the legacy AST output of every single file. "*": { @@ -177,7 +177,8 @@ Output Description start: 0, end: 100 ], - // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc + // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc. + // See below for complete list of types. type: "TypeError", // Mandatory: Component where the error originated, such as "general", "ewasm", etc. component: "general", @@ -273,3 +274,21 @@ Output Description } } } + + +Error types +~~~~~~~~~~~ + +1. ``JSONError``: JSON input doesn't conform to the required format, e.g. input is not a JSON object, the language is not supported, etc. +2. ``IOError``: IO and import processing errors, such as unresolvable URL or hash mismatch in supplied sources. +3. ``ParserError``: Source code doesn't conform to the language rules. +4. ``DocstringParsingError``: The NatSpec tags in the comment block cannot be parsed. +5. ``SyntaxError``: Syntactical error, such as ``continue`` is used outside of a ``for`` loop. +6. ``DeclarationError``: Invalid, unresolvable or clashing identifier names. e.g. ``Identifier not found`` +7. ``TypeError``: Error within the type system, such as invalid type conversions, invalid assignments, etc. +8. ``UnimplementedFeatureError``: Feature is not supported by the compiler, but is expected to be supported in future versions. +9. ``InternalCompilerError``: Internal bug triggered in the compiler - this should be reported as an issue. +10. ``Exception``: Unknown failure during compilation - this should be reported as an issue. +11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue. +12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue. +13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible. diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index e76a0949..b85abe95 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -183,6 +183,12 @@ template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U con _a.push_back(i); return _a; } +/// Concatenate the contents of a container onto a vector, move variant. +template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U&& _b) +{ + std::move(_b.begin(), _b.end(), std::back_inserter(_a)); + return _a; +} /// Concatenate the contents of a container onto a set template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U const& _b) { @@ -197,6 +203,17 @@ inline std::vector<T> operator+(std::vector<T> const& _a, std::vector<T> const& ret += _b; return ret; } +/// Concatenate two vectors of elements, moving them. +template <class T> +inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b) +{ + std::vector<T> ret(std::move(_a)); + if (&_a == &_b) + ret += ret; + else + ret += std::move(_b); + return ret; +} template <class T, class V> bool contains(T const& _t, V const& _v) diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 61a6ccda..4c2290c4 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -153,6 +153,31 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) } } +bool SemanticInformation::movable(Instruction _instruction) +{ + // These are not really functional. + if (isDupInstruction(_instruction) || isSwapInstruction(_instruction)) + return false; + InstructionInfo info = instructionInfo(_instruction); + if (info.sideEffects) + return false; + switch (_instruction) + { + case Instruction::KECCAK256: + case Instruction::BALANCE: + case Instruction::EXTCODESIZE: + case Instruction::RETURNDATASIZE: + case Instruction::SLOAD: + case Instruction::PC: + case Instruction::MSIZE: + case Instruction::GAS: + return false; + default: + return true; + } + return true; +} + bool SemanticInformation::invalidatesMemory(Instruction _instruction) { switch (_instruction) diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h index e5ea7c18..83656252 100644 --- a/libevmasm/SemanticInformation.h +++ b/libevmasm/SemanticInformation.h @@ -49,6 +49,10 @@ struct SemanticInformation /// @returns false if the value put on the stack by _item depends on anything else than /// the information in the current block header, memory, storage or stack. static bool isDeterministic(AssemblyItem const& _item); + /// @returns true if the instruction can be moved or copied (together with its arguments) + /// without altering the semantics. This means it cannot depend on storage or memory, + /// cannot have any side-effects, but it can depend on a call-constant state of the blockchain. + static bool movable(solidity::Instruction _instruction); /// @returns true if the given instruction modifies memory. static bool invalidatesMemory(solidity::Instruction _instruction); /// @returns true if the given instruction modifies storage (even indirectly). diff --git a/libjulia/ASTDataForward.h b/libjulia/ASTDataForward.h new file mode 100644 index 00000000..143b9c46 --- /dev/null +++ b/libjulia/ASTDataForward.h @@ -0,0 +1,54 @@ +/* + 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/>. +*/ +/** + * @date 2017 + * Pull in some identifiers from the solidity::assembly namespace. + */ + +#pragma once + +#include <libsolidity/inlineasm/AsmDataForward.h> + +namespace dev +{ +namespace julia +{ + +using Instruction = solidity::assembly::Instruction; +using Literal = solidity::assembly::Literal; +using Label = solidity::assembly::Label; +using StackAssignment = solidity::assembly::StackAssignment; +using Identifier = solidity::assembly::Identifier; +using Assignment = solidity::assembly::Assignment; +using VariableDeclaration = solidity::assembly::VariableDeclaration; +using FunctionalInstruction = solidity::assembly::FunctionalInstruction; +using FunctionDefinition = solidity::assembly::FunctionDefinition; +using FunctionCall = solidity::assembly::FunctionCall; +using If = solidity::assembly::If; +using Case = solidity::assembly::Case; +using Switch = solidity::assembly::Switch; +using ForLoop = solidity::assembly::ForLoop; +using ExpressionStatement = solidity::assembly::ExpressionStatement; +using Block = solidity::assembly::Block; + +using TypedName = solidity::assembly::TypedName; + +using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>; +using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; + +} +} diff --git a/libjulia/backends/evm/EVMAssembly.cpp b/libjulia/backends/evm/EVMAssembly.cpp index 1d499b20..07ad05c9 100644 --- a/libjulia/backends/evm/EVMAssembly.cpp +++ b/libjulia/backends/evm/EVMAssembly.cpp @@ -26,7 +26,7 @@ using namespace std; using namespace dev; -using namespace julia; +using namespace dev::julia; namespace { diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp index 13d9d011..0c7365fb 100644 --- a/libjulia/backends/evm/EVMCodeTransform.cpp +++ b/libjulia/backends/evm/EVMCodeTransform.cpp @@ -31,7 +31,8 @@ using namespace std; using namespace dev; using namespace dev::julia; using namespace dev::solidity; -using namespace dev::solidity::assembly; + +using Scope = dev::solidity::assembly::Scope; void CodeTransform::operator()(VariableDeclaration const& _varDecl) { @@ -76,6 +77,13 @@ void CodeTransform::operator()(StackAssignment const& _assignment) checkStackHeight(&_assignment); } +void CodeTransform::operator()(ExpressionStatement const& _statement) +{ + m_assembly.setSourceLocation(_statement.location); + boost::apply_visitor(*this, _statement.expression); + checkStackHeight(&_statement); +} + void CodeTransform::operator()(Label const& _label) { m_assembly.setSourceLocation(_label.location); @@ -124,11 +132,11 @@ void CodeTransform::operator()(FunctionCall const& _call) void CodeTransform::operator()(FunctionalInstruction const& _instruction) { if (m_evm15 && ( - _instruction.instruction.instruction == solidity::Instruction::JUMP || - _instruction.instruction.instruction == solidity::Instruction::JUMPI + _instruction.instruction == solidity::Instruction::JUMP || + _instruction.instruction == solidity::Instruction::JUMPI )) { - bool const isJumpI = _instruction.instruction.instruction == solidity::Instruction::JUMPI; + bool const isJumpI = _instruction.instruction == solidity::Instruction::JUMPI; if (isJumpI) { solAssert(_instruction.arguments.size() == 2, ""); @@ -149,7 +157,8 @@ void CodeTransform::operator()(FunctionalInstruction const& _instruction) { for (auto const& arg: _instruction.arguments | boost::adaptors::reversed) visitExpression(arg); - (*this)(_instruction.instruction); + m_assembly.setSourceLocation(_instruction.location); + m_assembly.appendInstruction(_instruction.instruction); } checkStackHeight(&_instruction); } @@ -289,7 +298,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) solAssert(m_info.scopes.at(&_function.body), ""); Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); solAssert(varScope, ""); - for (auto const& v: _function.arguments | boost::adaptors::reversed) + for (auto const& v: _function.parameters | boost::adaptors::reversed) { auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name)); m_context->variableStackHeights[&var] = height++; @@ -302,7 +311,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) if (m_evm15) { m_assembly.appendJumpTo(afterFunction, -stackHeightBefore); - m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.arguments.size()); + m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size()); } else { @@ -311,7 +320,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) } m_stackAdjustment += localStackAdjustment; - for (auto const& v: _function.returns) + for (auto const& v: _function.returnVariables) { auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name)); m_context->variableStackHeights[&var] = height++; @@ -341,9 +350,9 @@ void CodeTransform::operator()(FunctionDefinition const& _function) // modified parallel to the actual stack. vector<int> stackLayout; if (!m_evm15) - stackLayout.push_back(_function.returns.size()); // Move return label to the top - stackLayout += vector<int>(_function.arguments.size(), -1); // discard all arguments - for (size_t i = 0; i < _function.returns.size(); ++i) + stackLayout.push_back(_function.returnVariables.size()); // Move return label to the top + stackLayout += vector<int>(_function.parameters.size(), -1); // discard all arguments + for (size_t i = 0; i < _function.returnVariables.size(); ++i) stackLayout.push_back(i); // Move return values down, but keep order. solAssert(stackLayout.size() <= 17, "Stack too deep"); @@ -363,9 +372,9 @@ void CodeTransform::operator()(FunctionDefinition const& _function) } if (m_evm15) - m_assembly.appendReturnsub(_function.returns.size(), stackHeightBefore); + m_assembly.appendReturnsub(_function.returnVariables.size(), stackHeightBefore); else - m_assembly.appendJump(stackHeightBefore - _function.returns.size()); + m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size()); m_stackAdjustment -= localStackAdjustment; m_assembly.appendLabel(afterFunction); checkStackHeight(&_function); @@ -458,7 +467,7 @@ AbstractAssembly::LabelID CodeTransform::functionEntryID(string const& _name, Sc return m_context->functionEntryIDs[&_function]; } -void CodeTransform::visitExpression(Statement const& _expression) +void CodeTransform::visitExpression(Expression const& _expression) { int height = m_assembly.stackHeight(); boost::apply_visitor(*this, _expression); diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h index 387720a2..0f2aaf95 100644 --- a/libjulia/backends/evm/EVMCodeTransform.h +++ b/libjulia/backends/evm/EVMCodeTransform.h @@ -20,8 +20,9 @@ #include <libjulia/backends/evm/EVMAssembly.h> +#include <libjulia/ASTDataForward.h> + #include <libsolidity/inlineasm/AsmScope.h> -#include <libsolidity/inlineasm/AsmDataForward.h> #include <boost/variant.hpp> #include <boost/optional.hpp> @@ -95,38 +96,39 @@ protected: {} public: - void operator()(solidity::assembly::Instruction const& _instruction); - void operator()(solidity::assembly::Literal const& _literal); - void operator()(solidity::assembly::Identifier const& _identifier); - void operator()(solidity::assembly::FunctionalInstruction const& _instr); - void operator()(solidity::assembly::FunctionCall const&); - void operator()(solidity::assembly::Label const& _label); - void operator()(solidity::assembly::StackAssignment const& _assignment); - void operator()(solidity::assembly::Assignment const& _assignment); - void operator()(solidity::assembly::VariableDeclaration const& _varDecl); - void operator()(solidity::assembly::If const& _if); - void operator()(solidity::assembly::Switch const& _switch); - void operator()(solidity::assembly::FunctionDefinition const&); - void operator()(solidity::assembly::ForLoop const&); - void operator()(solidity::assembly::Block const& _block); + void operator()(Instruction const& _instruction); + void operator()(Literal const& _literal); + void operator()(Identifier const& _identifier); + void operator()(FunctionalInstruction const& _instr); + void operator()(FunctionCall const&); + void operator()(ExpressionStatement const& _statement); + void operator()(Label const& _label); + void operator()(StackAssignment const& _assignment); + void operator()(Assignment const& _assignment); + void operator()(VariableDeclaration const& _varDecl); + void operator()(If const& _if); + void operator()(Switch const& _switch); + void operator()(FunctionDefinition const&); + void operator()(ForLoop const&); + void operator()(Block const& _block); private: - AbstractAssembly::LabelID labelFromIdentifier(solidity::assembly::Identifier const& _identifier); + AbstractAssembly::LabelID labelFromIdentifier(Identifier const& _identifier); /// @returns the label ID corresponding to the given label, allocating a new one if /// necessary. AbstractAssembly::LabelID labelID(solidity::assembly::Scope::Label const& _label); AbstractAssembly::LabelID functionEntryID(std::string const& _name, solidity::assembly::Scope::Function const& _function); /// Generates code for an expression that is supposed to return a single value. - void visitExpression(solidity::assembly::Statement const& _expression); + void visitExpression(Expression const& _expression); - void visitStatements(std::vector<solidity::assembly::Statement> const& _statements); + void visitStatements(std::vector<Statement> const& _statements); /// Pops all variables declared in the block and checks that the stack height is equal /// to @a _blackStartStackHeight. - void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight); + void finalizeBlock(Block const& _block, int _blockStartStackHeight); - void generateMultiAssignment(std::vector<solidity::assembly::Identifier> const& _variableNames); - void generateAssignment(solidity::assembly::Identifier const& _variableName); + void generateMultiAssignment(std::vector<Identifier> const& _variableNames); + void generateAssignment(Identifier const& _variableName); /// Determines the stack height difference to the given variables. Throws /// if it is not yet in scope or the height difference is too large. Returns diff --git a/libjulia/optimiser/ASTCopier.cpp b/libjulia/optimiser/ASTCopier.cpp new file mode 100644 index 00000000..5c47be64 --- /dev/null +++ b/libjulia/optimiser/ASTCopier.cpp @@ -0,0 +1,185 @@ +/* + 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/>. +*/ +/** + * Creates an independent copy of an AST, renaming identifiers to be unique. + */ + +#include <libjulia/optimiser/ASTCopier.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <libdevcore/Common.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; + + +Statement ASTCopier::operator()(Instruction const&) +{ + solAssert(false, "Invalid operation."); + return {}; +} + +Statement ASTCopier::operator()(ExpressionStatement const& _statement) +{ + return ExpressionStatement{ _statement.location, translate(_statement.expression) }; +} + +Statement ASTCopier::operator()(VariableDeclaration const& _varDecl) +{ + return VariableDeclaration{ + _varDecl.location, + translateVector(_varDecl.variables), + translate(_varDecl.value) + }; +} + +Statement ASTCopier::operator()(Assignment const& _assignment) +{ + return Assignment{ + _assignment.location, + translateVector(_assignment.variableNames), + translate(_assignment.value) + }; +} + +Statement ASTCopier::operator()(StackAssignment const&) +{ + solAssert(false, "Invalid operation."); + return {}; +} + +Statement ASTCopier::operator()(Label const&) +{ + solAssert(false, "Invalid operation."); + return {}; +} + +Expression ASTCopier::operator()(FunctionCall const& _call) +{ + return FunctionCall{ + _call.location, + translate(_call.functionName), + translateVector(_call.arguments) + }; +} + +Expression ASTCopier::operator()(FunctionalInstruction const& _instruction) +{ + return FunctionalInstruction{ + _instruction.location, + _instruction.instruction, + translateVector(_instruction.arguments) + }; +} + +Expression ASTCopier::operator()(Identifier const& _identifier) +{ + return Identifier{_identifier.location, translateIdentifier(_identifier.name)}; +} + +Expression ASTCopier::operator()(Literal const& _literal) +{ + return translate(_literal); +} + +Statement ASTCopier::operator()(If const& _if) +{ + return If{_if.location, translate(_if.condition), translate(_if.body)}; +} + +Statement ASTCopier::operator()(Switch const& _switch) +{ + return Switch{_switch.location, translate(_switch.expression), translateVector(_switch.cases)}; +} + +Statement ASTCopier::operator()(FunctionDefinition const& _function) +{ + string translatedName = translateIdentifier(_function.name); + + enterFunction(_function); + ScopeGuard g([&]() { this->leaveFunction(_function); }); + + return FunctionDefinition{ + _function.location, + move(translatedName), + translateVector(_function.parameters), + translateVector(_function.returnVariables), + translate(_function.body) + }; +} + +Statement ASTCopier::operator()(ForLoop const& _forLoop) +{ + enterScope(_forLoop.pre); + ScopeGuard g([&]() { this->leaveScope(_forLoop.pre); }); + + return ForLoop{ + _forLoop.location, + translate(_forLoop.pre), + translate(_forLoop.condition), + translate(_forLoop.post), + translate(_forLoop.body) + }; +} + +Statement ASTCopier::operator ()(Block const& _block) +{ + return translate(_block); +} + +Expression ASTCopier::translate(Expression const& _expression) +{ + return _expression.apply_visitor(static_cast<ExpressionCopier&>(*this)); +} + +Statement ASTCopier::translate(Statement const& _statement) +{ + return _statement.apply_visitor(static_cast<StatementCopier&>(*this)); +} + +Block ASTCopier::translate(Block const& _block) +{ + enterScope(_block); + ScopeGuard g([&]() { this->leaveScope(_block); }); + + return Block{_block.location, translateVector(_block.statements)}; +} + +Case ASTCopier::translate(Case const& _case) +{ + return Case{_case.location, translate(_case.value), translate(_case.body)}; +} + +Identifier ASTCopier::translate(Identifier const& _identifier) +{ + return Identifier{_identifier.location, translateIdentifier(_identifier.name)}; +} + +Literal ASTCopier::translate(Literal const& _literal) +{ + return _literal; +} + +TypedName ASTCopier::translate(TypedName const& _typedName) +{ + return TypedName{_typedName.location, translateIdentifier(_typedName.name), _typedName.type}; +} + diff --git a/libjulia/optimiser/ASTCopier.h b/libjulia/optimiser/ASTCopier.h new file mode 100644 index 00000000..36a1ced5 --- /dev/null +++ b/libjulia/optimiser/ASTCopier.h @@ -0,0 +1,121 @@ +/* + 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/>. +*/ +/** + * Creates an independent copy of an AST, renaming identifiers to be unique. + */ + +#pragma once + +#include <libjulia/ASTDataForward.h> + +#include <boost/variant.hpp> +#include <boost/optional.hpp> + +#include <vector> +#include <set> +#include <memory> + +namespace dev +{ +namespace julia +{ + +class ExpressionCopier: public boost::static_visitor<Expression> +{ +public: + virtual Expression operator()(Literal const& _literal) = 0; + virtual Expression operator()(Identifier const& _identifier) = 0; + virtual Expression operator()(FunctionalInstruction const& _instr) = 0; + virtual Expression operator()(FunctionCall const&) = 0; +}; + +class StatementCopier: public boost::static_visitor<Statement> +{ +public: + virtual Statement operator()(ExpressionStatement const& _statement) = 0; + virtual Statement operator()(Instruction const& _instruction) = 0; + virtual Statement operator()(Label const& _label) = 0; + virtual Statement operator()(StackAssignment const& _assignment) = 0; + virtual Statement operator()(Assignment const& _assignment) = 0; + virtual Statement operator()(VariableDeclaration const& _varDecl) = 0; + virtual Statement operator()(If const& _if) = 0; + virtual Statement operator()(Switch const& _switch) = 0; + virtual Statement operator()(FunctionDefinition const&) = 0; + virtual Statement operator()(ForLoop const&) = 0; + virtual Statement operator()(Block const& _block) = 0; +}; + +/** + * Creates a copy of a iulia AST potentially replacing identifier names. + * Base class to be extended. + */ +class ASTCopier: public ExpressionCopier, public StatementCopier +{ +public: + virtual Expression operator()(Literal const& _literal) override; + virtual Statement operator()(Instruction const& _instruction) override; + virtual Expression operator()(Identifier const& _identifier) override; + virtual Expression operator()(FunctionalInstruction const& _instr) override; + virtual Expression operator()(FunctionCall const&) override; + virtual Statement operator()(ExpressionStatement const& _statement) override; + virtual Statement operator()(Label const& _label) override; + virtual Statement operator()(StackAssignment const& _assignment) override; + virtual Statement operator()(Assignment const& _assignment) override; + virtual Statement operator()(VariableDeclaration const& _varDecl) override; + virtual Statement operator()(If const& _if) override; + virtual Statement operator()(Switch const& _switch) override; + virtual Statement operator()(FunctionDefinition const&) override; + virtual Statement operator()(ForLoop const&) override; + virtual Statement operator()(Block const& _block) override; + + virtual Expression translate(Expression const& _expression); + virtual Statement translate(Statement const& _statement); + +protected: + template <typename T> + std::vector<T> translateVector(std::vector<T> const& _values); + + template <typename T> + std::shared_ptr<T> translate(std::shared_ptr<T> const& _v) + { + return _v ? std::make_shared<T>(translate(*_v)) : nullptr; + } + Block translate(Block const& _block); + Case translate(Case const& _case); + Identifier translate(Identifier const& _identifier); + Literal translate(Literal const& _literal); + TypedName translate(TypedName const& _typedName); + + virtual void enterScope(Block const&) { } + virtual void leaveScope(Block const&) { } + virtual void enterFunction(FunctionDefinition const&) { } + virtual void leaveFunction(FunctionDefinition const&) { } + virtual std::string translateIdentifier(std::string const& _name) { return _name; } +}; + +template <typename T> +std::vector<T> ASTCopier::translateVector(std::vector<T> const& _values) +{ + std::vector<T> translated; + for (auto const& v: _values) + translated.emplace_back(translate(v)); + return translated; +} + + +} +} diff --git a/libjulia/optimiser/ASTWalker.cpp b/libjulia/optimiser/ASTWalker.cpp new file mode 100644 index 00000000..6386b29d --- /dev/null +++ b/libjulia/optimiser/ASTWalker.cpp @@ -0,0 +1,159 @@ +/* + 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/>. +*/ +/** + * Generic AST walker. + */ + +#include <libjulia/optimiser/ASTWalker.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <boost/range/adaptor/reversed.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + + +void ASTWalker::operator()(FunctionalInstruction const& _instr) +{ + walkVector(_instr.arguments | boost::adaptors::reversed); +} + +void ASTWalker::operator()(FunctionCall const& _funCall) +{ + walkVector(_funCall.arguments | boost::adaptors::reversed); +} + +void ASTWalker::operator()(ExpressionStatement const& _statement) +{ + visit(_statement.expression); +} + +void ASTWalker::operator()(Assignment const& _assignment) +{ + for (auto const& name: _assignment.variableNames) + (*this)(name); + visit(*_assignment.value); +} + +void ASTWalker::operator()(VariableDeclaration const& _varDecl) +{ + if (_varDecl.value) + visit(*_varDecl.value); +} + +void ASTWalker::operator()(If const& _if) +{ + visit(*_if.condition); + (*this)(_if.body); +} + +void ASTWalker::operator()(Switch const& _switch) +{ + visit(*_switch.expression); + for (auto const& _case: _switch.cases) + { + if (_case.value) + (*this)(*_case.value); + (*this)(_case.body); + } +} + +void ASTWalker::operator()(FunctionDefinition const& _fun) +{ + (*this)(_fun.body); +} + +void ASTWalker::operator()(ForLoop const& _for) +{ + (*this)(_for.pre); + visit(*_for.condition); + (*this)(_for.post); + (*this)(_for.body); +} + +void ASTWalker::operator()(Block const& _block) +{ + walkVector(_block.statements); +} + +void ASTModifier::operator()(FunctionalInstruction& _instr) +{ + walkVector(_instr.arguments | boost::adaptors::reversed); +} + +void ASTModifier::operator()(FunctionCall& _funCall) +{ + walkVector(_funCall.arguments | boost::adaptors::reversed); +} + +void ASTModifier::operator()(ExpressionStatement& _statement) +{ + visit(_statement.expression); +} + +void ASTModifier::operator()(Assignment& _assignment) +{ + for (auto& name: _assignment.variableNames) + (*this)(name); + visit(*_assignment.value); +} + +void ASTModifier::operator()(VariableDeclaration& _varDecl) +{ + if (_varDecl.value) + visit(*_varDecl.value); +} + +void ASTModifier::operator()(If& _if) +{ + visit(*_if.condition); + (*this)(_if.body); +} + +void ASTModifier::operator()(Switch& _switch) +{ + visit(*_switch.expression); + for (auto& _case: _switch.cases) + { + if (_case.value) + (*this)(*_case.value); + (*this)(_case.body); + } +} + +void ASTModifier::operator()(FunctionDefinition& _fun) +{ + (*this)(_fun.body); +} + +void ASTModifier::operator()(ForLoop& _for) +{ + (*this)(_for.pre); + visit(*_for.condition); + (*this)(_for.post); + (*this)(_for.body); +} + +void ASTModifier::operator()(Block& _block) +{ + walkVector(_block.statements); +} diff --git a/libjulia/optimiser/ASTWalker.h b/libjulia/optimiser/ASTWalker.h new file mode 100644 index 00000000..dbf8194b --- /dev/null +++ b/libjulia/optimiser/ASTWalker.h @@ -0,0 +1,120 @@ +/* + 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/>. +*/ +/** + * Generic AST walker. + */ + +#pragma once + +#include <libjulia/ASTDataForward.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <boost/variant.hpp> +#include <boost/optional.hpp> + +#include <vector> +#include <set> +#include <map> + +namespace dev +{ +namespace julia +{ + +/** + * Generic AST walker. + */ +class ASTWalker: public boost::static_visitor<> +{ +public: + virtual void operator()(Literal const&) {} + virtual void operator()(Instruction const&) { solAssert(false, ""); } + virtual void operator()(Identifier const&) {} + virtual void operator()(FunctionalInstruction const& _instr); + virtual void operator()(FunctionCall const& _funCall); + virtual void operator()(ExpressionStatement const& _statement); + virtual void operator()(Label const&) { solAssert(false, ""); } + virtual void operator()(StackAssignment const&) { solAssert(false, ""); } + virtual void operator()(Assignment const& _assignment); + virtual void operator()(VariableDeclaration const& _varDecl); + virtual void operator()(If const& _if); + virtual void operator()(Switch const& _switch); + virtual void operator()(FunctionDefinition const&); + virtual void operator()(ForLoop const&); + virtual void operator()(Block const& _block); + + virtual void visit(Statement const& _st) + { + boost::apply_visitor(*this, _st); + } + virtual void visit(Expression const& _e) + { + boost::apply_visitor(*this, _e); + } + +protected: + template <class T> + void walkVector(T const& _statements) + { + for (auto const& st: _statements) + visit(st); + } +}; + +/** + * Generic AST modifier (i.e. non-const version of ASTWalker). + */ +class ASTModifier: public boost::static_visitor<> +{ +public: + virtual void operator()(Literal&) {} + virtual void operator()(Instruction&) { solAssert(false, ""); } + virtual void operator()(Identifier&) {} + virtual void operator()(FunctionalInstruction& _instr); + virtual void operator()(FunctionCall& _funCall); + virtual void operator()(ExpressionStatement& _statement); + virtual void operator()(Label&) { solAssert(false, ""); } + virtual void operator()(StackAssignment&) { solAssert(false, ""); } + virtual void operator()(Assignment& _assignment); + virtual void operator()(VariableDeclaration& _varDecl); + virtual void operator()(If& _if); + virtual void operator()(Switch& _switch); + virtual void operator()(FunctionDefinition&); + virtual void operator()(ForLoop&); + virtual void operator()(Block& _block); + + virtual void visit(Statement& _st) + { + boost::apply_visitor(*this, _st); + } + virtual void visit(Expression& _e) + { + boost::apply_visitor(*this, _e); + } + +protected: + template <class T> + void walkVector(T&& _statements) + { + for (auto& st: _statements) + visit(st); + } +}; + +} +} diff --git a/libjulia/optimiser/Disambiguator.cpp b/libjulia/optimiser/Disambiguator.cpp new file mode 100644 index 00000000..df2984e5 --- /dev/null +++ b/libjulia/optimiser/Disambiguator.cpp @@ -0,0 +1,85 @@ +/* + 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/>. +*/ +/** + * Optimiser component that makes all identifiers unique. + */ + +#include <libjulia/optimiser/Disambiguator.h> + +#include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/inlineasm/AsmScope.h> + +#include <libsolidity/interface/Exceptions.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + +using Scope = dev::solidity::assembly::Scope; + +string Disambiguator::translateIdentifier(string const& _originalName) +{ + solAssert(!m_scopes.empty() && m_scopes.back(), ""); + Scope::Identifier const* id = m_scopes.back()->lookup(_originalName); + solAssert(id, ""); + if (!m_translations.count(id)) + { + string translated = _originalName; + size_t suffix = 0; + while (m_usedNames.count(translated)) + { + suffix++; + translated = _originalName + "_" + std::to_string(suffix); + } + m_usedNames.insert(translated); + m_translations[id] = translated; + } + return m_translations.at(id); +} + +void Disambiguator::enterScope(Block const& _block) +{ + enterScopeInternal(*m_info.scopes.at(&_block)); +} + +void Disambiguator::leaveScope(Block const& _block) +{ + leaveScopeInternal(*m_info.scopes.at(&_block)); +} + +void Disambiguator::enterFunction(FunctionDefinition const& _function) +{ + enterScopeInternal(*m_info.scopes.at(m_info.virtualBlocks.at(&_function).get())); +} + +void Disambiguator::leaveFunction(FunctionDefinition const& _function) +{ + leaveScopeInternal(*m_info.scopes.at(m_info.virtualBlocks.at(&_function).get())); +} + +void Disambiguator::enterScopeInternal(Scope& _scope) +{ + m_scopes.push_back(&_scope); +} + +void Disambiguator::leaveScopeInternal(Scope& _scope) +{ + solAssert(!m_scopes.empty(), ""); + solAssert(m_scopes.back() == &_scope, ""); + m_scopes.pop_back(); +} diff --git a/libjulia/optimiser/Disambiguator.h b/libjulia/optimiser/Disambiguator.h new file mode 100644 index 00000000..18ffd157 --- /dev/null +++ b/libjulia/optimiser/Disambiguator.h @@ -0,0 +1,67 @@ +/* + 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/>. +*/ +/** + * Optimiser component that makes all identifiers unique. + */ + +#pragma once + +#include <libjulia/ASTDataForward.h> + +#include <libjulia/optimiser/ASTCopier.h> + +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> + +#include <boost/variant.hpp> +#include <boost/optional.hpp> + +#include <set> + +namespace dev +{ +namespace julia +{ + +/** + * Creates a copy of a iulia AST replacing all identifiers by unique names. + */ +class Disambiguator: public ASTCopier +{ +public: + Disambiguator(solidity::assembly::AsmAnalysisInfo const& _analysisInfo): + m_info(_analysisInfo) + {} + +protected: + virtual void enterScope(Block const& _block) override; + virtual void leaveScope(Block const& _block) override; + virtual void enterFunction(FunctionDefinition const& _function) override; + virtual void leaveFunction(FunctionDefinition const& _function) override; + virtual std::string translateIdentifier(std::string const& _name) override; + + void enterScopeInternal(solidity::assembly::Scope& _scope); + void leaveScopeInternal(solidity::assembly::Scope& _scope); + + solidity::assembly::AsmAnalysisInfo const& m_info; + + std::vector<solidity::assembly::Scope*> m_scopes; + std::map<void const*, std::string> m_translations; + std::set<std::string> m_usedNames; +}; + +} +} diff --git a/libjulia/optimiser/FunctionGrouper.cpp b/libjulia/optimiser/FunctionGrouper.cpp new file mode 100644 index 00000000..cc40bc46 --- /dev/null +++ b/libjulia/optimiser/FunctionGrouper.cpp @@ -0,0 +1,49 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Optimiser component that changes the code of a block so that all non-function definition + * statements are moved to a block of their own followed by all function definitions. + */ + +#include <libjulia/optimiser/FunctionGrouper.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <boost/range/algorithm_ext/erase.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + + +void FunctionGrouper::operator()(Block& _block) +{ + vector<Statement> reordered; + reordered.emplace_back(Block{_block.location, {}}); + + for (auto&& statement: _block.statements) + { + if (statement.type() == typeid(FunctionDefinition)) + reordered.emplace_back(std::move(statement)); + else + boost::get<Block>(reordered.front()).statements.emplace_back(std::move(statement)); + } + _block.statements = std::move(reordered); +} diff --git a/libjulia/optimiser/FunctionGrouper.h b/libjulia/optimiser/FunctionGrouper.h new file mode 100644 index 00000000..64a71318 --- /dev/null +++ b/libjulia/optimiser/FunctionGrouper.h @@ -0,0 +1,46 @@ +/* + 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/>. +*/ +/** + * Optimiser component that changes the code of a black so that all non-function definition + * instructions are moved to a block of their own followed by all function definitions. + */ + +#pragma once + +#include <libjulia/ASTDataForward.h> + +namespace dev +{ +namespace julia +{ + +/** + * Moves all instructions in a block into a new block at the start of the block, followed by + * all function definitions. + * + * After this step, a block is of the form + * { { I...} F... } + * Where I are (non-function-definition) instructions and F are function definitions. + */ +class FunctionGrouper +{ +public: + void operator()(Block& _block); +}; + +} +} diff --git a/libjulia/optimiser/FunctionHoister.cpp b/libjulia/optimiser/FunctionHoister.cpp new file mode 100644 index 00000000..9cf67f23 --- /dev/null +++ b/libjulia/optimiser/FunctionHoister.cpp @@ -0,0 +1,59 @@ +/* + 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/>. +*/ +/** + * Optimiser component that changes the code so that it consists of a block starting with + * a single block followed only by function definitions and with no functions defined + * anywhere else. + */ + +#include <libjulia/optimiser/FunctionHoister.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <libdevcore/CommonData.h> + +#include <boost/range/algorithm_ext/erase.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + + +void FunctionHoister::operator()(Block& _block) +{ + bool topLevel = m_isTopLevel; + m_isTopLevel = false; + for (auto&& statement: _block.statements) + { + boost::apply_visitor(*this, statement); + if (statement.type() == typeid(FunctionDefinition)) + { + m_functions.emplace_back(std::move(statement)); + statement = Block{_block.location, {}}; + } + } + auto isEmptyBlock = [](Statement const& _st) -> bool { + return _st.type() == typeid(Block) && boost::get<Block>(_st).statements.empty(); + }; + // Remove empty blocks + boost::range::remove_erase_if(_block.statements, isEmptyBlock); + if (topLevel) + _block.statements += std::move(m_functions); +} diff --git a/libjulia/optimiser/FunctionHoister.h b/libjulia/optimiser/FunctionHoister.h new file mode 100644 index 00000000..f9f8bce0 --- /dev/null +++ b/libjulia/optimiser/FunctionHoister.h @@ -0,0 +1,52 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Optimiser component that changes the code so that all function definitions are at the top + * level block. + */ + +#pragma once + +#include <libjulia/ASTDataForward.h> + +#include <libjulia/optimiser/ASTWalker.h> + +namespace dev +{ +namespace julia +{ + +/** + * Moves all functions to the top-level scope. + * Applying this transformation to source code that has ambiguous identifiers might + * lead to invalid code. + * + * Prerequisites: Disambiguator + */ +class FunctionHoister: public ASTModifier +{ +public: + using ASTModifier::operator(); + virtual void operator()(Block& _block); + +private: + bool m_isTopLevel = true; + std::vector<Statement> m_functions; +}; + +} +} diff --git a/libjulia/optimiser/NameCollector.cpp b/libjulia/optimiser/NameCollector.cpp new file mode 100644 index 00000000..7b4c4793 --- /dev/null +++ b/libjulia/optimiser/NameCollector.cpp @@ -0,0 +1,44 @@ +/* + 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/>. +*/ +/** + * Specific AST walker that collects all defined names. + */ + +#include <libjulia/optimiser/NameCollector.h> + +#include <libsolidity/inlineasm/AsmData.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; + +void NameCollector::operator()(VariableDeclaration const& _varDecl) +{ + for (auto const& var: _varDecl.variables) + m_names.insert(var.name); +} + +void NameCollector::operator ()(FunctionDefinition const& _funDef) +{ + m_names.insert(_funDef.name); + m_functions[_funDef.name] = &_funDef; + for (auto const arg: _funDef.parameters) + m_names.insert(arg.name); + for (auto const ret: _funDef.returnVariables) + m_names.insert(ret.name); + ASTWalker::operator ()(_funDef); +} diff --git a/libjulia/optimiser/NameCollector.h b/libjulia/optimiser/NameCollector.h new file mode 100644 index 00000000..b7e38f46 --- /dev/null +++ b/libjulia/optimiser/NameCollector.h @@ -0,0 +1,52 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Specific AST walker that collects all defined names. + */ + +#pragma once + +#include <libjulia/optimiser/ASTWalker.h> + +#include <string> +#include <map> +#include <set> + +namespace dev +{ +namespace julia +{ + +/** + * Specific AST walker that collects all defined names. + */ +class NameCollector: public ASTWalker +{ +public: + using ASTWalker::operator (); + virtual void operator()(VariableDeclaration const& _varDecl) override; + virtual void operator()(FunctionDefinition const& _funDef) override; + + std::set<std::string> const& names() const { return m_names; } + std::map<std::string, FunctionDefinition const*> const& functions() const { return m_functions; } +private: + std::set<std::string> m_names; + std::map<std::string, FunctionDefinition const*> m_functions; +}; + +} +} diff --git a/libjulia/optimiser/README.md b/libjulia/optimiser/README.md new file mode 100644 index 00000000..771cb707 --- /dev/null +++ b/libjulia/optimiser/README.md @@ -0,0 +1,61 @@ +## IULIA Optimiser + +The iulia optimiser consists of several stages and components that all transform +the AST in a semantically equivalent way. The goal is to end up either with code +that is shorter or at least only marginally longer but will allow further +optimisation steps. + +The optimiser currently follows a purely greedy strategy and does not do any +backtracking. + +## Disambiguator + +The disambiguator takes an AST and returns a fresh copy where all identifiers have +names unique to the input AST. This is a prerequisite for all other optimiser stages. +One of the benefits is that identifier lookup does not need to take scopes into account +and we can basically ignore the result of the analysis phase. + +All subsequent stages have the property that all names stay unique. This means if +a new identifier needs to be introduced, a new unique name is generated. + +## Function Hoister + +The function hoister moves all function definitions to the topmost block. This is +a semantically equivalent transformation as long as it is performed after the +disambiguation stage. The reason is that moving a definition upwards cannot decrease +its visibility and it is impossible to reference variables defined in a different function. + +The benefit of this stage is that function definitions can be lookup up more easily. + +## Function Grouper + +The function grouper has to be applied after the disambiguator and the function hoister. +Its effect is that all topmost elements that are not function definitions are moved +into a single block which is the first satement of the root block. + +After this step, a program has the following normal form: + + { I F... } + +Where I is a block that does not contain any function definitions (not even recursively) +and F is a list of function definitions such that no function contains a function definition. + +## Functional Inliner + +The functional inliner depends on the disambiguator, the function hoister and function grouper. +It performs function inlining such that the result of the inlining is an expression. This can +only be done if the body of the function to be inlined has the form ``{ r := E }`` where ``r`` +is the single return value of the function, ``E`` is an expression and all arguments in the +function call are so-called movable expressions. A movable expression is either a literal, a +variable or a function call (or EVM opcode) which does not have side-effects and also does not +depend on any side-effects. + +As an example, neither ``mload`` nor ``mstore`` would be allowed. + +## Full Function Inliner + +## Variable Eliminator + +## Unused Declaration Pruner + +## Function Unifier diff --git a/libjulia/optimiser/Semantics.cpp b/libjulia/optimiser/Semantics.cpp new file mode 100644 index 00000000..92728c46 --- /dev/null +++ b/libjulia/optimiser/Semantics.cpp @@ -0,0 +1,60 @@ +/*( + 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/>. +*/ +/** + * Specific AST walkers that collect semantical facts. + */ + +#include <libjulia/optimiser/Semantics.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libevmasm/SemanticInformation.h> + +#include <libdevcore/CommonData.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; + +MovableChecker::MovableChecker(Expression const& _expression) +{ + visit(_expression); +} + +void MovableChecker::operator()(Identifier const& _identifier) +{ + ASTWalker::operator()(_identifier); + m_variableReferences.insert(_identifier.name); +} + +void MovableChecker::operator()(FunctionalInstruction const& _instr) +{ + if (!eth::SemanticInformation::movable(_instr.instruction)) + m_movable = false; + else + ASTWalker::operator()(_instr); +} + +void MovableChecker::operator()(FunctionCall const&) +{ + m_movable = false; +} + +void MovableChecker::visit(Statement const&) +{ + solAssert(false, "Movability for statement requested."); +} diff --git a/libjulia/optimiser/Semantics.h b/libjulia/optimiser/Semantics.h new file mode 100644 index 00000000..6df5f01a --- /dev/null +++ b/libjulia/optimiser/Semantics.h @@ -0,0 +1,62 @@ +/* + 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/>. +*/ +/** + * Specific AST walkers that collect semantical facts. + */ + +#pragma once + +#include <libjulia/optimiser/ASTWalker.h> + +#include <string> +#include <map> +#include <set> + +namespace dev +{ +namespace julia +{ + +/** + * Specific AST walker that determines whether an expression is movable. + */ +class MovableChecker: public ASTWalker +{ +public: + MovableChecker() = default; + explicit MovableChecker(Expression const& _expression); + + virtual void operator()(Identifier const& _identifier) override; + virtual void operator()(FunctionalInstruction const& _functionalInstruction) override; + virtual void operator()(FunctionCall const& _functionCall) override; + + /// Disallow visiting anything apart from Expressions (this throws). + virtual void visit(Statement const&) override; + using ASTWalker::visit; + + bool movable() const { return m_movable; } + std::set<std::string> const& referencedVariables() const { return m_variableReferences; } + +private: + /// Which variables the current expression references. + std::set<std::string> m_variableReferences; + /// Is the current expression movable or not. + bool m_movable = true; +}; + +} +} diff --git a/libjulia/optimiser/Substitution.cpp b/libjulia/optimiser/Substitution.cpp new file mode 100644 index 00000000..668b6cb6 --- /dev/null +++ b/libjulia/optimiser/Substitution.cpp @@ -0,0 +1,39 @@ +/* + 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/>. +*/ +/** + * Specific AST copier that replaces certain identifiers with expressions. + */ + +#include <libjulia/optimiser/Substitution.h> + +#include <libsolidity/inlineasm/AsmData.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; + +Expression Substitution::translate(Expression const& _expression) +{ + if (_expression.type() == typeid(Identifier)) + { + string const& name = boost::get<Identifier>(_expression).name; + if (m_substitutions.count(name)) + // No recursive substitution + return ASTCopier().translate(*m_substitutions.at(name)); + } + return ASTCopier::translate(_expression); +} diff --git a/libjulia/optimiser/Substitution.h b/libjulia/optimiser/Substitution.h new file mode 100644 index 00000000..313a08d7 --- /dev/null +++ b/libjulia/optimiser/Substitution.h @@ -0,0 +1,50 @@ +/* + 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/>. +*/ +/** + * Specific AST copier that replaces certain identifiers with expressions. + */ + +#pragma once + +#include <libjulia/optimiser/ASTCopier.h> + +#include <string> +#include <map> +#include <set> + +namespace dev +{ +namespace julia +{ + +/** + * Specific AST copier that replaces certain identifiers with expressions. + */ +class Substitution: public ASTCopier +{ +public: + Substitution(std::map<std::string, Expression const*> const& _substitutions): + m_substitutions(_substitutions) + {} + virtual Expression translate(Expression const& _expression) override; + +private: + std::map<std::string, Expression const*> const& m_substitutions; +}; + +} +} diff --git a/libsolc/CMakeLists.txt b/libsolc/CMakeLists.txt new file mode 100644 index 00000000..e67583dd --- /dev/null +++ b/libsolc/CMakeLists.txt @@ -0,0 +1,9 @@ +if (EMSCRIPTEN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_license\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\",\"_compileStandard\"]' -s RESERVED_FUNCTION_POINTERS=20") + add_executable(soljson libsolc.cpp) + target_link_libraries(soljson PRIVATE solidity) +else() + add_library(libsolc libsolc.cpp) + set_target_properties(libsolc PROPERTIES OUTPUT_NAME solc) + target_link_libraries(libsolc PRIVATE solidity) +endif() diff --git a/solc/jsonCompiler.cpp b/libsolc/libsolc.cpp index 7e797a62..3a6e1521 100644 --- a/solc/jsonCompiler.cpp +++ b/libsolc/libsolc.cpp @@ -20,7 +20,7 @@ * JSON interface for the solidity compiler to be used from Javascript. */ -#include <solc/jsonCompiler.h> +#include <libsolc/libsolc.h> #include <libdevcore/Common.h> #include <libdevcore/JSON.h> #include <libsolidity/interface/StandardCompiler.h> @@ -128,6 +128,11 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback input["settings"]["optimizer"]["enabled"] = _optimize; input["settings"]["optimizer"]["runs"] = 200; + // Enable all SourceUnit-level outputs. + input["settings"]["outputSelection"]["*"][""][0] = "*"; + // Enable all Contract-level outputs. + input["settings"]["outputSelection"]["*"]["*"][0] = "*"; + StandardCompiler compiler(wrapReadCallback(_readCallback)); Json::Value ret = compiler.compile(input); diff --git a/solc/jsonCompiler.h b/libsolc/libsolc.h index c392ce93..c392ce93 100644 --- a/solc/jsonCompiler.h +++ b/libsolc/libsolc.h diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 4d546e68..83f37f47 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -28,51 +28,42 @@ using namespace std; using namespace dev; using namespace dev::solidity; -/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(UnaryOperation const& _operation) { - TypePointer const& subType = _operation.subExpression().annotation().type; - if (!dynamic_cast<RationalNumberType const*>(subType.get())) - m_errorReporter.fatalTypeError(_operation.subExpression().location(), "Invalid constant expression."); - TypePointer t = subType->unaryOperatorResult(_operation.getOperator()); - _operation.annotation().type = t; + auto sub = type(_operation.subExpression()); + if (sub) + setType(_operation, sub->unaryOperatorResult(_operation.getOperator())); } -/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(BinaryOperation const& _operation) { - TypePointer const& leftType = _operation.leftExpression().annotation().type; - TypePointer const& rightType = _operation.rightExpression().annotation().type; - if (!dynamic_cast<RationalNumberType const*>(leftType.get())) - m_errorReporter.fatalTypeError(_operation.leftExpression().location(), "Invalid constant expression."); - if (!dynamic_cast<RationalNumberType const*>(rightType.get())) - m_errorReporter.fatalTypeError(_operation.rightExpression().location(), "Invalid constant expression."); - TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); - if (!commonType) + auto left = type(_operation.leftExpression()); + auto right = type(_operation.rightExpression()); + if (left && right) { - m_errorReporter.typeError( - _operation.location(), - "Operator " + - string(Token::toString(_operation.getOperator())) + - " not compatible with types " + - leftType->toString() + - " and " + - rightType->toString() + auto commonType = left->binaryOperatorResult(_operation.getOperator(), right); + if (!commonType) + m_errorReporter.fatalTypeError( + _operation.location(), + "Operator " + + string(Token::toString(_operation.getOperator())) + + " not compatible with types " + + left->toString() + + " and " + + right->toString() + ); + setType( + _operation, + Token::isCompareOp(_operation.getOperator()) ? + make_shared<BoolType>() : + commonType ); - commonType = leftType; } - _operation.annotation().commonType = commonType; - _operation.annotation().type = - Token::isCompareOp(_operation.getOperator()) ? - make_shared<BoolType>() : - commonType; } void ConstantEvaluator::endVisit(Literal const& _literal) { - _literal.annotation().type = Type::forLiteral(_literal); - if (!_literal.annotation().type) - m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); + setType(_literal, Type::forLiteral(_literal)); } void ConstantEvaluator::endVisit(Identifier const& _identifier) @@ -81,18 +72,34 @@ void ConstantEvaluator::endVisit(Identifier const& _identifier) if (!variableDeclaration) return; if (!variableDeclaration->isConstant()) - m_errorReporter.fatalTypeError(_identifier.location(), "Identifier must be declared constant."); + return; - ASTPointer<Expression> value = variableDeclaration->value(); + ASTPointer<Expression> const& value = variableDeclaration->value(); if (!value) - m_errorReporter.fatalTypeError(_identifier.location(), "Constant identifier declaration must have a constant value."); - - if (!value->annotation().type) + return; + else if (!m_types->count(value.get())) { if (m_depth > 32) m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted)."); - ConstantEvaluator e(*value, m_errorReporter, m_depth + 1); + ConstantEvaluator(m_errorReporter, m_depth + 1, m_types).evaluate(*value); } - _identifier.annotation().type = value->annotation().type; + setType(_identifier, type(*value)); +} + +void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type) +{ + if (_type && _type->category() == Type::Category::RationalNumber) + (*m_types)[&_node] = _type; +} + +TypePointer ConstantEvaluator::type(ASTNode const& _node) +{ + return (*m_types)[&_node]; +} + +TypePointer ConstantEvaluator::evaluate(Expression const& _expr) +{ + _expr.accept(*this); + return type(_expr); } diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h index 6725d610..77a357b6 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -38,22 +38,32 @@ class TypeChecker; class ConstantEvaluator: private ASTConstVisitor { public: - ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter, size_t _newDepth = 0): + ConstantEvaluator( + ErrorReporter& _errorReporter, + size_t _newDepth = 0, + std::shared_ptr<std::map<ASTNode const*, TypePointer>> _types = std::make_shared<std::map<ASTNode const*, TypePointer>>() + ): m_errorReporter(_errorReporter), - m_depth(_newDepth) + m_depth(_newDepth), + m_types(_types) { - _expr.accept(*this); } + TypePointer evaluate(Expression const& _expr); + private: virtual void endVisit(BinaryOperation const& _operation); virtual void endVisit(UnaryOperation const& _operation); virtual void endVisit(Literal const& _literal); virtual void endVisit(Identifier const& _identifier); + void setType(ASTNode const& _node, TypePointer const& _type); + TypePointer type(ASTNode const& _node); + ErrorReporter& m_errorReporter; /// Current recursion depth. - size_t m_depth; + size_t m_depth = 0; + std::shared_ptr<std::map<ASTNode const*, TypePointer>> m_types; }; } diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index f22c95cc..540ffaf5 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -146,11 +146,12 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array."); if (Expression const* length = _typeName.length()) { - if (!length->annotation().type) - ConstantEvaluator e(*length, m_errorReporter); - auto const* lengthType = dynamic_cast<RationalNumberType const*>(length->annotation().type.get()); + TypePointer lengthTypeGeneric = length->annotation().type; + if (!lengthTypeGeneric) + lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length); + RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric.get()); if (!lengthType || !lengthType->mobileType()) - fatalTypeError(length->location(), "Invalid array length, expected integer literal."); + fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression."); else if (lengthType->isFractional()) fatalTypeError(length->location(), "Array with fractional length specified."); else if (lengthType->isNegative()) @@ -206,7 +207,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // Will be re-generated later with correct information assembly::AsmAnalysisInfo analysisInfo; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations()); + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 73047e76..191f78e9 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -171,13 +171,7 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con ssl.append("Another declaration is here:", (*it)->location()); string msg = "More than one constructor defined."; - size_t occurrences = ssl.infos.size(); - if (occurrences > 32) - { - ssl.infos.resize(32); - msg += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences."; - } - + ssl.limitSize(msg); m_errorReporter.declarationError( functions[_contract.name()].front()->location(), ssl, @@ -219,12 +213,7 @@ void TypeChecker::findDuplicateDefinitions(map<string, vector<T>> const& _defini if (ssl.infos.size() > 0) { - size_t occurrences = ssl.infos.size(); - if (occurrences > 32) - { - ssl.infos.resize(32); - _message += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences."; - } + ssl.limitSize(_message); m_errorReporter.declarationError( overloads[i]->location(), @@ -570,6 +559,17 @@ bool TypeChecker::visit(FunctionDefinition const& _function) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); + if ( + _function.visibility() > FunctionDefinition::Visibility::Internal && + type(*var)->category() == Type::Category::Struct && + !type(*var)->dataStoredIn(DataLocation::Storage) && + !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) + ) + m_errorReporter.typeError( + var->location(), + "Structs are only supported in the new experimental ABI encoder. " + "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." + ); var->accept(*this); } @@ -873,7 +873,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) assembly::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, - false, + assembly::AsmFlavour::Loose, identifierAccess ); if (!analyzer.analyze(_inlineAssembly.operations())) @@ -1060,7 +1060,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) _statement.initialValue()->location(), "Invalid rational " + valueComponentType->toString() + - " (absolute value too large or divison by zero)." + " (absolute value too large or division by zero)." ); else solAssert(false, ""); @@ -1551,8 +1551,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size()) { + bool isStructConstructorCall = _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall; + string msg = - "Wrong argument count for function call: " + + "Wrong argument count for " + + string(isStructConstructorCall ? "struct constructor" : "function call") + + ": " + toString(arguments.size()) + " arguments given but expected " + toString(parameterTypes.size()) + @@ -1668,10 +1672,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) SecondarySourceLocation ssl; for (auto function: contract->annotation().unimplementedFunctions) ssl.append("Missing implementation:", function->location()); + string msg = "Trying to create an instance of an abstract contract."; + ssl.limitSize(msg); m_errorReporter.typeError( _newExpression.location(), ssl, - "Trying to create an instance of an abstract contract." + msg ); } if (!contract->constructorIsPublic()) diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 7e41fc16..6257ac6d 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -40,19 +40,20 @@ public: void operator()(assembly::Label const&) { } void operator()(assembly::Instruction const& _instruction) { - if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction)) - m_reportMutability(StateMutability::NonPayable, _instruction.location); - else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction)) - m_reportMutability(StateMutability::View, _instruction.location); + checkInstruction(_instruction.location, _instruction.instruction); } void operator()(assembly::Literal const&) {} void operator()(assembly::Identifier const&) {} void operator()(assembly::FunctionalInstruction const& _instr) { - (*this)(_instr.instruction); + checkInstruction(_instr.location, _instr.instruction); for (auto const& arg: _instr.arguments) boost::apply_visitor(*this, arg); } + void operator()(assembly::ExpressionStatement const& _expr) + { + boost::apply_visitor(*this, _expr.expression); + } void operator()(assembly::StackAssignment const&) {} void operator()(assembly::Assignment const& _assignment) { @@ -102,6 +103,13 @@ public: private: std::function<void(StateMutability, SourceLocation const&)> m_reportMutability; + void checkInstruction(SourceLocation _location, solidity::Instruction _instruction) + { + if (eth::SemanticInformation::invalidInViewFunctions(_instruction)) + m_reportMutability(StateMutability::NonPayable, _location); + else if (eth::SemanticInformation::invalidInPureFunctions(_instruction)) + m_reportMutability(StateMutability::View, _location); + } }; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 635279ab..a54e4e09 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -257,7 +257,7 @@ public: } virtual u256 literalValue(Literal const*) const { - solAssert(false, "Literal value requested for type without literals."); + solAssert(false, "Literal value requested for type without literals: " + toString(false)); } /// @returns a (simpler) type that is encoded in the same way for external function calls. diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index bb39cbbb..00f59065 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -22,9 +22,13 @@ #include <libsolidity/codegen/ABIFunctions.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/CompilerUtils.h> + #include <libdevcore/Whiskers.h> -#include <libsolidity/ast/AST.h> +#include <boost/algorithm/string/join.hpp> +#include <boost/range/adaptor/reversed.hpp> using namespace std; using namespace dev; @@ -99,6 +103,79 @@ string ABIFunctions::tupleEncoder( }); } +string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) +{ + string functionName = string("abi_decode_tuple_"); + for (auto const& t: _types) + functionName += t->identifier(); + if (_fromMemory) + functionName += "_fromMemory"; + + solAssert(!_types.empty(), ""); + + return createFunction(functionName, [&]() { + TypePointers decodingTypes; + for (auto const& t: _types) + decodingTypes.emplace_back(t->decodingType()); + + Whiskers templ(R"( + function <functionName>(headStart, dataEnd) -> <valueReturnParams> { + if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) } + <decodeElements> + } + )"); + templ("functionName", functionName); + templ("minimumSize", to_string(headSize(decodingTypes))); + + string decodeElements; + vector<string> valueReturnParams; + size_t headPos = 0; + size_t stackPos = 0; + for (size_t i = 0; i < _types.size(); ++i) + { + solAssert(_types[i], ""); + solAssert(decodingTypes[i], ""); + size_t sizeOnStack = _types[i]->sizeOnStack(); + solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), ""); + solAssert(sizeOnStack > 0, ""); + vector<string> valueNamesLocal; + for (size_t j = 0; j < sizeOnStack; j++) + { + valueNamesLocal.push_back("value" + to_string(stackPos)); + valueReturnParams.push_back("value" + to_string(stackPos)); + stackPos++; + } + bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); + Whiskers elementTempl( + dynamic ? + R"( + { + let offset := <load>(add(headStart, <pos>)) + if gt(offset, 0xffffffffffffffff) { revert(0, 0) } + <values> := <abiDecode>(add(headStart, offset), dataEnd) + } + )" : + R"( + { + let offset := <pos> + <values> := <abiDecode>(add(headStart, offset), dataEnd) + } + )" + ); + elementTempl("load", _fromMemory ? "mload" : "calldataload"); + elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); + elementTempl("pos", to_string(headPos)); + elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true)); + decodeElements += elementTempl.render(); + headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize(); + } + templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); + templ("decodeElements", decodeElements); + + return templ.render(); + }); +} + string ABIFunctions::requestedFunctions() { string result; @@ -141,10 +218,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) solUnimplemented("Fixed point types not implemented."); break; case Type::Category::Array: - solAssert(false, "Array cleanup requested."); - break; case Type::Category::Struct: - solAssert(false, "Struct cleanup requested."); + solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); + templ("body", "cleaned := value"); break; case Type::Category::FixedBytes: { @@ -367,6 +443,24 @@ string ABIFunctions::combineExternalFunctionIdFunction() }); } +string ABIFunctions::splitExternalFunctionIdFunction() +{ + string functionName = "split_external_function_id"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function <functionName>(combined) -> addr, selector { + combined := <shr64>(combined) + selector := and(combined, 0xffffffff) + addr := <shr32>(combined) + } + )") + ("functionName", functionName) + ("shr32", shiftRightFunction(32, false)) + ("shr64", shiftRightFunction(64, false)) + .render(); + }); +} + string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, @@ -963,6 +1057,307 @@ string ABIFunctions::abiEncodingFunctionFunctionType( }); } +string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) +{ + // The decoding function has to perform bounds checks unless it decodes a value type. + // Conversely, bounds checks have to be performed before the decoding function + // of a value type is called. + + TypePointer decodingType = _type.decodingType(); + solAssert(decodingType, ""); + + if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType.get())) + { + if (arrayType->dataStoredIn(DataLocation::CallData)) + { + solAssert(!_fromMemory, ""); + return abiDecodingFunctionCalldataArray(*arrayType); + } + else if (arrayType->isByteArray()) + return abiDecodingFunctionByteArray(*arrayType, _fromMemory); + else + return abiDecodingFunctionArray(*arrayType, _fromMemory); + } + else if (auto const* structType = dynamic_cast<StructType const*>(decodingType.get())) + return abiDecodingFunctionStruct(*structType, _fromMemory); + else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType.get())) + return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); + else + return abiDecodingFunctionValueType(_type, _fromMemory); +} + +string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory) +{ + TypePointer decodingType = _type.decodingType(); + solAssert(decodingType, ""); + solAssert(decodingType->sizeOnStack() == 1, ""); + solAssert(decodingType->isValueType(), ""); + solAssert(decodingType->calldataEncodedSize() == 32, ""); + solAssert(!decodingType->isDynamicallyEncoded(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + return createFunction(functionName, [&]() { + Whiskers templ(R"( + function <functionName>(offset, end) -> value { + value := <cleanup>(<load>(offset)) + } + )"); + templ("functionName", functionName); + templ("load", _fromMemory ? "mload" : "calldataload"); + // Cleanup itself should use the type and not decodingType, because e.g. + // the decoding type of an enum is a plain int. + templ("cleanup", cleanupFunction(_type, true)); + return templ.render(); + }); + +} + +string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + solAssert(!_type.isByteArray(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + solAssert(!_type.dataStoredIn(DataLocation::Storage), ""); + + return createFunction(functionName, [&]() { + string load = _fromMemory ? "mload" : "calldataload"; + bool dynamicBase = _type.baseType()->isDynamicallyEncoded(); + Whiskers templ( + R"( + // <readableTypeName> + function <functionName>(offset, end) -> array { + if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + let length := <retrieveLength> + array := <allocate>(<allocationSize>(length)) + let dst := array + <storeLength> // might update offset and dst + let src := offset + <staticBoundsCheck> + for { let i := 0 } lt(i, length) { i := add(i, 1) } + { + let elementPos := <retrieveElementPos> + mstore(dst, <decodingFun>(elementPos, end)) + dst := add(dst, 0x20) + src := add(src, <baseEncodedSize>) + } + } + )" + ); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); + templ("allocate", allocationFunction()); + templ("allocationSize", arrayAllocationSizeFunction(_type)); + if (_type.isDynamicallySized()) + templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); + else + templ("storeLength", ""); + if (dynamicBase) + { + templ("staticBoundsCheck", ""); + templ("retrieveElementPos", "add(offset, " + load + "(src))"); + templ("baseEncodedSize", "0x20"); + } + else + { + string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); + templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }"); + templ("retrieveElementPos", "src"); + templ("baseEncodedSize", baseEncodedSize); + } + templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) +{ + // This does not work with arrays of complex types - the array access + // is not yet implemented in Solidity. + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + if (!_type.isDynamicallySized()) + solAssert(_type.length() < u256("0xffffffffffffffff"), ""); + solAssert(!_type.baseType()->isDynamicallyEncoded(), ""); + solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); + + string functionName = + "abi_decode_" + + _type.identifier(); + return createFunction(functionName, [&]() { + string templ; + if (_type.isDynamicallySized()) + templ = R"( + // <readableTypeName> + function <functionName>(offset, end) -> arrayPos, length { + if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + length := calldataload(offset) + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + arrayPos := add(offset, 0x20) + if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) } + } + )"; + else + templ = R"( + // <readableTypeName> + function <functionName>(offset, end) -> arrayPos { + arrayPos := offset + if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) } + } + )"; + Whiskers w{templ}; + w("functionName", functionName); + w("readableTypeName", _type.toString(true)); + w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); + w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length())); + return w.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + solAssert(_type.isByteArray(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + return createFunction(functionName, [&]() { + Whiskers templ( + R"( + function <functionName>(offset, end) -> array { + if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + let length := <load>(offset) + array := <allocate>(<allocationSize>(length)) + mstore(array, length) + let src := add(offset, 0x20) + let dst := add(array, 0x20) + if gt(add(src, length), end) { revert(0, 0) } + <copyToMemFun>(src, dst, length) + } + )" + ); + templ("functionName", functionName); + templ("load", _fromMemory ? "mload" : "calldataload"); + templ("allocate", allocationFunction()); + templ("allocationSize", arrayAllocationSizeFunction(_type)); + templ("copyToMemFun", copyToMemoryFunction(!_fromMemory)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) +{ + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), ""); + + return createFunction(functionName, [&]() { + Whiskers templ(R"( + // <readableTypeName> + function <functionName>(headStart, end) -> value { + if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) } + value := <allocate>(<memorySize>) + <#members> + { + // <memberName> + <decode> + } + </members> + } + )"); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("allocate", allocationFunction()); + solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); + templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); + size_t headPos = 0; + vector<map<string, string>> members; + for (auto const& member: _type.members(nullptr)) + { + solAssert(member.type, ""); + solAssert(member.type->canLiveOutsideStorage(), ""); + auto decodingType = member.type->decodingType(); + solAssert(decodingType, ""); + bool dynamic = decodingType->isDynamicallyEncoded(); + Whiskers memberTempl( + dynamic ? + R"( + let offset := <load>(add(headStart, <pos>)) + if gt(offset, 0xffffffffffffffff) { revert(0, 0) } + mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end)) + )" : + R"( + let offset := <pos> + mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end)) + )" + ); + memberTempl("load", _fromMemory ? "mload" : "calldataload"); + memberTempl("pos", to_string(headPos)); + memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); + memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false)); + + members.push_back({}); + members.back()["decode"] = memberTempl.render(); + members.back()["memberName"] = member.name; + headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize(); + } + templ("members", members); + templ("minimumSize", toCompactHexWithPrefix(headPos)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack) +{ + solAssert(_type.kind() == FunctionType::Kind::External, ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : "") + + (_forUseOnStack ? "_onStack" : ""); + + return createFunction(functionName, [&]() { + if (_forUseOnStack) + { + return Whiskers(R"( + function <functionName>(offset, end) -> addr, function_selector { + addr, function_selector := <splitExtFun>(<load>(offset)) + } + )") + ("functionName", functionName) + ("load", _fromMemory ? "mload" : "calldataload") + ("splitExtFun", splitExternalFunctionIdFunction()) + .render(); + } + else + { + return Whiskers(R"( + function <functionName>(offset, end) -> fun { + fun := <cleanExtFun>(<load>(offset)) + } + )") + ("functionName", functionName) + ("load", _fromMemory ? "mload" : "calldataload") + ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + .render(); + } + }); +} + string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) { string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; @@ -1098,6 +1493,33 @@ string ABIFunctions::arrayLengthFunction(ArrayType const& _type) }); } +string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + string functionName = "array_allocation_size_" + _type.identifier(); + return createFunction(functionName, [&]() { + Whiskers w(R"( + function <functionName>(length) -> size { + // Make sure we can allocate memory without overflow + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + size := <allocationSize> + <addLengthSlot> + } + )"); + w("functionName", functionName); + if (_type.isByteArray()) + // Round up + w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); + else + w("allocationSize", "mul(length, 0x20)"); + if (_type.isDynamicallySized()) + w("addLengthSlot", "size := add(size, 0x20)"); + else + w("addLengthSlot", ""); + return w.render(); + }); +} + string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) { string functionName = "array_dataslot_" + _type.identifier(); @@ -1189,6 +1611,25 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) }); } +string ABIFunctions::allocationFunction() +{ + string functionName = "allocateMemory"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function <functionName>(size) -> memPtr { + memPtr := mload(<freeMemoryPointer>) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(<freeMemoryPointer>, newFreePtr) + } + )") + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("functionName", functionName) + .render(); + }); +} + string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator) { if (!m_requestedFunctions.count(_name)) diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index e61f68bc..2b582e84 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -66,6 +66,16 @@ public: bool _encodeAsLibraryTypes = false ); + /// @returns name of an assembly function to ABI-decode values of @a _types + /// into memory. If @a _fromMemory is true, decodes from memory instead of + /// from calldata. + /// Can allocate memory. + /// Inputs: <source_offset> <source_end> (layout reversed on stack) + /// Outputs: <value0> <value1> ... <valuen> + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); + /// @returns concatenation of all generated functions. std::string requestedFunctions(); @@ -87,6 +97,10 @@ private: /// for use in the ABI. std::string combineExternalFunctionIdFunction(); + /// @returns a function that splits the address and selector from a single value + /// for use in the ABI. + std::string splitExternalFunctionIdFunction(); + /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. /// @param _fromStack if false, the input value was just loaded from storage @@ -146,6 +160,31 @@ private: bool _fromStack ); + /// @returns the name of the ABI decoding function for the given type + /// and queues the generation of the function to the requested functions. + /// The caller has to ensure that no out of bounds access (at least to the static + /// part) can happen inside this function. + /// @param _fromMemory if decoding from memory instead of from calldata + /// @param _forUseOnStack if the decoded value is stored on stack or in memory. + std::string abiDecodingFunction( + Type const& _Type, + bool _fromMemory, + bool _forUseOnStack + ); + + /// Part of @a abiDecodingFunction for value types. + std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for "regular" array types. + std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for calldata array types. + std::string abiDecodingFunctionCalldataArray(ArrayType const& _type); + /// Part of @a abiDecodingFunction for byte array types. + std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for struct types. + std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for array types. + std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); + /// @returns a function that copies raw bytes of dynamic length from calldata /// or memory to memory. /// Pads with zeros and might write more than exactly length. @@ -158,6 +197,10 @@ private: std::string roundUpFunction(); std::string arrayLengthFunction(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required + /// to store an array in memory given its length (internally encoded, not ABI encoded). + /// The function reverts for too large lengthes. + std::string arrayAllocationSizeFunction(ArrayType const& _type); /// @returns the name of a function that converts a storage slot number /// or a memory pointer to the slot number / memory pointer for the data position of an array /// which is stored in that slot / memory area. @@ -166,6 +209,12 @@ private: /// Only works for memory arrays and storage arrays that store one item per slot. std::string nextArrayElementFunction(ArrayType const& _type); + /// @returns the name of a function that allocates memory. + /// Modifies the "free memory pointer" + /// Arguments: size + /// Return value: pointer + std::string allocationFunction(); + /// Helper function that uses @a _creator to create a function and add it to /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both /// cases. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ce9c3b7f..7a88475a 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -319,14 +319,19 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--"); - auto parserResult = assembly::Parser(errorReporter).parse(scanner); + auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::Strict).parse(scanner); #ifdef SOL_OUTPUT_ASM cout << assembly::AsmPrinter()(*parserResult) << endl; #endif assembly::AsmAnalysisInfo analysisInfo; bool analyzerResult = false; if (parserResult) - analyzerResult = assembly::AsmAnalyzer(analysisInfo, errorReporter, false, identifierAccess.resolve).analyze(*parserResult); + analyzerResult = assembly::AsmAnalyzer( + analysisInfo, + errorReporter, + assembly::AsmFlavour::Strict, + identifierAccess.resolve + ).analyze(*parserResult); if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) { string message = @@ -347,6 +352,9 @@ void CompilerContext::appendInlineAssembly( solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system); + + // Reset the source location to the one of the node (instead of the CODEGEN source location) + updateSourceLocation(); } FunctionDefinition const& CompilerContext::resolveVirtualFunction( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 7743fd3f..0e8b639c 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -187,8 +187,8 @@ public: CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } - /// Appends inline assembly. @a _replacements are string-matching replacements that are performed - /// prior to parsing the inline assembly. + /// Appends inline assembly (strict mode). + /// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly. /// @param _localVariables assigns stack positions to variables with the last one being the stack top /// @param _system if true, this is a "system-level" assembly where all functions use named labels. void appendInlineAssembly( diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 053bed6a..533aca5c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -319,6 +319,23 @@ void CompilerUtils::abiEncodeV2( m_context << ret.tag(); } +void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory) +{ + // stack: <source_offset> + auto ret = m_context.pushNewTag(); + m_context << Instruction::SWAP1; + if (_fromMemory) + // TODO pass correct size for the memory case + m_context << (u256(1) << 63); + else + m_context << Instruction::CALLDATASIZE; + m_context << Instruction::SWAP1; + string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory); + m_context.appendJumpTo(m_context.namedTag(decoderName)); + m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3); + m_context << ret.tag(); +} + void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) { auto repeat = m_context.newTag(); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index ad3989ad..3cde281b 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -146,6 +146,13 @@ public: bool _encodeAsLibraryTypes = false ); + /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true, + /// the data is taken from memory instead of from calldata. + /// Can allocate memory. + /// Stack pre: <source_offset> + /// Stack post: <value0> <value1> ... <valuen> + void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false); + /// Zero-initialises (the data part of) an already allocated memory array. /// Length has to be nonzero! /// Stack pre: <length> <memptr> diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 74565ae4..a81ba518 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -322,6 +322,15 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // We do not check the calldata size, everything is zero-padded + if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + { + // Use the new JULIA-based decoding function + auto stackHeightBefore = m_context.stackHeight(); + CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory); + solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, ""); + return; + } + //@todo this does not yet support nested dynamic arrays // Retain the offset pointer as base_offset, the point from which the data offsets are computed. diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 1050621e..a64024b3 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -55,7 +55,7 @@ void SMTChecker::analyze(SourceUnit const& _source) void SMTChecker::endVisit(VariableDeclaration const& _varDecl) { if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) - assignment(_varDecl, *_varDecl.value()); + assignment(_varDecl, *_varDecl.value(), _varDecl.location()); } bool SMTChecker::visit(FunctionDefinition const& _function) @@ -71,6 +71,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) m_interface->reset(); m_currentSequenceCounter.clear(); m_nextFreeSequenceCounter.clear(); + m_pathConditions.clear(); m_conditionalExecutionHappened = false; initializeLocalVariables(_function); return true; @@ -90,15 +91,16 @@ bool SMTChecker::visit(IfStatement const& _node) checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE."); - visitBranch(_node.trueStatement(), expr(_node.condition())); + auto countersEndFalse = m_currentSequenceCounter; + auto countersEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition())); vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement()); if (_node.falseStatement()) { - visitBranch(*_node.falseStatement(), !expr(_node.condition())); + countersEndFalse = visitBranch(*_node.falseStatement(), !expr(_node.condition())); touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement()); } - resetVariables(touchedVariables); + mergeVariables(touchedVariables, expr(_node.condition()), countersEndTrue, countersEndFalse); return false; } @@ -178,8 +180,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) else if (knownVariable(*_varDecl.declarations()[0])) { if (_varDecl.initialValue()) - // TODO more checks? - assignment(*_varDecl.declarations()[0], *_varDecl.initialValue()); + assignment(*_varDecl.declarations()[0], *_varDecl.initialValue(), _varDecl.location()); } else m_errorReporter.warning( @@ -208,7 +209,10 @@ void SMTChecker::endVisit(Assignment const& _assignment) { Declaration const* decl = identifier->annotation().referencedDeclaration; if (knownVariable(*decl)) - assignment(*decl, _assignment.rightHandSide()); + { + assignment(*decl, _assignment.rightHandSide(), _assignment.location()); + defineExpr(_assignment, expr(_assignment.rightHandSide())); + } else m_errorReporter.warning( _assignment.location(), @@ -230,7 +234,81 @@ void SMTChecker::endVisit(TupleExpression const& _tuple) "Assertion checker does not yet implement tules and inline arrays." ); else - m_interface->addAssertion(expr(_tuple) == expr(*_tuple.components()[0])); + defineExpr(_tuple, expr(*_tuple.components()[0])); +} + +void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _type, SourceLocation const& _location) +{ + checkCondition( + _value < minValue(_type), + _location, + "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")", + "value", + &_value + ); + checkCondition( + _value > maxValue(_type), + _location, + "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")", + "value", + &_value + ); +} + +void SMTChecker::endVisit(UnaryOperation const& _op) +{ + switch (_op.getOperator()) + { + case Token::Not: // ! + { + solAssert(_op.annotation().type->category() == Type::Category::Bool, ""); + defineExpr(_op, !expr(_op.subExpression())); + break; + } + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) + { + solAssert(_op.annotation().type->category() == Type::Category::Integer, ""); + solAssert(_op.subExpression().annotation().lValueRequested, ""); + if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression())) + { + Declaration const* decl = identifier->annotation().referencedDeclaration; + if (knownVariable(*decl)) + { + auto innerValue = currentValue(*decl); + auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; + assignment(*decl, newValue, _op.location()); + defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement such assignments." + ); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement such increments / decrements." + ); + break; + } + case Token::Add: // + + defineExpr(_op, expr(_op.subExpression())); + break; + case Token::Sub: // - + { + defineExpr(_op, 0 - expr(_op.subExpression())); + if (auto intType = dynamic_cast<IntegerType const*>(_op.annotation().type.get())) + checkUnderOverflow(expr(_op), *intType, _op.location()); + break; + } + default: + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator." + ); + } } void SMTChecker::endVisit(BinaryOperation const& _op) @@ -268,16 +346,14 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) solAssert(args.size() == 1, ""); solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); checkCondition(!(expr(*args[0])), _funCall.location(), "Assertion violation"); - m_interface->addAssertion(expr(*args[0])); + addPathImpliedExpression(expr(*args[0])); } else if (funType.kind() == FunctionType::Kind::Require) { solAssert(args.size() == 1, ""); solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); - m_interface->addAssertion(expr(*args[0])); - checkCondition(!(expr(*args[0])), _funCall.location(), "Unreachable code"); - // TODO is there something meaningful we can check here? - // We can check whether the condition is always fulfilled or never fulfilled. + checkBooleanNotConstant(*args[0], "Condition is always $VALUE."); + addPathImpliedExpression(expr(*args[0])); } } @@ -290,7 +366,7 @@ void SMTChecker::endVisit(Identifier const& _identifier) // Will be translated as part of the node that requested the lvalue. } else if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get())) - m_interface->addAssertion(expr(_identifier) == currentValue(*decl)); + defineExpr(_identifier, currentValue(*decl)); else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) { if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require) @@ -306,10 +382,10 @@ void SMTChecker::endVisit(Literal const& _literal) if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type)) solAssert(!rational->isFractional(), ""); - m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal))); + defineExpr(_literal, smt::Expression(type.literalValue(&_literal))); } else if (type.category() == Type::Category::Bool) - m_interface->addAssertion(expr(_literal) == smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); + defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); else m_errorReporter.warning( _literal.location(), @@ -326,36 +402,30 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) case Token::Add: case Token::Sub: case Token::Mul: + case Token::Div: { solAssert(_op.annotation().commonType, ""); solAssert(_op.annotation().commonType->category() == Type::Category::Integer, ""); + auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType); smt::Expression left(expr(_op.leftExpression())); smt::Expression right(expr(_op.rightExpression())); Token::Value op = _op.getOperator(); smt::Expression value( op == Token::Add ? left + right : op == Token::Sub ? left - right : + op == Token::Div ? division(left, right, intType) : /*op == Token::Mul*/ left * right ); - // Overflow check - auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType); - checkCondition( - value < minValue(intType), - _op.location(), - "Underflow (resulting value less than " + formatNumber(intType.minValue()) + ")", - "value", - &value - ); - checkCondition( - value > maxValue(intType), - _op.location(), - "Overflow (resulting value larger than " + formatNumber(intType.maxValue()) + ")", - "value", - &value - ); + if (_op.getOperator() == Token::Div) + { + checkCondition(right == 0, _op.location(), "Division by zero", "value", &right); + m_interface->addAssertion(right != 0); + } + + checkUnderOverflow(value, intType, _op.location()); - m_interface->addAssertion(expr(_op) == value); + defineExpr(_op, value); break; } default: @@ -383,7 +453,7 @@ void SMTChecker::compareOperation(BinaryOperation const& _op) /*op == Token::GreaterThanOrEqual*/ (left >= right) ); // TODO: check that other values for op are not possible. - m_interface->addAssertion(expr(_op) == value); + defineExpr(_op, value); } else m_errorReporter.warning( @@ -400,9 +470,9 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) { // @TODO check that both of them are not constant if (_op.getOperator() == Token::And) - m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression())); + defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression())); else - m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) || expr(_op.rightExpression())); + defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression())); } else m_errorReporter.warning( @@ -411,30 +481,50 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) ); } -void SMTChecker::assignment(Declaration const& _variable, Expression const& _value) +smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type) { - // TODO more checks? - // TODO add restrictions about type (might be assignment from smaller type) - m_interface->addAssertion(newValue(_variable) == expr(_value)); + // Signed division in SMTLIB2 rounds differently for negative division. + if (_type.isSigned()) + return (smt::Expression::ite( + _left >= 0, + smt::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))), + smt::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right)) + )); + else + return _left / _right; +} + +void SMTChecker::assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location) +{ + assignment(_variable, expr(_value), _location); } -void SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +void SMTChecker::assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location) { - visitBranch(_statement, &_condition); + TypePointer type = _variable.type(); + if (auto const* intType = dynamic_cast<IntegerType const*>(type.get())) + checkUnderOverflow(_value, *intType, _location); + m_interface->addAssertion(newValue(_variable) == _value); } -void SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) +SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +{ + return visitBranch(_statement, &_condition); +} + +SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) { VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter; - m_interface->push(); if (_condition) - m_interface->addAssertion(*_condition); + pushPathCondition(*_condition); _statement.accept(*this); - m_interface->pop(); + if (_condition) + popPathCondition(); m_conditionalExecutionHappened = true; - m_currentSequenceCounter = sequenceCountersStart; + std::swap(sequenceCountersStart, m_currentSequenceCounter); + return sequenceCountersStart; } void SMTChecker::checkCondition( @@ -446,7 +536,7 @@ void SMTChecker::checkCondition( ) { m_interface->push(); - m_interface->addAssertion(_condition); + addPathConjoinedExpression(_condition); vector<smt::Expression> expressionsToEvaluate; vector<string> expressionNames; @@ -472,7 +562,7 @@ void SMTChecker::checkCondition( } smt::CheckResult result; vector<string> values; - tie(result, values) = checkSatisifableAndGenerateModel(expressionsToEvaluate); + tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); string conditionalComment; if (m_conditionalExecutionHappened) @@ -518,13 +608,13 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co return; m_interface->push(); - m_interface->addAssertion(expr(_condition)); - auto positiveResult = checkSatisifable(); + addPathConjoinedExpression(expr(_condition)); + auto positiveResult = checkSatisfiable(); m_interface->pop(); m_interface->push(); - m_interface->addAssertion(!expr(_condition)); - auto negatedResult = checkSatisifable(); + addPathConjoinedExpression(!expr(_condition)); + auto negatedResult = checkSatisfiable(); m_interface->pop(); if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) @@ -554,7 +644,7 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co } pair<smt::CheckResult, vector<string>> -SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate) +SMTChecker::checkSatisfiableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate) { smt::CheckResult result; vector<string> values; @@ -584,9 +674,9 @@ SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _exp return make_pair(result, values); } -smt::CheckResult SMTChecker::checkSatisifable() +smt::CheckResult SMTChecker::checkSatisfiable() { - return checkSatisifableAndGenerateModel({}).first; + return checkSatisfiableAndGenerateModel({}).first; } void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) @@ -614,6 +704,22 @@ void SMTChecker::resetVariables(vector<Declaration const*> _variables) } } +void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse) +{ + set<Declaration const*> uniqueVars(_variables.begin(), _variables.end()); + for (auto const* decl: uniqueVars) + { + int trueCounter = _countersEndTrue.at(decl); + int falseCounter = _countersEndFalse.at(decl); + solAssert(trueCounter != falseCounter, ""); + m_interface->addAssertion(newValue(*decl) == smt::Expression::ite( + _condition, + valueAtSequence(*decl, trueCounter), + valueAtSequence(*decl, falseCounter)) + ); + } +} + bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) { if (dynamic_cast<IntegerType const*>(_varDecl.type().get())) @@ -696,6 +802,18 @@ smt::Expression SMTChecker::expr(Expression const& _e) { if (!m_expressions.count(&_e)) { + m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." ); + createExpr(_e); + } + return m_expressions.at(&_e); +} + +void SMTChecker::createExpr(Expression const& _e) +{ + if (m_expressions.count(&_e)) + m_errorReporter.warning(_e.location(), "Internal error: Expression created twice in SMT solver." ); + else + { solAssert(_e.annotation().type, ""); switch (_e.annotation().type->category()) { @@ -716,7 +834,12 @@ smt::Expression SMTChecker::expr(Expression const& _e) solAssert(false, "Type not implemented."); } } - return m_expressions.at(&_e); +} + +void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) +{ + createExpr(_e); + m_interface->addAssertion(expr(_e) == _value); } smt::Expression SMTChecker::var(Declaration const& _decl) @@ -724,3 +847,31 @@ smt::Expression SMTChecker::var(Declaration const& _decl) solAssert(m_variables.count(&_decl), ""); return m_variables.at(&_decl); } + +void SMTChecker::popPathCondition() +{ + solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty."); + m_pathConditions.pop_back(); +} + +void SMTChecker::pushPathCondition(smt::Expression const& _e) +{ + m_pathConditions.push_back(currentPathConditions() && _e); +} + +smt::Expression SMTChecker::currentPathConditions() +{ + if (m_pathConditions.size() == 0) + return smt::Expression(true); + return m_pathConditions.back(); +} + +void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e) +{ + m_interface->addAssertion(currentPathConditions() && _e); +} + +void SMTChecker::addPathImpliedExpression(smt::Expression const& _e) +{ + m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e)); +} diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 8e07d74d..b57f0f96 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -26,6 +26,7 @@ #include <map> #include <string> +#include <vector> namespace dev { @@ -57,6 +58,7 @@ private: virtual void endVisit(ExpressionStatement const& _node) override; virtual void endVisit(Assignment const& _node) override; virtual void endVisit(TupleExpression const& _node) override; + virtual void endVisit(UnaryOperation const& _node) override; virtual void endVisit(BinaryOperation const& _node) override; virtual void endVisit(FunctionCall const& _node) override; virtual void endVisit(Identifier const& _node) override; @@ -66,12 +68,21 @@ private: void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); - void assignment(Declaration const& _variable, Expression const& _value); + /// Division expression in the given type. Requires special treatment because + /// of rounding for signed division. + smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type); - // Visits the branch given by the statement, pushes and pops the SMT checker. - // @param _condition if present, asserts that this condition is true within the branch. - void visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); - void visitBranch(Statement const& _statement, smt::Expression _condition); + void assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location); + void assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location); + + /// Maps a variable to an SSA index. + using VariableSequenceCounters = std::map<Declaration const*, int>; + + /// Visits the branch given by the statement, pushes and pops the current path conditions. + /// @param _condition if present, asserts that this condition is true within the branch. + /// @returns the variable sequence counter after visiting the branch. + VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); + VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression _condition); /// Check that a condition can be satisfied. void checkCondition( @@ -88,14 +99,21 @@ private: Expression const& _condition, std::string const& _description ); + /// Checks that the value is in the range given by the type. + void checkUnderOverflow(smt::Expression _value, IntegerType const& _Type, SourceLocation const& _location); + std::pair<smt::CheckResult, std::vector<std::string>> - checkSatisifableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate); + checkSatisfiableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate); - smt::CheckResult checkSatisifable(); + smt::CheckResult checkSatisfiable(); void initializeLocalVariables(FunctionDefinition const& _function); void resetVariables(std::vector<Declaration const*> _variables); + /// Given two different branches and the touched variables, + /// merge the touched variables into after-branch ite variables + /// using the branch condition as guard. + void mergeVariables(std::vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse); /// Tries to create an uninitialized variable and returns true on success. /// This fails if the type is not supported. bool createVariable(VariableDeclaration const& _varDecl); @@ -124,15 +142,27 @@ private: static smt::Expression minValue(IntegerType const& _t); static smt::Expression maxValue(IntegerType const& _t); - using VariableSequenceCounters = std::map<Declaration const*, int>; - - /// Returns the expression corresponding to the AST node. Creates a new expression - /// if it does not exist yet. + /// Returns the expression corresponding to the AST node. Throws if the expression does not exist. smt::Expression expr(Expression const& _e); + /// Creates the expression (value can be arbitrary) + void createExpr(Expression const& _e); + /// Creates the expression and sets its value. + void defineExpr(Expression const& _e, smt::Expression _value); /// Returns the function declaration corresponding to the given variable. /// The function takes one argument which is the "sequence number". smt::Expression var(Declaration const& _decl); + /// Adds a new path condition + void pushPathCondition(smt::Expression const& _e); + /// Remove the last path condition + void popPathCondition(); + /// Returns the conjunction of all path conditions or True if empty + smt::Expression currentPathConditions(); + /// Conjoin the current path conditions with the given parameter and add to the solver + void addPathConjoinedExpression(smt::Expression const& _e); + /// Add to the solver: the given expression implied by the current path conditions + void addPathImpliedExpression(smt::Expression const& _e); + std::shared_ptr<smt::SolverInterface> m_interface; std::shared_ptr<VariableUsage> m_variableUsage; bool m_conditionalExecutionHappened = false; @@ -140,6 +170,7 @@ private: std::map<Declaration const*, int> m_nextFreeSequenceCounter; std::map<Expression const*, smt::Expression> m_expressions; std::map<Declaration const*, smt::Expression> m_variables; + std::vector<smt::Expression> m_pathConditions; ErrorReporter& m_errorReporter; FunctionDefinition const* m_currentFunction = nullptr; diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index c9adf863..88487310 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -72,6 +72,11 @@ public: }, _trueValue.sort); } + static Expression implies(Expression _a, Expression _b) + { + return !std::move(_a) || std::move(_b); + } + friend Expression operator!(Expression _a) { return Expression("not", std::move(_a), Sort::Bool); @@ -120,6 +125,10 @@ public: { return Expression("*", std::move(_a), std::move(_b), Sort::Int); } + friend Expression operator/(Expression _a, Expression _b) + { + return Expression("/", std::move(_a), std::move(_b), Sort::Int); + } Expression operator()(Expression _a) const { solAssert( diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index e5c1aef4..769e6edb 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -127,7 +127,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) {">=", 2}, {"+", 2}, {"-", 2}, - {"*", 2} + {"*", 2}, + {"/", 2} }; string const& n = _expr.name; if (m_functions.count(n)) @@ -173,6 +174,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) return arguments[0] - arguments[1]; else if (n == "*") return arguments[0] * arguments[1]; + else if (n == "/") + return arguments[0] / arguments[1]; // Cannot reach here. solAssert(false, ""); return arguments[0]; diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index 2804ddfc..2d6e58de 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -54,14 +54,15 @@ bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::operator()(Label const& _label) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); m_info.stackHeightInfo[&_label] = m_stackHeight; + warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); return true; } bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); auto const& info = instructionInfo(_instruction.instruction); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instruction] = m_stackHeight; @@ -140,22 +141,34 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { - solAssert(!m_julia, ""); + solAssert(m_flavour != AsmFlavour::IULIA, ""); bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) if (!expectExpression(arg)) success = false; // Parser already checks that the number of arguments is correct. - solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), ""); - if (!(*this)(_instr.instruction)) - success = false; + auto const& info = instructionInfo(_instr.instruction); + solAssert(info.args == int(_instr.arguments.size()), ""); + m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instr] = m_stackHeight; + warnOnInstructions(_instr.instruction, _instr.location); + return success; +} + +bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) +{ + size_t initialStackHeight = m_stackHeight; + bool success = boost::apply_visitor(*this, _statement.expression); + if (m_flavour != AsmFlavour::Loose) + if (!expectDeposit(0, initialStackHeight, _statement.location)) + success = false; + m_info.stackHeightInfo[&_statement] = m_stackHeight; return success; } bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); bool success = checkAssignment(_assignment.variableName, size_t(-1)); m_info.stackHeightInfo[&_assignment] = m_stackHeight; return success; @@ -217,14 +230,14 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); solAssert(virtualBlock, ""); Scope& varScope = scope(virtualBlock); - for (auto const& var: _funDef.arguments + _funDef.returns) + for (auto const& var: _funDef.parameters + _funDef.returnVariables) { expectValidType(var.type, var.location); m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name))); } int const stackHeight = m_stackHeight; - m_stackHeight = _funDef.arguments.size() + _funDef.returns.size(); + m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size(); bool success = (*this)(_funDef.body); @@ -405,13 +418,13 @@ bool AsmAnalyzer::operator()(Block const& _block) return success; } -bool AsmAnalyzer::expectExpression(Statement const& _statement) +bool AsmAnalyzer::expectExpression(Expression const& _expr) { bool success = true; int const initialHeight = m_stackHeight; - if (!boost::apply_visitor(*this, _statement)) + if (!boost::apply_visitor(*this, _expr)) success = false; - if (!expectDeposit(1, initialHeight, locationOf(_statement))) + if (!expectDeposit(1, initialHeight, locationOf(_expr))) success = false; return success; } @@ -495,7 +508,7 @@ Scope& AsmAnalyzer::scope(Block const* _block) } void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) { - if (!m_julia) + if (m_flavour != AsmFlavour::IULIA) return; if (!builtinTypes.count(type)) @@ -522,11 +535,11 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio "the Metropolis hard fork. Before that it acts as an invalid instruction." ); - if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI) + if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) m_errorReporter.warning( _location, - "Jump instructions are low-level EVM features that can lead to " + "Jump instructions and labels are low-level EVM features that can lead to " "incorrect stack access. Because of that they are discouraged. " - "Please consider using \"switch\" or \"for\" statements instead." + "Please consider using \"switch\", \"if\" or \"for\" statements instead." ); } diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index e484b876..7a81dbf8 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -54,9 +54,9 @@ public: explicit AsmAnalyzer( AsmAnalysisInfo& _analysisInfo, ErrorReporter& _errorReporter, - bool _julia = false, + AsmFlavour _flavour = AsmFlavour::Loose, julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() - ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {} + ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {} bool analyze(assembly::Block const& _block); @@ -65,6 +65,7 @@ public: bool operator()(assembly::Identifier const&); bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); bool operator()(assembly::Label const& _label); + bool operator()(assembly::ExpressionStatement const&); bool operator()(assembly::StackAssignment const&); bool operator()(assembly::Assignment const& _assignment); bool operator()(assembly::VariableDeclaration const& _variableDeclaration); @@ -77,7 +78,7 @@ public: private: /// Visits the statement and expects it to deposit one item onto the stack. - bool expectExpression(Statement const& _statement); + bool expectExpression(Expression const& _expr); bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location); /// Verifies that a variable to be assigned to exists and has the same size @@ -96,7 +97,7 @@ private: std::set<Scope::Variable const*> m_activeVariables; AsmAnalysisInfo& m_info; ErrorReporter& m_errorReporter; - bool m_julia = false; + AsmFlavour m_flavour = AsmFlavour::Loose; }; } diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index a792a1b8..2982d5e0 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -58,23 +58,25 @@ struct StackAssignment { SourceLocation location; Identifier variableName; }; /// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy /// a single stack slot and expects a single expression on the right hand returning /// the same amount of items as the number of variables. -struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Statement> value; }; +struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Expression> value; }; /// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" -struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; }; -struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; }; +struct FunctionalInstruction { SourceLocation location; solidity::Instruction instruction; std::vector<Expression> arguments; }; +struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Expression> arguments; }; +/// Statement that contains only a single expression +struct ExpressionStatement { SourceLocation location; Expression expression; }; /// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted -struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Statement> value; }; +struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Expression> value; }; /// Block that creates a scope (frees declared stack variables) struct Block { SourceLocation location; std::vector<Statement> statements; }; /// Function definition ("function f(a, b) -> (d, e) { ... }") -struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; }; +struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList parameters; TypedNameList returnVariables; Block body; }; /// Conditional execution without "else" part. -struct If { SourceLocation location; std::shared_ptr<Statement> condition; Block body; }; +struct If { SourceLocation location; std::shared_ptr<Expression> condition; Block body; }; /// Switch case or default case struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; }; /// Switch statement -struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; }; -struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; }; +struct Switch { SourceLocation location; std::shared_ptr<Expression> expression; std::vector<Case> cases; }; +struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Expression> condition; Block post; Block body; }; struct LocationExtractor: boost::static_visitor<SourceLocation> { diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h index d627b41a..3a9600fe 100644 --- a/libsolidity/inlineasm/AsmDataForward.h +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -43,10 +43,22 @@ struct FunctionDefinition; struct FunctionCall; struct If; struct Switch; +struct Case; struct ForLoop; +struct ExpressionStatement; struct Block; -using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; +struct TypedName; + +using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>; +using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; + +enum class AsmFlavour +{ + Loose, // no types, EVM instructions as function, jumps and direct stack manipulations + Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations + IULIA // same as Strict mode with types +}; } } diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 8f171005..306b07e6 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -77,9 +77,7 @@ assembly::Statement Parser::parseStatement() { assembly::If _if = createWithLocation<assembly::If>(); m_scanner->next(); - _if.condition = make_shared<Statement>(parseExpression()); - if (_if.condition->type() == typeid(assembly::Instruction)) - fatalParserError("Instructions are not supported as conditions for if - try to append \"()\"."); + _if.condition = make_shared<Expression>(parseExpression()); _if.body = parseBlock(); return _if; } @@ -87,9 +85,7 @@ assembly::Statement Parser::parseStatement() { assembly::Switch _switch = createWithLocation<assembly::Switch>(); m_scanner->next(); - _switch.expression = make_shared<Statement>(parseExpression()); - if (_switch.expression->type() == typeid(assembly::Instruction)) - fatalParserError("Instructions are not supported as expressions for switch - try to append \"()\"."); + _switch.expression = make_shared<Expression>(parseExpression()); while (m_scanner->currentToken() == Token::Case) _switch.cases.emplace_back(parseCase()); if (m_scanner->currentToken() == Token::Default) @@ -107,14 +103,14 @@ assembly::Statement Parser::parseStatement() return parseForLoop(); case Token::Assign: { - if (m_julia) + if (m_flavour != AsmFlavour::Loose) break; assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>(); advance(); expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = currentLiteral(); - if (!m_julia && instructions().count(assignment.variableName.name)) + if (instructions().count(assignment.variableName.name)) fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); @@ -127,18 +123,21 @@ assembly::Statement Parser::parseStatement() // Simple instruction (might turn into functional), // literal, // identifier (might turn into label or functional assignment) - Statement statement(parseElementaryOperation(false)); + ElementaryOperation elementary(parseElementaryOperation()); switch (currentToken()) { case Token::LParen: - return parseCall(std::move(statement)); + { + Expression expr = parseCall(std::move(elementary)); + return ExpressionStatement{locationOf(expr), expr}; + } case Token::Comma: { // if a comma follows, a multiple assignment is assumed - if (statement.type() != typeid(assembly::Identifier)) + if (elementary.type() != typeid(assembly::Identifier)) fatalParserError("Label name / variable name must precede \",\" (multiple assignment)."); - assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement); + assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary); Assignment assignment = createWithLocation<Assignment>(identifier.location); assignment.variableNames.emplace_back(identifier); @@ -146,43 +145,43 @@ assembly::Statement Parser::parseStatement() do { expectToken(Token::Comma); - statement = parseElementaryOperation(false); - if (statement.type() != typeid(assembly::Identifier)) + elementary = parseElementaryOperation(); + if (elementary.type() != typeid(assembly::Identifier)) fatalParserError("Variable name expected in multiple assignemnt."); - assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(statement)); + assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary)); } while (currentToken() == Token::Comma); expectToken(Token::Colon); expectToken(Token::Assign); - assignment.value.reset(new Statement(parseExpression())); + assignment.value.reset(new Expression(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; return assignment; } case Token::Colon: { - if (statement.type() != typeid(assembly::Identifier)) + if (elementary.type() != typeid(assembly::Identifier)) fatalParserError("Label name / variable name must precede \":\"."); - assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement); + assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary); advance(); // identifier:=: should be parsed as identifier: =: (i.e. a label), // while identifier:= (being followed by a non-colon) as identifier := (assignment). if (currentToken() == Token::Assign && peekNextToken() != Token::Colon) { assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location); - if (!m_julia && instructions().count(identifier.name)) + if (m_flavour != AsmFlavour::IULIA && instructions().count(identifier.name)) fatalParserError("Cannot use instruction names for identifier names."); advance(); assignment.variableNames.emplace_back(identifier); - assignment.value.reset(new Statement(parseExpression())); + assignment.value.reset(new Expression(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; return assignment; } else { // label - if (m_julia) + if (m_flavour != AsmFlavour::Loose) fatalParserError("Labels are not supported."); Label label = createWithLocation<Label>(identifier.location); label.name = identifier.name; @@ -190,11 +189,25 @@ assembly::Statement Parser::parseStatement() } } default: - if (m_julia) + if (m_flavour != AsmFlavour::Loose) fatalParserError("Call or assignment expected."); break; } - return statement; + if (elementary.type() == typeid(assembly::Identifier)) + { + Expression expr = boost::get<assembly::Identifier>(elementary); + return ExpressionStatement{locationOf(expr), expr}; + } + else if (elementary.type() == typeid(assembly::Literal)) + { + Expression expr = boost::get<assembly::Literal>(elementary); + return ExpressionStatement{locationOf(expr), expr}; + } + else + { + solAssert(elementary.type() == typeid(assembly::Instruction), "Invalid elementary operation."); + return boost::get<assembly::Instruction>(elementary); + } } assembly::Case Parser::parseCase() @@ -206,10 +219,10 @@ assembly::Case Parser::parseCase() else if (m_scanner->currentToken() == Token::Case) { m_scanner->next(); - assembly::Statement statement = parseElementaryOperation(); - if (statement.type() != typeid(assembly::Literal)) + ElementaryOperation literal = parseElementaryOperation(); + if (literal.type() != typeid(assembly::Literal)) fatalParserError("Literal expected."); - _case.value = make_shared<Literal>(std::move(boost::get<assembly::Literal>(statement))); + _case.value = make_shared<Literal>(boost::get<assembly::Literal>(std::move(literal))); } else fatalParserError("Case or default case expected."); @@ -224,22 +237,39 @@ assembly::ForLoop Parser::parseForLoop() ForLoop forLoop = createWithLocation<ForLoop>(); expectToken(Token::For); forLoop.pre = parseBlock(); - forLoop.condition = make_shared<Statement>(parseExpression()); - if (forLoop.condition->type() == typeid(assembly::Instruction)) - fatalParserError("Instructions are not supported as conditions for the for statement."); + forLoop.condition = make_shared<Expression>(parseExpression()); forLoop.post = parseBlock(); forLoop.body = parseBlock(); forLoop.location.end = forLoop.body.location.end; return forLoop; } -assembly::Statement Parser::parseExpression() +assembly::Expression Parser::parseExpression() { RecursionGuard recursionGuard(*this); - Statement operation = parseElementaryOperation(true); + // In strict mode, this might parse a plain Instruction, but + // it will be converted to a FunctionalInstruction inside + // parseCall below. + ElementaryOperation operation = parseElementaryOperation(); if (operation.type() == typeid(Instruction)) { Instruction const& instr = boost::get<Instruction>(operation); + // Disallow instructions returning multiple values (and DUP/SWAP) as expression. + if ( + instructionInfo(instr.instruction).ret != 1 || + isDupInstruction(instr.instruction) || + isSwapInstruction(instr.instruction) + ) + fatalParserError( + "Instruction \"" + + instructionNames().at(instr.instruction) + + "\" not allowed in this context." + ); + if (m_flavour != AsmFlavour::Loose && currentToken() != Token::LParen) + fatalParserError( + "Non-functional instructions are not allowed in this context." + ); + // Enforce functional notation for instructions requiring multiple arguments. int args = instructionInfo(instr.instruction).args; if (args > 0 && currentToken() != Token::LParen) fatalParserError(string( @@ -252,8 +282,20 @@ assembly::Statement Parser::parseExpression() } if (currentToken() == Token::LParen) return parseCall(std::move(operation)); + else if (operation.type() == typeid(Instruction)) + { + // Instructions not taking arguments are allowed as expressions. + solAssert(m_flavour == AsmFlavour::Loose, ""); + Instruction& instr = boost::get<Instruction>(operation); + return FunctionalInstruction{std::move(instr.location), instr.instruction, {}}; + } + else if (operation.type() == typeid(assembly::Identifier)) + return boost::get<assembly::Identifier>(operation); else - return operation; + { + solAssert(operation.type() == typeid(assembly::Literal), ""); + return boost::get<assembly::Literal>(operation); + } } std::map<string, dev::solidity::Instruction> const& Parser::instructions() @@ -296,10 +338,10 @@ std::map<dev::solidity::Instruction, string> const& Parser::instructionNames() return s_instructionNames; } -assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) +Parser::ElementaryOperation Parser::parseElementaryOperation() { RecursionGuard recursionGuard(*this); - Statement ret; + ElementaryOperation ret; switch (currentToken()) { case Token::Identifier: @@ -317,15 +359,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) else literal = currentLiteral(); // first search the set of instructions. - if (!m_julia && instructions().count(literal)) + if (m_flavour != AsmFlavour::IULIA && instructions().count(literal)) { dev::solidity::Instruction const& instr = instructions().at(literal); - if (_onlySinglePusher) - { - InstructionInfo info = dev::solidity::instructionInfo(instr); - if (info.ret != 1) - fatalParserError("Instruction \"" + literal + "\" not allowed in this context."); - } ret = Instruction{location(), instr}; } else @@ -364,7 +400,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) "" }; advance(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { expectToken(Token::Colon); literal.location.end = endPosition(); @@ -377,7 +413,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) } default: fatalParserError( - m_julia ? + m_flavour == AsmFlavour::IULIA ? "Literal or identifier expected." : "Literal, identifier or instruction expected." ); @@ -402,7 +438,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() { expectToken(Token::Colon); expectToken(Token::Assign); - varDecl.value.reset(new Statement(parseExpression())); + varDecl.value.reset(new Expression(parseExpression())); varDecl.location.end = locationOf(*varDecl.value).end; } else @@ -419,7 +455,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() expectToken(Token::LParen); while (currentToken() != Token::RParen) { - funDef.arguments.emplace_back(parseTypedName()); + funDef.parameters.emplace_back(parseTypedName()); if (currentToken() == Token::RParen) break; expectToken(Token::Comma); @@ -431,7 +467,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() expectToken(Token::GreaterThan); while (true) { - funDef.returns.emplace_back(parseTypedName()); + funDef.returnVariables.emplace_back(parseTypedName()); if (currentToken() == Token::LBrace) break; expectToken(Token::Comma); @@ -442,16 +478,17 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() return funDef; } -assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) +assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) { RecursionGuard recursionGuard(*this); - if (_instruction.type() == typeid(Instruction)) + if (_initialOp.type() == typeid(Instruction)) { - solAssert(!m_julia, "Instructions are invalid in JULIA"); + solAssert(m_flavour != AsmFlavour::IULIA, "Instructions are invalid in JULIA"); + Instruction& instruction = boost::get<Instruction>(_initialOp); FunctionalInstruction ret; - ret.instruction = std::move(boost::get<Instruction>(_instruction)); - ret.location = ret.instruction.location; - solidity::Instruction instr = ret.instruction.instruction; + ret.instruction = instruction.instruction; + ret.location = std::move(instruction.location); + solidity::Instruction instr = ret.instruction; InstructionInfo instrInfo = instructionInfo(instr); if (solidity::isDupInstruction(instr)) fatalParserError("DUPi instructions not allowed for functional notation"); @@ -498,10 +535,10 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) expectToken(Token::RParen); return ret; } - else if (_instruction.type() == typeid(Identifier)) + else if (_initialOp.type() == typeid(Identifier)) { FunctionCall ret; - ret.functionName = std::move(boost::get<Identifier>(_instruction)); + ret.functionName = std::move(boost::get<Identifier>(_initialOp)); ret.location = ret.functionName.location; expectToken(Token::LParen); while (currentToken() != Token::RParen) @@ -517,7 +554,7 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) } else fatalParserError( - m_julia ? + m_flavour == AsmFlavour::IULIA ? "Function name expected." : "Assembly instruction or function name required in front of \"(\")" ); @@ -530,7 +567,7 @@ TypedName Parser::parseTypedName() RecursionGuard recursionGuard(*this); TypedName typedName = createWithLocation<TypedName>(); typedName.name = expectAsmIdentifier(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { expectToken(Token::Colon); typedName.location.end = endPosition(); @@ -542,12 +579,18 @@ TypedName Parser::parseTypedName() string Parser::expectAsmIdentifier() { string name = currentLiteral(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { - if (currentToken() == Token::Bool) + switch (currentToken()) { + case Token::Return: + case Token::Byte: + case Token::Address: + case Token::Bool: advance(); return name; + default: + break; } } else if (instructions().count(name)) diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index e46d1732..015aeef3 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -37,13 +37,16 @@ namespace assembly class Parser: public ParserBase { public: - explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {} + explicit Parser(ErrorReporter& _errorReporter, AsmFlavour _flavour = AsmFlavour::Loose): + ParserBase(_errorReporter), m_flavour(_flavour) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. std::shared_ptr<Block> parse(std::shared_ptr<Scanner> const& _scanner); protected: + using ElementaryOperation = boost::variant<assembly::Instruction, assembly::Literal, assembly::Identifier>; + /// Creates an inline assembly node with the given source location. template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation()) const { @@ -65,20 +68,23 @@ protected: Case parseCase(); ForLoop parseForLoop(); /// Parses a functional expression that has to push exactly one stack element - Statement parseExpression(); + assembly::Expression parseExpression(); static std::map<std::string, dev::solidity::Instruction> const& instructions(); static std::map<dev::solidity::Instruction, std::string> const& instructionNames(); - Statement parseElementaryOperation(bool _onlySinglePusher = false); + /// Parses an elementary operation, i.e. a literal, identifier or instruction. + /// This will parse instructions even in strict mode as part of the full parser + /// for FunctionalInstruction. + ElementaryOperation parseElementaryOperation(); VariableDeclaration parseVariableDeclaration(); FunctionDefinition parseFunctionDefinition(); - Statement parseCall(Statement&& _instruction); + assembly::Expression parseCall(ElementaryOperation&& _initialOp); TypedName parseTypedName(); std::string expectAsmIdentifier(); static bool isValidNumberLiteral(std::string const& _literal); private: - bool m_julia = false; + AsmFlavour m_flavour = AsmFlavour::Loose; }; } diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index 0f183244..bacd7a94 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -94,7 +94,7 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional { solAssert(!m_julia, ""); return - (*this)(_functionalInstruction.instruction) + + boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) + "(" + boost::algorithm::join( _functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), @@ -102,6 +102,11 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional ")"; } +string AsmPrinter::operator()(ExpressionStatement const& _statement) +{ + return boost::apply_visitor(*this, _statement.expression); +} + string AsmPrinter::operator()(assembly::Label const& _label) { solAssert(!m_julia, ""); @@ -144,17 +149,17 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin { string out = "function " + _functionDefinition.name + "("; out += boost::algorithm::join( - _functionDefinition.arguments | boost::adaptors::transformed( + _functionDefinition.parameters | boost::adaptors::transformed( [this](TypedName argument) { return argument.name + appendTypeName(argument.type); } ), ", " ); out += ")"; - if (!_functionDefinition.returns.empty()) + if (!_functionDefinition.returnVariables.empty()) { out += " -> "; out += boost::algorithm::join( - _functionDefinition.returns | boost::adaptors::transformed( + _functionDefinition.returnVariables | boost::adaptors::transformed( [this](TypedName argument) { return argument.name + appendTypeName(argument.type); } ), ", " diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index eadf81d9..5bd87aba 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -42,6 +42,7 @@ public: std::string operator()(assembly::Literal const& _literal); std::string operator()(assembly::Identifier const& _identifier); std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction); + std::string operator()(assembly::ExpressionStatement const& _expr); std::string operator()(assembly::Label const& _label); std::string operator()(assembly::StackAssignment const& _assignment); std::string operator()(assembly::Assignment const& _assignment); diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp index 77ae9102..86f3809c 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -45,6 +45,11 @@ ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter): m_currentScope = &scope(nullptr); } +bool ScopeFiller::operator()(ExpressionStatement const& _expr) +{ + return boost::apply_visitor(*this, _expr.expression); +} + bool ScopeFiller::operator()(Label const& _item) { if (!m_currentScope->registerLabel(_item.name)) @@ -71,10 +76,10 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) { bool success = true; vector<Scope::JuliaType> arguments; - for (auto const& _argument: _funDef.arguments) + for (auto const& _argument: _funDef.parameters) arguments.push_back(_argument.type); vector<Scope::JuliaType> returns; - for (auto const& _return: _funDef.returns) + for (auto const& _return: _funDef.returnVariables) returns.push_back(_return.type); if (!m_currentScope->registerFunction(_funDef.name, arguments, returns)) { @@ -91,7 +96,7 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) varScope.superScope = m_currentScope; m_currentScope = &varScope; varScope.functionScope = true; - for (auto const& var: _funDef.arguments + _funDef.returns) + for (auto const& var: _funDef.parameters + _funDef.returnVariables) if (!registerVariable(var, _funDef.location, varScope)) success = false; diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h index ed28abbf..bb023f61 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.h +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -53,6 +53,7 @@ public: bool operator()(assembly::Literal const&) { return true; } bool operator()(assembly::Identifier const&) { return true; } bool operator()(assembly::FunctionalInstruction const&) { return true; } + bool operator()(assembly::ExpressionStatement const& _expr); bool operator()(assembly::Label const& _label); bool operator()(assembly::StackAssignment const&) { return true; } bool operator()(assembly::Assignment const&) { return true; } diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 504ad92c..1b4bd270 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -38,6 +38,25 @@ using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ +assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language) +{ + switch (_language) + { + case AssemblyStack::Language::Assembly: + return assembly::AsmFlavour::Loose; + case AssemblyStack::Language::StrictAssembly: + return assembly::AsmFlavour::Strict; + case AssemblyStack::Language::JULIA: + return assembly::AsmFlavour::IULIA; + } + solAssert(false, ""); + return assembly::AsmFlavour::IULIA; +} + +} + Scanner const& AssemblyStack::scanner() const { @@ -50,7 +69,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string m_errors.clear(); m_analysisSuccessful = false; m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName); - m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner); + m_parserResult = assembly::Parser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner); if (!m_errorReporter.errors().empty()) return false; solAssert(m_parserResult, ""); @@ -72,7 +91,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann bool AssemblyStack::analyzeParsed() { m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); - assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA); + assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language)); m_analysisSuccessful = analyzer.analyze(*m_parserResult); return m_analysisSuccessful; } diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 2ae596ed..6ae7e8d1 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -51,7 +51,7 @@ struct MachineAssemblyObject class AssemblyStack { public: - enum class Language { JULIA, Assembly }; + enum class Language { JULIA, Assembly, StrictAssembly }; enum class Machine { EVM, EVM15, eWasm }; explicit AssemblyStack(Language _language = Language::Assembly): diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h index 09301b10..7c66d572 100644 --- a/libsolidity/interface/Exceptions.h +++ b/libsolidity/interface/Exceptions.h @@ -109,6 +109,18 @@ public: infos.push_back(std::make_pair(_errMsg, _sourceLocation)); return *this; } + /// Limits the number of secondary source locations to 32 and appends a notice to the + /// error message. + void limitSize(std::string& _message) + { + size_t occurrences = infos.size(); + if (occurrences > 32) + { + infos.resize(32); + _message += " Truncated from " + boost::lexical_cast<std::string>(occurrences) + " to the first 32 occurrences."; + } + } + std::vector<errorSourceLocationInfo> infos; }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 430739ac..04f5bd25 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -81,15 +81,15 @@ Json::Value formatErrorWithException( else message = _message; + Json::Value sourceLocation; if (location && location->sourceName) { - Json::Value sourceLocation = Json::objectValue; sourceLocation["file"] = *location->sourceName; sourceLocation["start"] = location->start; sourceLocation["end"] = location->end; } - return formatError(_warning, _type, _component, message, formattedMessage, location); + return formatError(_warning, _type, _component, message, formattedMessage, sourceLocation); } set<string> requestedContractNames(Json::Value const& _outputSelection) @@ -131,6 +131,61 @@ StringMap createSourceList(Json::Value const& _input) return sources; } +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact) +{ + for (auto const& artifact: _outputSelection) + /// @TODO support sub-matching, e.g "evm" matches "evm.assembly" + if (artifact == "*" || artifact == _artifact) + return true; + return false; +} + +/// +/// @a _outputSelection is a JSON object containining a two-level hashmap, where the first level is the filename, +/// the second level is the contract name and the value is an array of artifact names to be requested for that contract. +/// @a _file is the current file +/// @a _contract is the current contract +/// @a _artifact is the current artifact name +/// +/// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract. +/// +/// In @a _outputSelection the use of '*' as a wildcard is permitted. +/// +/// @TODO optimise this. Perhaps flatten the structure upfront. +/// +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact) +{ + if (!_outputSelection.isObject()) + return false; + + for (auto const& file: { _file, string("*") }) + if (_outputSelection.isMember(file) && _outputSelection[file].isObject()) + { + /// For SourceUnit-level targets (such as AST) only allow empty name, otherwise + /// for Contract-level targets try both contract name and wildcard + vector<string> contracts{ _contract }; + if (!_contract.empty()) + contracts.push_back("*"); + for (auto const& contract: contracts) + if ( + _outputSelection[file].isMember(contract) && + _outputSelection[file][contract].isArray() && + isArtifactRequested(_outputSelection[file][contract], _artifact) + ) + return true; + } + + return false; +} + +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts) +{ + for (auto const& artifact: _artifacts) + if (isArtifactRequested(_outputSelection, _file, _contract, artifact)) + return true; + return false; +} + Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences) { Json::Value ret(Json::objectValue); @@ -138,7 +193,7 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere for (auto const& ref: linkReferences) { string const& fullname = ref.second; - size_t colon = fullname.find(':'); + size_t colon = fullname.rfind(':'); solAssert(colon != string::npos, ""); string file = fullname.substr(0, colon); string name = fullname.substr(colon + 1); @@ -396,43 +451,65 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); - sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); + if (isArtifactRequested(outputSelection, sourceName, "", "ast")) + sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); + if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST")) + sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); output["sources"][sourceName] = sourceResult; } Json::Value contractsOutput = Json::objectValue; for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>()) { - size_t colon = contractName.find(':'); + size_t colon = contractName.rfind(':'); solAssert(colon != string::npos, ""); string file = contractName.substr(0, colon); string name = contractName.substr(colon + 1); // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - contractData["abi"] = m_compilerStack.contractABI(contractName); - contractData["metadata"] = m_compilerStack.metadata(contractName); - contractData["userdoc"] = m_compilerStack.natspecUser(contractName); - contractData["devdoc"] = m_compilerStack.natspecDev(contractName); + if (isArtifactRequested(outputSelection, file, name, "abi")) + contractData["abi"] = m_compilerStack.contractABI(contractName); + if (isArtifactRequested(outputSelection, file, name, "metadata")) + contractData["metadata"] = m_compilerStack.metadata(contractName); + if (isArtifactRequested(outputSelection, file, name, "userdoc")) + contractData["userdoc"] = m_compilerStack.natspecUser(contractName); + if (isArtifactRequested(outputSelection, file, name, "devdoc")) + contractData["devdoc"] = m_compilerStack.natspecDev(contractName); // EVM Json::Value evmData(Json::objectValue); // @TODO: add ir - evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); - evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); - evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); - evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); - - evmData["bytecode"] = collectEVMObject( - m_compilerStack.object(contractName), - m_compilerStack.sourceMapping(contractName) - ); - - evmData["deployedBytecode"] = collectEVMObject( - m_compilerStack.runtimeObject(contractName), - m_compilerStack.runtimeSourceMapping(contractName) - ); + if (isArtifactRequested(outputSelection, file, name, "evm.assembly")) + evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); + if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly")) + evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); + if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers")) + evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); + if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates")) + evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); + + if (isArtifactRequested( + outputSelection, + file, + name, + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + )) + evmData["bytecode"] = collectEVMObject( + m_compilerStack.object(contractName), + m_compilerStack.sourceMapping(contractName) + ); + + if (isArtifactRequested( + outputSelection, + file, + name, + { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" } + )) + evmData["deployedBytecode"] = collectEVMObject( + m_compilerStack.runtimeObject(contractName), + m_compilerStack.runtimeSourceMapping(contractName) + ); contractData["evm"] = evmData; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 821e81d2..05b877b5 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -644,15 +644,11 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition() expectToken(Token::Event); ASTPointer<ASTString> name(expectIdentifierToken()); - ASTPointer<ParameterList> parameters; - if (m_scanner->currentToken() == Token::LParen) - { - VarDeclParserOptions options; - options.allowIndexed = true; - parameters = parseParameterList(options); - } - else - parameters = createEmptyParameterList(); + + VarDeclParserOptions options; + options.allowIndexed = true; + ASTPointer<ParameterList> parameters = parseParameterList(options); + bool anonymous = false; if (m_scanner->currentToken() == Token::Anonymous) { diff --git a/scripts/build.sh b/scripts/build.sh index 3785e1c1..bddbb97a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -7,6 +7,11 @@ else fi cd $(dirname "$0")/.. && + +if [[ "$(git tag --points-at HEAD 2>/dev/null)" == v* ]]; then + touch prerelease.txt +fi + mkdir -p build && cd build && cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" && @@ -20,4 +25,4 @@ fi if [ -z $CI ]; then echo "Installing solc and soltest" install solc/solc /usr/local/bin && install test/soltest /usr/local/bin -fi
\ No newline at end of file +fi diff --git a/scripts/bytecodecompare/storebytecode.sh b/scripts/bytecodecompare/storebytecode.sh index 8d4100bf..557e3275 100755 --- a/scripts/bytecodecompare/storebytecode.sh +++ b/scripts/bytecodecompare/storebytecode.sh @@ -40,7 +40,7 @@ TMPDIR=$(mktemp -d) if [[ "$SOLC_EMSCRIPTEN" = "On" ]] then - cp "$REPO_ROOT/build/solc/soljson.js" . + cp "$REPO_ROOT/build/libsolc/soljson.js" . npm install solc cat > solc <<EOF #!/usr/bin/env node diff --git a/scripts/docker_deploy_manual.sh b/scripts/docker_deploy_manual.sh new file mode 100755 index 00000000..c098f4ee --- /dev/null +++ b/scripts/docker_deploy_manual.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env sh + +set -e + +if [ -z "$1" ] +then + echo "Usage: $0 <tag/branch>" + exit 1 +fi +branch="$1" + +#docker login + +DIR=$(mktemp -d) +( +cd "$DIR" + +git clone --depth 2 https://github.com/ethereum/solidity.git -b "$branch" +cd solidity +commithash=$(git rev-parse --short=8 HEAD) +echo -n "$commithash" > commit_hash.txt +version=$($(dirname "$0")/get_version.sh) +if [ "$branch" = "release" -o "$branch" = v"$version" ] +then + echo -n > prerelease.txt +else + date -u +"nightly.%Y.%-m.%-d" > prerelease.txt +fi + +rm -rf .git +docker build -t ethereum/solc:build -f scripts/Dockerfile . +tmp_container=$(docker create ethereum/solc:build sh) +if [ "$branch" = "develop" ] +then + docker tag ethereum/solc:build ethereum/solc:nightly; + docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$commithash" + docker push ethereum/solc:nightly-"$version"-"$commithash"; + docker push ethereum/solc:nightly; +elif [ "$branch" = v"$version" ] +then + docker tag ethereum/solc:build ethereum/solc:stable; + docker tag ethereum/solc:build ethereum/solc:"$version"; + docker push ethereum/solc:stable; + docker push ethereum/solc:"$version"; +else + echo "Not publishing docker image from branch or tag $branch" +fi +) +rm -rf "$DIR" diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 15e864b5..a8966f96 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -137,7 +137,7 @@ case $(uname -s) in # All our dependencies can be found in the Arch Linux official repositories. # See https://wiki.archlinux.org/index.php/Official_repositories # Also adding ethereum-git to allow for testing with the `eth` client - sudo pacman -Sy \ + sudo pacman -Syu \ base-devel \ boost \ cmake \ @@ -253,19 +253,20 @@ case $(uname -s) in echo "See https://github.com/ethereum/webthree-umbrella/issues/552." exit 1 ;; - #------------------------------------------------------------------------------ # Ubuntu # #------------------------------------------------------------------------------ - Ubuntu) + Ubuntu|LinuxMint) + #LinuxMint is a distro on top of Ubuntu. #Ubuntu install_z3="" case $(lsb_release -cs) in - trusty) + trusty|qiana|rebecca|rafaela|rosa) #trusty echo "Installing solidity dependencies on Ubuntu Trusty Tahr (14.04)." + echo "Or, you may also be running Linux Mint Qiana / Rebecca / Rafaela / Rosa (base: Ubuntu Trusty Tahr (14.04).)" ;; utopic) #utopic @@ -279,9 +280,10 @@ case $(uname -s) in #wily echo "Installing solidity dependencies on Ubuntu Wily Werewolf (15.10)." ;; - xenial) + xenial|sarah|serena|sonya|sylvia) #xenial echo "Installing solidity dependencies on Ubuntu Xenial Xerus (16.04)." + echo "Or, you may also be running Linux Mint Sarah / Serena / Sonya / Sylvia (base: Ubuntu Xenial Xerus (16.04).)" install_z3="libz3-dev" ;; yakkety) @@ -299,6 +301,15 @@ case $(uname -s) in echo "Installing solidity dependencies on Ubuntu Artful (17.10)." install_z3="libz3-dev" ;; + betsy) + #do not try anything for betsy. + echo "Linux Mint Betsy is not supported at the moment as it runs off of Debian." + echo "We only support Sylvia, Sonya, Serena, Sarah, Rosa, Rafaela, Rebecca, and Qiana." + echo "See http://solidity.readthedocs.io/en/latest/installing-solidity.html for manual instructions." + echo "If you would like to get your distro working, that would be fantastic." + echo "Drop us a message at https://gitter.im/ethereum/solidity-dev." + exit 1 + ;; *) #other Ubuntu echo "ERROR - Unknown or unsupported Ubuntu version (" $(lsb_release -cs) ")" @@ -345,7 +356,7 @@ case $(uname -s) in # Make Sure we have the EPEL repos sudo yum -y install epel-release # Get g++ 4.8 - sudo rpm --import http://ftp.scientificlinux.org/linux/scientific/5x/x86_64/RPM-GPG-KEYs/RPM-GPG-KEY-cern + sudo rpm --import http://linuxsoft.cern.ch/cern/slc6X/i386/RPM-GPG-KEY-cern wget -O /etc/yum.repos.d/slc6-devtoolset.repo http://linuxsoft.cern.ch/cern/devtoolset/slc6-devtoolset.repo sudo yum -y install devtoolset-2-gcc devtoolset-2-gcc-c++ devtoolset-2-binutils diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index cb3519b0..2dd43302 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -54,7 +54,7 @@ keyid=703F83D0 email=builds@ethereum.org packagename=solc -for distribution in trusty vivid xenial zesty +for distribution in trusty vivid xenial zesty artful do cd /tmp/ rm -rf $distribution diff --git a/scripts/test_emscripten.sh b/scripts/test_emscripten.sh index 4996e957..b659e5e5 100755 --- a/scripts/test_emscripten.sh +++ b/scripts/test_emscripten.sh @@ -29,7 +29,7 @@ set -e REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) -SOLJSON="$REPO_ROOT/build/solc/soljson.js" +SOLJSON="$REPO_ROOT/build/libsolc/soljson.js" DIR=$(mktemp -d) ( diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index bf460e8e..56826997 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -87,8 +87,8 @@ make -j 4 cd .. mkdir -p upload -cp build/solc/soljson.js upload/ -cp build/solc/soljson.js ./ +cp build/libsolc/soljson.js upload/ +cp build/libsolc/soljson.js ./ OUTPUT_SIZE=`ls -la soljson.js` diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt index 656b27c3..d9b12a06 100644 --- a/solc/CMakeLists.txt +++ b/solc/CMakeLists.txt @@ -10,14 +10,6 @@ target_link_libraries(solc PRIVATE solidity ${Boost_PROGRAM_OPTIONS_LIBRARIES}) include(GNUInstallDirs) install(TARGETS solc DESTINATION "${CMAKE_INSTALL_BINDIR}") -if (EMSCRIPTEN) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_license\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\",\"_compileStandard\"]' -s RESERVED_FUNCTION_POINTERS=20") - add_executable(soljson jsonCompiler.cpp) -else() - add_library(soljson jsonCompiler.cpp) -endif() -target_link_libraries(soljson PRIVATE solidity) - if(SOLC_LINK_STATIC AND UNIX AND NOT APPLE) # Produce solc as statically linked binary (includes C/C++ standard libraries) # This is not supported on macOS, see diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 9e2cb77a..adcfee9c 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -97,6 +97,7 @@ static string const g_strJulia = "julia"; static string const g_strLicense = "license"; static string const g_strLibraries = "libraries"; static string const g_strLink = "link"; +static string const g_strMachine = "machine"; static string const g_strMetadata = "metadata"; static string const g_strMetadataLiteral = "metadata-literal"; static string const g_strNatspecDev = "devdoc"; @@ -112,6 +113,7 @@ static string const g_strSourceList = "sourceList"; static string const g_strSrcMap = "srcmap"; static string const g_strSrcMapRuntime = "srcmap-runtime"; static string const g_strStandardJSON = "standard-json"; +static string const g_strStrictAssembly = "strict-assembly"; static string const g_strPrettyJson = "pretty-json"; static string const g_strVersion = "version"; @@ -134,10 +136,10 @@ static string const g_argFormal = g_strFormal; static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; static string const g_argInputFile = g_strInputFile; -static string const g_argJulia = "julia"; +static string const g_argJulia = g_strJulia; static string const g_argLibraries = g_strLibraries; static string const g_argLink = g_strLink; -static string const g_argMachine = "machine"; +static string const g_argMachine = g_strMachine; static string const g_argMetadata = g_strMetadata; static string const g_argMetadataLiteral = g_strMetadataLiteral; static string const g_argNatspecDev = g_strNatspecDev; @@ -148,6 +150,7 @@ static string const g_argOptimizeRuns = g_strOptimizeRuns; static string const g_argOutputDir = g_strOutputDir; static string const g_argSignatureHashes = g_strSignatureHashes; static string const g_argStandardJSON = g_strStandardJSON; +static string const g_argStrictAssembly = g_strStrictAssembly; static string const g_argVersion = g_strVersion; static string const g_stdinFileName = g_stdinFileNameStr; @@ -575,6 +578,10 @@ Allowed options)", "Switch to JULIA mode, ignoring all options except --machine and assumes input is JULIA." ) ( + g_argStrictAssembly.c_str(), + "Switch to strict assembly mode, ignoring all options except --machine and assumes input is strict assembly." + ) + ( g_argMachine.c_str(), po::value<string>()->value_name(boost::join(g_machineArgs, ",")), "Target machine in assembly or JULIA mode." @@ -737,13 +744,13 @@ bool CommandLineInterface::processInput() if (!parseLibraryOption(library)) return false; - if (m_args.count(g_argAssemble) || m_args.count(g_argJulia)) + if (m_args.count(g_argAssemble) || m_args.count(g_argStrictAssembly) || m_args.count(g_argJulia)) { // switch to assembly mode m_onlyAssemble = true; using Input = AssemblyStack::Language; using Machine = AssemblyStack::Machine; - Input inputLanguage = m_args.count(g_argJulia) ? Input::JULIA : Input::Assembly; + Input inputLanguage = m_args.count(g_argJulia) ? Input::JULIA : (m_args.count(g_argStrictAssembly) ? Input::StrictAssembly : Input::Assembly); Machine targetMachine = Machine::EVM; if (m_args.count(g_argMachine)) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6a8a4399..f36ad4c5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,7 +3,7 @@ list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/fuzzer.cpp") file(GLOB_RECURSE headers "*.h") add_executable(soltest ${sources} ${headers}) -target_link_libraries(soltest PRIVATE soljson solidity lll evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) +target_link_libraries(soltest PRIVATE libsolc solidity lll evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) add_executable(solfuzzer fuzzer.cpp) -target_link_libraries(solfuzzer soljson evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 2c61c0a6..8aa99473 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -84,14 +84,24 @@ public: return callFallbackWithValue(0); } - template <class... Args> - bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments) + bytes const& callContractFunctionWithValueNoEncoding(std::string _sig, u256 const& _value, bytes const& _arguments) { FixedHash<4> hash(dev::keccak256(_sig)); - sendMessage(hash.asBytes() + encodeArgs(_arguments...), false, _value); + sendMessage(hash.asBytes() + _arguments, false, _value); return m_output; } + bytes const& callContractFunctionNoEncoding(std::string _sig, bytes const& _arguments) + { + return callContractFunctionWithValueNoEncoding(_sig, 0, _arguments); + } + + template <class... Args> + bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments) + { + return callContractFunctionWithValueNoEncoding(_sig, _value, encodeArgs(_arguments...)); + } + template <class... Args> bytes const& callContractFunction(std::string _sig, Args const&... _arguments) { diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 7b452e06..a3cc51c5 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -57,6 +57,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) if (dev::test::Options::get().disableIPC) { for (auto suite: { + "ABIDecoderTest", "ABIEncoderTest", "SolidityAuctionRegistrar", "SolidityFixedFeeRegistrar", diff --git a/test/externalTests.sh b/test/externalTests.sh index 1cc0af19..11972eae 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -44,53 +44,6 @@ DIR=$(mktemp -d) npm install find . -name soljson.js -exec cp "$SOLJSON" {} \; - # This is a patch that lets truffle ignore the pre-release compiler warning - cat > truffle.patch <<EOF ---- node_modules/truffle/build/cli.bundled.js 2017-11-27 16:56:47.114830112 +0100 -+++ /tmp/patched 2017-11-27 16:52:31.887064115 +0100 -@@ -313846,9 +313846,12 @@ - }); - - output = JSON.parse(output); -+ var errors = output.errors.filter(function(solidity_error) { -+ return solidity_error.formattedMessage.indexOf("pre-release compiler") < 0; -+ }); - -- if (output.errors) { -- throw new CompileError(output.errors[0].formattedMessage); -+ if (errors) { -+ throw new CompileError(errors[0].formattedMessage); - } - - return { -@@ -313901,9 +313904,13 @@ - return {error: importErrorKey}; - }); - -- output = JSON.parse(output); -+ output = JSON.parse(output); -+ -+ var errors = output.errors.filter(function(solidity_error) { -+ return solidity_error.formattedMessage.indexOf("pre-release compiler") < 0; -+ }); - -- var nonImportErrors = output.errors.filter(function(solidity_error) { -+ var nonImportErrors = errors.filter(function(solidity_error) { - // If the import error key is not found, we must not have an import error. - // This means we have a *different* parsing error which we should show to the user. - // Note: solc can return multiple parsing errors at once. -@@ -313917,7 +313924,7 @@ - - // Now, all errors must be import errors. - // Filter out our forced import, then get the import paths of the rest. -- var imports = output.errors.filter(function(solidity_error) { -+ var imports = errors.filter(function(solidity_error) { - return solidity_error.message.indexOf(failingImportFileName) < 0; - }).map(function(solidity_error) { - var matches = solidity_error.formattedMessage.match(/import[^'"]+("|')([^'"]+)("|');/); -EOF - - patch node_modules/truffle/build/cli.bundled.js ./truffle.patch npm run test ) rm -rf "$DIR" diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index 53ba7201..578e63a4 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -20,7 +20,7 @@ #include <libevmasm/Assembly.h> #include <libevmasm/ConstantOptimiser.h> -#include <solc/jsonCompiler.h> +#include <libsolc/libsolc.h> #include <json/json.h> diff --git a/test/libjulia/Common.cpp b/test/libjulia/Common.cpp new file mode 100644 index 00000000..e1ab8215 --- /dev/null +++ b/test/libjulia/Common.cpp @@ -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/>. +*/ +/** + * @date 2017 + * Common functions the iulia tests. + */ + +#include <test/libjulia/Common.h> + +#include <libjulia/optimiser/Disambiguator.h> + +#include <libsolidity/parsing/Scanner.h> + +#include <libsolidity/inlineasm/AsmParser.h> +#include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmPrinter.h> + +#include <libsolidity/interface/SourceReferenceFormatter.h> +#include <libsolidity/interface/ErrorReporter.h> + +#include <boost/test/unit_test.hpp> + +using namespace std; +using namespace dev::julia; +using namespace dev::solidity; + +void dev::julia::test::printErrors(ErrorList const& _errors, Scanner const& _scanner) +{ + for (auto const& error: _errors) + SourceReferenceFormatter::printExceptionInformation( + cout, + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error", + [&](std::string const&) -> Scanner const& { return _scanner; } + ); +} + + +pair<shared_ptr<Block>, shared_ptr<assembly::AsmAnalysisInfo>> dev::julia::test::parse(string const& _source, bool _julia) +{ + auto flavour = _julia ? assembly::AsmFlavour::IULIA : assembly::AsmFlavour::Strict; + ErrorList errors; + ErrorReporter errorReporter(errors); + auto scanner = make_shared<Scanner>(CharStream(_source), ""); + auto parserResult = assembly::Parser(errorReporter, flavour).parse(scanner); + if (parserResult) + { + BOOST_REQUIRE(errorReporter.errors().empty()); + auto analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); + assembly::AsmAnalyzer analyzer(*analysisInfo, errorReporter, flavour); + if (analyzer.analyze(*parserResult)) + { + BOOST_REQUIRE(errorReporter.errors().empty()); + return make_pair(parserResult, analysisInfo); + } + } + printErrors(errors, *scanner); + BOOST_FAIL("Invalid source."); + + // Unreachable. + return {}; +} + +assembly::Block dev::julia::test::disambiguate(string const& _source, bool _julia) +{ + auto result = parse(_source, _julia); + return boost::get<Block>(Disambiguator(*result.second)(*result.first)); +} + +string dev::julia::test::format(string const& _source, bool _julia) +{ + return assembly::AsmPrinter(_julia)(*parse(_source, _julia).first); +} diff --git a/test/libjulia/Common.h b/test/libjulia/Common.h new file mode 100644 index 00000000..1371101c --- /dev/null +++ b/test/libjulia/Common.h @@ -0,0 +1,55 @@ +/* + 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/>. +*/ +/** + * @date 2017 + * Common functions the iulia tests. + */ + +#pragma once + +#include <libsolidity/inlineasm/AsmData.h> + +#include <string> +#include <vector> +#include <memory> + +namespace dev +{ +namespace solidity +{ +class Scanner; +class Error; +using ErrorList = std::vector<std::shared_ptr<Error const>>; +namespace assembly +{ +struct AsmAnalysisInfo; +} +} +namespace julia +{ +namespace test +{ + +void printErrors(solidity::ErrorList const& _errors, solidity::Scanner const& _scanner); +std::pair<std::shared_ptr<solidity::assembly::Block>, std::shared_ptr<solidity::assembly::AsmAnalysisInfo>> +parse(std::string const& _source, bool _julia = true); +solidity::assembly::Block disambiguate(std::string const& _source, bool _julia = true); +std::string format(std::string const& _source, bool _julia = true); + +} +} +} diff --git a/test/libjulia/Disambiguator.cpp b/test/libjulia/Disambiguator.cpp new file mode 100644 index 00000000..a6338449 --- /dev/null +++ b/test/libjulia/Disambiguator.cpp @@ -0,0 +1,105 @@ +/* + 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/>. +*/ +/** + * @date 2017 + * Unit tests for the iulia name disambiguator. + */ + +#include <test/libjulia/Common.h> + +#include <libsolidity/inlineasm/AsmPrinter.h> + +#include <boost/test/unit_test.hpp> + +using namespace std; +using namespace dev::julia::test; +using namespace dev::solidity; + +#define CHECK(_original, _expectation)\ +do\ +{\ + assembly::AsmPrinter p(true);\ + string result = p(disambiguate(_original));\ + BOOST_CHECK_EQUAL(result, format(_expectation));\ + BOOST_CHECK_EQUAL(result, p(disambiguate(result)));\ +}\ +while(false) + +BOOST_AUTO_TEST_SUITE(IuliaDisambiguator) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + CHECK("{ }", "{ }"); +} + +BOOST_AUTO_TEST_CASE(variables) +{ + CHECK( + "{ { let a:u256 } { let a:u256 } }", + "{ { let a:u256 } { let a_1:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(variables_clash) +{ + CHECK( + "{ { let a:u256 let a_1:u256 } { let a:u256 } }", + "{ { let a:u256 let a_1:u256 } { let a_2:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(variables_inside_functions) +{ + CHECK( + "{ { let c:u256 let b:u256 } function f(a:u256, c:u256) -> b:u256 { let x:u256 } { let a:u256 let x:u256 } }", + "{ { let c:u256 let b:u256 } function f(a:u256, c_1:u256) -> b_1:u256 { let x:u256 } { let a_1:u256 let x_1:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(function_call) +{ + CHECK( + "{ { let a:u256, b:u256, c:u256, d:u256, f:u256 } { function f(a:u256) -> c:u256, d:u256 { let b:u256, c_1:u256 := f(a) } } }", + "{ { let a:u256, b:u256, c:u256, d:u256, f:u256 } { function f_1(a_1:u256) -> c_1:u256, d_1:u256 { let b_1:u256, c_1_1:u256 := f_1(a_1) } } }" + ); +} + +BOOST_AUTO_TEST_CASE(for_statement) +{ + CHECK( + "{ { let a:u256, b:u256 } { for { let a:u256 } a { a := a } { let b:u256 := a } } }", + "{ { let a:u256, b:u256 } { for { let a_1:u256 } a_1 { a_1 := a_1 } { let b_1:u256 := a_1 } } }" + ); +} + +BOOST_AUTO_TEST_CASE(switch_statement) +{ + CHECK( + "{ { let a:u256, b:u256, c:u256 } { let a:u256 switch a case 0:u256 { let b:u256 := a } default { let c:u256 := a } } }", + "{ { let a:u256, b:u256, c:u256 } { let a_1:u256 switch a_1 case 0:u256 { let b_1:u256 := a_1 } default { let c_1:u256 := a_1 } } }" + ); +} + +BOOST_AUTO_TEST_CASE(if_statement) +{ + CHECK( + "{ { let a:u256, b:u256, c:u256 } { let a:bool if a { let b:bool := a } } }", + "{ { let a:u256, b:u256, c:u256 } { let a_1:bool if a_1 { let b_1:bool := a_1 } } }" + ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libjulia/FunctionGrouper.cpp b/test/libjulia/FunctionGrouper.cpp new file mode 100644 index 00000000..78f382cb --- /dev/null +++ b/test/libjulia/FunctionGrouper.cpp @@ -0,0 +1,85 @@ +/* + 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/>. +*/ +/** + * @date 2017 + * Unit tests for the iulia function grouper. + */ + +#include <test/libjulia/Common.h> + +#include <libjulia/optimiser/FunctionGrouper.h> + +#include <libsolidity/inlineasm/AsmPrinter.h> + +#include <boost/test/unit_test.hpp> + +using namespace std; +using namespace dev::julia; +using namespace dev::julia::test; +using namespace dev::solidity; + +#define CHECK(_original, _expectation)\ +do\ +{\ + assembly::AsmPrinter p(true);\ + Block b = disambiguate(_original);\ + (FunctionGrouper{})(b);\ + string result = p(b);\ + BOOST_CHECK_EQUAL(result, format(_expectation));\ +}\ +while(false) + +BOOST_AUTO_TEST_SUITE(IuliaFunctionGrouper) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + CHECK("{ }", "{ { } }"); +} + +BOOST_AUTO_TEST_CASE(single_fun) +{ + CHECK( + "{ let a:u256 function f() {} }", + "{ { let a:u256 } function f() {} }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_fun_mixed) +{ + CHECK( + "{ let a:u256 function f() { let b:u256 } let c:u256 function g() { let d:u256 } let e:u256 }", + "{ { let a:u256 let c:u256 let e:u256 } function f() { let b:u256 } function g() { let d:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(nested_fun) +{ + CHECK( + "{ let a:u256 function f() { let b:u256 function g() { let c:u256} let d:u256 } }", + "{ { let a:u256 } function f() { let b:u256 function g() { let c:u256} let d:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(empty_block) +{ + CHECK( + "{ let a:u256 { } function f() -> x:bool { let b:u256 := 4:u256 {} for {} f() {} {} } }", + "{ { let a:u256 { } } function f() -> x:bool { let b:u256 := 4:u256 {} for {} f() {} {} } }" + ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libjulia/FunctionHoister.cpp b/test/libjulia/FunctionHoister.cpp new file mode 100644 index 00000000..3d6fff85 --- /dev/null +++ b/test/libjulia/FunctionHoister.cpp @@ -0,0 +1,85 @@ +/* + 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/>. +*/ +/** + * @date 2017 + * Unit tests for the iulia function hoister. + */ + +#include <test/libjulia/Common.h> + +#include <libjulia/optimiser/FunctionHoister.h> + +#include <libsolidity/inlineasm/AsmPrinter.h> + +#include <boost/test/unit_test.hpp> + +using namespace std; +using namespace dev::julia; +using namespace dev::julia::test; +using namespace dev::solidity; + +#define CHECK(_original, _expectation)\ +do\ +{\ + assembly::AsmPrinter p(true);\ + Block b = disambiguate(_original);\ + (FunctionHoister{})(b);\ + string result = p(b);\ + BOOST_CHECK_EQUAL(result, format(_expectation));\ +}\ +while(false) + +BOOST_AUTO_TEST_SUITE(IuliaFunctionHoister) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + CHECK("{ }", "{ }"); +} + +BOOST_AUTO_TEST_CASE(single_fun) +{ + CHECK( + "{ let a:u256 function f() {} }", + "{ let a:u256 function f() {} }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_fun_mixed) +{ + CHECK( + "{ let a:u256 function f() { let b:u256 } let c:u256 function g() { let d:u256 } let e:u256 }", + "{ let a:u256 let c:u256 let e:u256 function f() { let b:u256 } function g() { let d:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(nested_fun) +{ + CHECK( + "{ let a:u256 function f() { let b:u256 function g() { let c:u256} let d:u256 } }", + "{ let a:u256 function g() { let c:u256 } function f() { let b:u256 let d:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(empty_block) +{ + CHECK( + "{ let a:u256 { } function f() -> x:bool { let b:u256 := 4:u256 {} for {} f() {} {} } }", + "{ let a:u256 function f() -> x:bool { let b:u256 := 4:u256 for {} f() {} {} } }" + ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index 9aa325a4..a8a41b3c 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -52,11 +52,11 @@ bool parse(string const& _source, ErrorReporter& errorReporter) try { auto scanner = make_shared<Scanner>(CharStream(_source)); - auto parserResult = assembly::Parser(errorReporter, true).parse(scanner); + auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::IULIA).parse(scanner); if (parserResult) { assembly::AsmAnalysisInfo analysisInfo; - return (assembly::AsmAnalyzer(analysisInfo, errorReporter, true)).analyze(*parserResult); + return (assembly::AsmAnalyzer(analysisInfo, errorReporter, assembly::AsmFlavour::IULIA)).analyze(*parserResult); } } catch (FatalError const&) @@ -196,6 +196,14 @@ BOOST_AUTO_TEST_CASE(empty_call) CHECK_ERROR("{ () }", ParserError, "Literal or identifier expected."); } +BOOST_AUTO_TEST_CASE(tokens_as_identifers) +{ + BOOST_CHECK(successParse("{ let return:u256 := 1:u256 }")); + BOOST_CHECK(successParse("{ let byte:u256 := 1:u256 }")); + BOOST_CHECK(successParse("{ let address:u256 := 1:u256 }")); + BOOST_CHECK(successParse("{ let bool:u256 := 1:u256 }")); +} + BOOST_AUTO_TEST_CASE(lacking_types) { CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected token Identifier got 'Assign'"); diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp new file mode 100644 index 00000000..15c04b37 --- /dev/null +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -0,0 +1,794 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for Solidity's ABI decoder. + */ + +#include <functional> +#include <string> +#include <tuple> +#include <boost/test/unit_test.hpp> +#include <libsolidity/interface/Exceptions.h> +#include <test/libsolidity/SolidityExecutionFramework.h> + +#include <test/libsolidity/ABITestsCommon.h> + +using namespace std; +using namespace std::placeholders; +using namespace dev::test; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework) + +BOOST_AUTO_TEST_CASE(both_encoders_macro) +{ + // This tests that the "both decoders macro" at least runs twice and + // modifies the source. + string sourceCode; + int runs = 0; + BOTH_ENCODERS(runs++;) + BOOST_CHECK(sourceCode == NewEncoderPragma); + BOOST_CHECK_EQUAL(runs, 2); +} + +BOOST_AUTO_TEST_CASE(value_types) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool e, C g) public returns (uint) { + if (a != 1) return 1; + if (b != 2) return 2; + if (c != 3) return 3; + if (d != 4) return 4; + if (x != "abc") return 5; + if (e != true) return 6; + if (g != this) return 7; + return 20; + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction( + "f(uint256,uint16,uint24,int24,bytes3,bool,address)", + 1, 2, 3, 4, string("abc"), true, u160(m_contractAddress) + ), encodeArgs(u256(20))); + ) +} + +BOOST_AUTO_TEST_CASE(enums) +{ + string sourceCode = R"( + contract C { + enum E { A, B } + function f(E e) public pure returns (uint x) { + assembly { x := e } + } + } + )"; + bool newDecoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint8)", 0), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint8)", 1), encodeArgs(u256(1))); + // The old decoder was not as strict about enums + ABI_CHECK(callContractFunction("f(uint8)", 2), (newDecoder ? encodeArgs() : encodeArgs(2))); + ABI_CHECK(callContractFunction("f(uint8)", u256(-1)), (newDecoder? encodeArgs() : encodeArgs(u256(0xff)))); + newDecoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(cleanup) +{ + string sourceCode = R"( + contract C { + function f(uint16 a, int16 b, address c, bytes3 d, bool e) + public pure returns (uint v, uint w, uint x, uint y, uint z) { + assembly { v := a w := b x := c y := d z := e} + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(uint16,int16,address,bytes3,bool)", 1, 2, 3, "a", true), + encodeArgs(u256(1), u256(2), u256(3), string("a"), true) + ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(4) + ), + encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true) + ); + ) +} + +BOOST_AUTO_TEST_CASE(fixed_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint16[3] a, uint16[2][3] b, uint i, uint j, uint k) + public pure returns (uint, uint) { + return (a[i], b[j][k]); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 1, 2, 3, + 11, 12, + 21, 22, + 31, 32, + 1, 2, 1 + ); + ABI_CHECK( + callContractFunction("f(uint16[3],uint16[2][3],uint256,uint256,uint256)", args), + encodeArgs(u256(2), u256(32)) + ); + ) +} + +BOOST_AUTO_TEST_CASE(dynamic_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16[] b, uint c) + public pure returns (uint, uint, uint) { + return (b.length, b[a], c); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + 7, + 11, 12, 13, 14, 15, 16, 17 + ); + ABI_CHECK( + callContractFunction("f(uint256,uint16[],uint256)", args), + encodeArgs(u256(7), u256(17), u256(9)) + ); + ) +} + +BOOST_AUTO_TEST_CASE(dynamic_nested_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16[][] b, uint[2][][3] c, uint d) + public pure returns (uint, uint, uint, uint, uint, uint, uint) { + return (a, b.length, b[1].length, b[1][1], c[1].length, c[1][1][1], d); + } + function test() view returns (uint, uint, uint, uint, uint, uint, uint) { + uint16[][] memory b = new uint16[][](3); + b[0] = new uint16[](2); + b[0][0] = 0x55; + b[0][1] = 0x56; + b[1] = new uint16[](4); + b[1][0] = 0x65; + b[1][1] = 0x66; + b[1][2] = 0x67; + b[1][3] = 0x68; + + uint[2][][3] memory c; + c[0] = new uint[2][](1); + c[0][0][1] = 0x75; + c[1] = new uint[2][](5); + c[1][1][1] = 0x85; + + return this.f(0x12, b, c, 0x13); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 0x12, 4 * 0x20, 17 * 0x20, 0x13, + // b + 3, 3 * 0x20, 6 * 0x20, 11 * 0x20, + 2, 85, 86, + 4, 101, 102, 103, 104, + 0, + // c + 3 * 0x20, 6 * 0x20, 17 * 0x20, + 1, 0, 117, + 5, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, + 0 + ); + + bytes expectation = encodeArgs(0x12, 3, 4, 0x66, 5, 0x85, 0x13); + ABI_CHECK(callContractFunction("test()"), expectation); + ABI_CHECK(callContractFunction("f(uint256,uint16[][],uint256[2][][3],uint256)", args), expectation); + ) +} + +BOOST_AUTO_TEST_CASE(byte_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, bytes b, uint c) + public pure returns (uint, uint, byte, uint) { + return (a, b.length, b[3], c); + } + + function f_external(uint a, bytes b, uint c) + external pure returns (uint, uint, byte, uint) { + return (a, b.length, b[3], c); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + 7, "abcdefg" + ); + ABI_CHECK( + callContractFunction("f(uint256,bytes,uint256)", args), + encodeArgs(u256(6), u256(7), "d", 9) + ); + ABI_CHECK( + callContractFunction("f_external(uint256,bytes,uint256)", args), + encodeArgs(u256(6), u256(7), "d", 9) + ); + ) +} + +BOOST_AUTO_TEST_CASE(calldata_arrays_too_large) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint[] b, uint c) external pure returns (uint) { + return 7; + } + } + )"; + bool newEncoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + (u256(1) << 255) + 2, 1, 2 + ); + ABI_CHECK( + callContractFunction("f(uint256,uint256[],uint256)", args), + newEncoder ? encodeArgs() : encodeArgs(7) + ); + newEncoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(decode_from_memory_simple) +{ + string sourceCode = R"( + contract C { + uint public _a; + uint[] public _b; + function C(uint a, uint[] b) { + _a = a; + _b = b; + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C", encodeArgs( + 7, 0x40, + // b + 3, 0x21, 0x22, 0x23 + )); + ABI_CHECK(callContractFunction("_a()"), encodeArgs(7)); + ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21)); + ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22)); + ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23)); + ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(decode_function_type) +{ + string sourceCode = R"( + contract D { + function () external returns (uint) public _a; + function D(function () external returns (uint) a) { + _a = a; + } + } + contract C { + function f() returns (uint) { + return 3; + } + function g(function () external returns (uint) _f) returns (uint) { + return _f(); + } + // uses "decode from memory" + function test1() returns (uint) { + D d = new D(this.f); + return d._a()(); + } + // uses "decode from calldata" + function test2() returns (uint) { + return this.g(this.f); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test1()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test2()"), encodeArgs(3)); + ) +} + +BOOST_AUTO_TEST_CASE(decode_function_type_array) +{ + string sourceCode = R"( + contract D { + function () external returns (uint)[] public _a; + function D(function () external returns (uint)[] a) { + _a = a; + } + } + contract E { + function () external returns (uint)[3] public _a; + function E(function () external returns (uint)[3] a) { + _a = a; + } + } + contract C { + function f1() public returns (uint) { + return 1; + } + function f2() public returns (uint) { + return 2; + } + function f3() public returns (uint) { + return 3; + } + function g(function () external returns (uint)[] _f, uint i) public returns (uint) { + return _f[i](); + } + function h(function () external returns (uint)[3] _f, uint i) public returns (uint) { + return _f[i](); + } + // uses "decode from memory" + function test1_dynamic() public returns (uint) { + var x = new function() external returns (uint)[](3); + x[0] = this.f1; + x[1] = this.f2; + x[2] = this.f3; + D d = new D(x); + return d._a(2)(); + } + function test1_static() public returns (uint) { + E e = new E([this.f1, this.f2, this.f3]); + return e._a(2)(); + } + // uses "decode from calldata" + function test2_dynamic() public returns (uint) { + var x = new function() external returns (uint)[](3); + x[0] = this.f1; + x[1] = this.f2; + x[2] = this.f3; + return this.g(x, 0); + } + function test2_static() public returns (uint) { + return this.h([this.f1, this.f2, this.f3], 0); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test1_static()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test1_dynamic()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test2_static()"), encodeArgs(1)); + ABI_CHECK(callContractFunction("test2_dynamic()"), encodeArgs(1)); + ) +} + +BOOST_AUTO_TEST_CASE(decode_from_memory_complex) +{ + string sourceCode = R"( + contract C { + uint public _a; + uint[] public _b; + bytes[2] public _c; + function C(uint a, uint[] b, bytes[2] c) { + _a = a; + _b = b; + _c = c; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C", encodeArgs( + 7, 0x60, 7 * 0x20, + // b + 3, 0x21, 0x22, 0x23, + // c + 0x40, 0x80, + 8, string("abcdefgh"), + 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ") + )); + ABI_CHECK(callContractFunction("_a()"), encodeArgs(7)); + ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21)); + ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22)); + ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23)); + ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs()); + ABI_CHECK(callContractFunction("_c(uint256)", 0), encodeArgs(0x20, 8, string("abcdefgh"))); + ABI_CHECK(callContractFunction("_c(uint256)", 1), encodeArgs(0x20, 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"))); + ABI_CHECK(callContractFunction("_c(uint256)", 2), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_value_type) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint b) public pure returns (uint) { return a; } + } + )"; + bool newDecoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint256,uint256)", 1, 2), encodeArgs(1)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(64, 0)), encodeArgs(0)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), newDecoder ? encodeArgs() : encodeArgs(0)); + newDecoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(short_input_array) +{ + string sourceCode = R"( + contract C { + function f(uint[] a) public pure returns (uint) { return 7; } + } + )"; + bool newDecoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), newDecoder ? encodeArgs() : encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), newDecoder ? encodeArgs() : encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(32, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 2, 5, 6)), encodeArgs(7)); + newDecoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(short_dynamic_input_array) +{ + string sourceCode = R"( + contract C { + function f(bytes[1] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[1])", encodeArgs(0x20)), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_bytes) +{ + string sourceCode = R"( + contract C { + function e(bytes a) public pure returns (uint) { return 7; } + function f(bytes[] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(5, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(6, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(7, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(8, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(5, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(6, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(7, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(8, 0)), encodeArgs(7)); + ) +} + +BOOST_AUTO_TEST_CASE(cleanup_int_inside_arrays) +{ + string sourceCode = R"( + contract C { + enum E { A, B } + function f(uint16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + function g(int16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + function h(E[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, 7), encodeArgs(7)); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, 7), encodeArgs(7)); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256("0xffff"))); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256(-1))); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0x1ffff")), encodeArgs(u256("0xffff"))); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0x10fff")), encodeArgs(u256("0x0fff"))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 0), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 1), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 2), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(storage_ptr) +{ + string sourceCode = R"( + library L { + struct S { uint x; uint y; } + function f(uint[] storage r, S storage s) public returns (uint, uint, uint, uint) { + r[2] = 8; + s.x = 7; + return (r[0], r[1], s.x, s.y); + } + } + contract C { + uint8 x = 3; + L.S s; + uint[] r; + function f() public returns (uint, uint, uint, uint, uint, uint) { + r.length = 6; + r[0] = 1; + r[1] = 2; + r[2] = 3; + s.x = 11; + s.y = 12; + var (a, b, c, d) = L.f(r, s); + return (r[2], s.x, a, b, c, d); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "L"); + compileAndRun(sourceCode, 0, "C", bytes(), map<string, Address>{{"L", m_contractAddress}}); + ABI_CHECK(callContractFunction("f()"), encodeArgs(8, 7, 1, 2, 7, 12)); + ) +} + +BOOST_AUTO_TEST_CASE(struct_simple) +{ + string sourceCode = R"( + contract C { + struct S { uint a; uint8 b; uint8 c; bytes2 d; } + function f(S s) public pure returns (uint a, uint b, uint c, uint d) { + a = s.a; + b = s.b; + c = s.c; + d = uint(s.d); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f((uint256,uint8,uint8,bytes2))", 1, 2, 3, "ab"), encodeArgs(1, 2, 3, 'a' * 0x100 + 'b')); + ) +} + +BOOST_AUTO_TEST_CASE(struct_cleanup) +{ + string sourceCode = R"( + contract C { + struct S { int16 a; uint8 b; bytes2 c; } + function f(S s) public pure returns (uint a, uint b, uint c) { + assembly { + a := mload(s) + b := mload(add(s, 0x20)) + c := mload(add(s, 0x40)) + } + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK( + callContractFunction("f((int16,uint8,bytes2))", 0xff010, 0xff0002, "abcd"), + encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010"), 2, "ab") + ); + ) +} + +BOOST_AUTO_TEST_CASE(struct_short) +{ + string sourceCode = R"( + contract C { + struct S { int a; uint b; bytes16 c; } + function f(S s) public pure returns (S q) { + q = s; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK( + callContractFunction("f((int256,uint256,bytes16))", 0xff010, 0xff0002, "abcd"), + encodeArgs(0xff010, 0xff0002, "abcd") + ); + ABI_CHECK( + callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(32, 0)), + encodeArgs(0xff010, 0xff0002, 0) + ); + ABI_CHECK( + callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(31, 0)), + encodeArgs() + ); + ) +} + +BOOST_AUTO_TEST_CASE(struct_function) +{ + string sourceCode = R"( + contract C { + struct S { function () external returns (uint) f; uint b; } + function f(S s) public returns (uint, uint) { + return (s.f(), s.b); + } + function test() public returns (uint, uint) { + return this.f(S(this.g, 3)); + } + function g() public returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test()"), encodeArgs(7, 3)); + ) +} + +BOOST_AUTO_TEST_CASE(empty_struct) +{ + string sourceCode = R"( + contract C { + struct S { } + function f(uint a, S s, uint b) public pure returns (uint x, uint y) { + assembly { x := a y := b } + } + function g() public returns (uint, uint) { + return this.f(7, S(), 8); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256,(),uint256)", 7, 8), encodeArgs(7, 8)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(7, 8)); + ) +} + +BOOST_AUTO_TEST_CASE(mediocre_struct) +{ + string sourceCode = R"( + contract C { + struct S { C c; } + function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) { + r1 = a; + r2 = s1[0].c; + r3 = b; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address)[2],uint256)"; + ABI_CHECK(callContractFunction(sig, + 7, u256(u160(m_contractAddress)), 0, 8 + ), encodeArgs(7, u256(u160(m_contractAddress)), 8)); + ) +} + +BOOST_AUTO_TEST_CASE(mediocre2_struct) +{ + string sourceCode = R"( + contract C { + struct S { C c; uint[] x; } + function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) { + r1 = a; + r2 = s1[0].c; + r3 = b; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address,uint256[])[2],uint256)"; + ABI_CHECK(callContractFunction(sig, + 7, 0x60, 8, + 0x40, 7 * 0x20, + u256(u160(m_contractAddress)), 0x40, + 2, 0x11, 0x12, + 0x99, 0x40, + 4, 0x31, 0x32, 0x34, 0x35 + ), encodeArgs(7, u256(u160(m_contractAddress)), 8)); + ) +} + +BOOST_AUTO_TEST_CASE(complex_struct) +{ + string sourceCode = R"( + contract C { + enum E {A, B, C} + struct T { uint x; E e; uint8 y; } + struct S { C c; T[] t;} + function f(uint a, S[2] s1, S[] s2, uint b) public returns + (uint r1, C r2, uint r3, uint r4, C r5, uint r6, E r7, uint8 r8) { + r1 = a; + r2 = s1[0].c; + r3 = b; + r4 = s2.length; + r5 = s2[1].c; + r6 = s2[1].t.length; + r7 = s2[1].t[1].e; + r8 = s2[1].t[1].y; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address,(uint256,uint8,uint8)[])[2],(address,(uint256,uint8,uint8)[])[],uint256)"; + bytes args = encodeArgs( + 7, 0x80, 0x1e0, 8, + // S[2] s1 + 0x40, + 0x100, + // S s1[0] + u256(u160(m_contractAddress)), + 0x40, + // T s1[0].t + 1, // length + // s1[0].t[0] + 0x11, 1, 0x12, + // S s1[1] + 0, 0x40, + // T s1[1].t + 0, + // S[] s2 (0x1e0) + 2, // length + 0x40, 0xa0, + // S s2[0] + 0, 0x40, 0, + // S s2[1] + 0x1234, 0x40, + // s2[1].t + 3, // length + 0, 0, 0, + 0x21, 2, 0x22, + 0, 0, 0 + ); + ABI_CHECK(callContractFunction(sig, args), encodeArgs(7, u256(u160(m_contractAddress)), 8, 2, 0x1234, 3, 2, 0x22)); + // invalid enum value + args.data()[0x20 * 28] = 3; + ABI_CHECK(callContractFunction(sig, args), encodeArgs()); + ) +} + + + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index af51edcc..49db9ce1 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -25,6 +25,8 @@ #include <libsolidity/interface/Exceptions.h> #include <test/libsolidity/SolidityExecutionFramework.h> +#include <test/libsolidity/ABITestsCommon.h> + using namespace std; using namespace std::placeholders; using namespace dev::test; @@ -42,20 +44,6 @@ namespace test BOOST_CHECK_EQUAL(toHex(m_logs[0].data), toHex(DATA)); \ } while (false) -static string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n"; - -#define NEW_ENCODER(CODE) \ -{ \ - sourceCode = NewEncoderPragma + sourceCode; \ - { CODE } \ -} - -#define BOTH_ENCODERS(CODE) \ -{ \ - { CODE } \ - NEW_ENCODER(CODE) \ -} - BOOST_FIXTURE_TEST_SUITE(ABIEncoderTest, SolidityExecutionFramework) BOOST_AUTO_TEST_CASE(both_encoders_macro) @@ -74,7 +62,7 @@ BOOST_AUTO_TEST_CASE(value_types) string sourceCode = R"( contract C { event E(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool, C); - function f() { + function f() public { bytes6 x = hex"1bababababa2"; bool b; assembly { b := 7 } @@ -98,7 +86,7 @@ BOOST_AUTO_TEST_CASE(string_literal) string sourceCode = R"( contract C { event E(string, bytes20, string); - function f() { + function f() public { E("abcdef", "abcde", "abcdefabcdefgehabcabcasdfjklabcdefabcedefghabcabcasdfjklabcdefabcdefghabcabcasdfjklabcdeefabcdefghabcabcasdefjklabcdefabcdefghabcabcasdfjkl"); } } @@ -120,7 +108,7 @@ BOOST_AUTO_TEST_CASE(enum_type_cleanup) string sourceCode = R"( contract C { enum E { A, B } - function f(uint x) returns (E en) { + function f(uint x) public returns (E en) { assembly { en := x } } } @@ -138,7 +126,7 @@ BOOST_AUTO_TEST_CASE(conversion) string sourceCode = R"( contract C { event E(bytes4, bytes4, uint16, uint8, int16, int8); - function f() { + function f() public { bytes2 x; assembly { x := 0xf1f2f3f400000000000000000000000000000000000000000000000000000000 } uint8 a; uint16 b = 0x1ff; @@ -164,7 +152,7 @@ BOOST_AUTO_TEST_CASE(memory_array_one_dim) string sourceCode = R"( contract C { event E(uint a, int16[] b, uint c); - function f() { + function f() public { int16[] memory x = new int16[](3); assembly { for { let i := 0 } lt(i, 3) { i := add(i, 1) } { @@ -191,7 +179,7 @@ BOOST_AUTO_TEST_CASE(memory_array_two_dim) string sourceCode = R"( contract C { event E(uint a, int16[][2] b, uint c); - function f() { + function f() public { int16[][2] memory x; x[0] = new int16[](3); x[1] = new int16[](2); @@ -216,7 +204,7 @@ BOOST_AUTO_TEST_CASE(memory_byte_array) string sourceCode = R"( contract C { event E(uint a, bytes[] b, uint c); - function f() { + function f() public { bytes[] memory x = new bytes[](2); x[0] = "abcabcdefghjklmnopqrsuvwabcdefgijklmnopqrstuwabcdefgijklmnoprstuvw"; x[1] = "abcdefghijklmnopqrtuvwabcfghijklmnopqstuvwabcdeghijklmopqrstuvw"; @@ -243,7 +231,7 @@ BOOST_AUTO_TEST_CASE(storage_byte_array) bytes short; bytes long; event E(bytes s, bytes l); - function f() { + function f() public { short = "123456789012345678901234567890a"; long = "ffff123456789012345678901234567890afffffffff123456789012345678901234567890a"; E(short, long); @@ -267,7 +255,7 @@ BOOST_AUTO_TEST_CASE(storage_array) contract C { address[3] addr; event E(address[3] a); - function f() { + function f() public { assembly { sstore(0, sub(0, 1)) sstore(1, sub(0, 2)) @@ -290,7 +278,7 @@ BOOST_AUTO_TEST_CASE(storage_array_dyn) contract C { address[] addr; event E(address[] a); - function f() { + function f() public { addr.push(1); addr.push(2); addr.push(3); @@ -311,7 +299,7 @@ BOOST_AUTO_TEST_CASE(storage_array_compact) contract C { int72[] x; event E(int72[]); - function f() { + function f() public { x.push(-1); x.push(2); x.push(-3); @@ -339,7 +327,7 @@ BOOST_AUTO_TEST_CASE(external_function) contract C { event E(function(uint) external returns (uint), function(uint) external returns (uint)); function(uint) external returns (uint) g; - function f(uint) returns (uint) { + function f(uint) public returns (uint) { g = this.f; E(this.f, g); } @@ -347,7 +335,7 @@ BOOST_AUTO_TEST_CASE(external_function) )"; BOTH_ENCODERS( compileAndRun(sourceCode); - callContractFunction("f(uint256)"); + callContractFunction("f(uint256)", u256(0)); string functionIdF = asString(m_contractAddress.ref()) + asString(FixedHash<4>(dev::keccak256("f(uint256)")).ref()); REQUIRE_LOG_DATA(encodeArgs(functionIdF, functionIdF)); ) @@ -360,7 +348,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup) event E(function(uint) external returns (uint), function(uint) external returns (uint)); // This test relies on the fact that g is stored in slot zero. function(uint) external returns (uint) g; - function f(uint) returns (uint) { + function f(uint) public returns (uint) { function(uint) external returns (uint)[1] memory h; assembly { sstore(0, sub(0, 1)) mstore(h, sub(0, 1)) } E(h[0], g); @@ -369,7 +357,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup) )"; BOTH_ENCODERS( compileAndRun(sourceCode); - callContractFunction("f(uint256)"); + callContractFunction("f(uint256)", u256(0)); REQUIRE_LOG_DATA(encodeArgs(string(24, char(-1)), string(24, char(-1)))); ) } @@ -404,7 +392,7 @@ BOOST_AUTO_TEST_CASE(function_name_collision) // and by the ABI encoder string sourceCode = R"( contract C { - function f(uint x) returns (uint) { + function f(uint x) public returns (uint) { assembly { function abi_encode_t_uint256_to_t_uint256() { mstore(0, 7) @@ -432,7 +420,7 @@ BOOST_AUTO_TEST_CASE(structs) struct T { uint64[2] x; } S s; event e(uint16, S); - function f() returns (uint, S) { + function f() public returns (uint, S) { uint16 x = 7; s.a = 8; s.b = 9; diff --git a/test/libsolidity/ABITestsCommon.h b/test/libsolidity/ABITestsCommon.h new file mode 100644 index 00000000..2ef555f3 --- /dev/null +++ b/test/libsolidity/ABITestsCommon.h @@ -0,0 +1,43 @@ +/* + 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/>. +*/ + +#include <string> + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +static std::string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n"; + +#define NEW_ENCODER(CODE) \ +{ \ + sourceCode = NewEncoderPragma + sourceCode; \ + { CODE } \ +} + +#define BOTH_ENCODERS(CODE) \ +{ \ + { CODE } \ + NEW_ENCODER(CODE) \ +} + +} +} +} // end namespaces diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 358d3c72..59af6d41 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -86,23 +86,59 @@ eth::AssemblyItems compileContract(const string& _sourceCode) return AssemblyItems(); } +void printAssemblyLocations(AssemblyItems const& _items) +{ + auto printRepeated = [](SourceLocation const& _loc, size_t _repetitions) + { + cout << + "\t\tvector<SourceLocation>(" << + _repetitions << + ", SourceLocation(" << + _loc.start << + ", " << + _loc.end << + ", make_shared<string>(\"" << + *_loc.sourceName << + "\"))) +" << endl; + }; + + vector<SourceLocation> locations; + for (auto const& item: _items) + locations.push_back(item.location()); + size_t repetitions = 0; + SourceLocation const* previousLoc = nullptr; + for (size_t i = 0; i < locations.size(); ++i) + { + SourceLocation& loc = locations[i]; + if (previousLoc && *previousLoc == loc) + repetitions++; + else + { + if (previousLoc) + printRepeated(*previousLoc, repetitions); + previousLoc = &loc; + repetitions = 1; + } + } + if (previousLoc) + printRepeated(*previousLoc, repetitions); +} + void checkAssemblyLocations(AssemblyItems const& _items, vector<SourceLocation> const& _locations) { BOOST_CHECK_EQUAL(_items.size(), _locations.size()); for (size_t i = 0; i < min(_items.size(), _locations.size()); ++i) { - BOOST_CHECK_MESSAGE( - _items[i].location() == _locations[i], - "Location mismatch for assembly item " + to_string(i) + ". Found: " + - (_items[i].location().sourceName ? *_items[i].location().sourceName + ":" : "(null source name)") + - to_string(_items[i].location().start) + "-" + - to_string(_items[i].location().end) + ", expected: " + - (_locations[i].sourceName ? *_locations[i].sourceName + ":" : "(null source name)") + - to_string(_locations[i].start) + "-" + - to_string(_locations[i].end)); + if (_items[i].location() != _locations[i]) + { + BOOST_CHECK_MESSAGE(false, "Location mismatch for item " + to_string(i) + ". Found the following locations:"); + printAssemblyLocations(_items); + return; + } } } + } // end anonymous namespace BOOST_AUTO_TEST_SUITE(Assembly) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index e9fb8431..b09eb261 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -51,10 +51,11 @@ boost::optional<Error> parseAndReturnFirstError( string const& _source, bool _assemble = false, bool _allowWarnings = true, + AssemblyStack::Language _language = AssemblyStack::Language::Assembly, AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM ) { - AssemblyStack stack; + AssemblyStack stack(_language); bool success = false; try { @@ -87,22 +88,29 @@ bool successParse( string const& _source, bool _assemble = false, bool _allowWarnings = true, + AssemblyStack::Language _language = AssemblyStack::Language::Assembly, AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM ) { - return !parseAndReturnFirstError(_source, _assemble, _allowWarnings, _machine); + return !parseAndReturnFirstError(_source, _assemble, _allowWarnings, _language, _machine); } -bool successAssemble(string const& _source, bool _allowWarnings = true) +bool successAssemble(string const& _source, bool _allowWarnings = true, AssemblyStack::Language _language = AssemblyStack::Language::Assembly) { - return successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM) && - successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM15); + return + successParse(_source, true, _allowWarnings, _language, AssemblyStack::Machine::EVM) && + successParse(_source, true, _allowWarnings, _language, AssemblyStack::Machine::EVM15); } -Error expectError(std::string const& _source, bool _assemble, bool _allowWarnings = false) +Error expectError( + std::string const& _source, + bool _assemble, + bool _allowWarnings = false, + AssemblyStack::Language _language = AssemblyStack::Language::Assembly +) { - auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings); + auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings, _language); BOOST_REQUIRE(error); return *error; } @@ -120,14 +128,17 @@ void parsePrintCompare(string const& _source, bool _canWarn = false) } -#define CHECK_ERROR(text, assemble, typ, substring, warnings) \ +#define CHECK_ERROR_LANG(text, assemble, typ, substring, warnings, language) \ do \ { \ - Error err = expectError((text), (assemble), warnings); \ + Error err = expectError((text), (assemble), warnings, (language)); \ BOOST_CHECK(err.type() == (Error::Type::typ)); \ BOOST_CHECK(searchErrorMessage(err, (substring))); \ } while(0) +#define CHECK_ERROR(text, assemble, typ, substring, warnings) \ +CHECK_ERROR_LANG(text, assemble, typ, substring, warnings, AssemblyStack::Language::Assembly) + #define CHECK_PARSE_ERROR(text, type, substring) \ CHECK_ERROR(text, false, type, substring, false) @@ -137,6 +148,14 @@ CHECK_ERROR(text, false, type, substring, false) #define CHECK_ASSEMBLE_ERROR(text, type, substring) \ CHECK_ERROR(text, true, type, substring, false) +#define CHECK_STRICT_ERROR(text, type, substring) \ +CHECK_ERROR_LANG(text, false, type, substring, false, AssemblyStack::Language::StrictAssembly) + +#define CHECK_STRICT_WARNING(text, type, substring) \ +CHECK_ERROR(text, false, type, substring, false, AssemblyStack::Language::StrictAssembly) + +#define SUCCESS_STRICT(text) \ +do { successParse((text), false, false, AssemblyStack::Language::StrictAssembly); } while (false) BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly) @@ -266,7 +285,7 @@ BOOST_AUTO_TEST_CASE(if_statement_scope) BOOST_AUTO_TEST_CASE(if_statement_invalid) { - CHECK_PARSE_ERROR("{ if calldatasize {}", ParserError, "Instructions are not supported as conditions for if"); + CHECK_PARSE_ERROR("{ if mload {} }", ParserError, "Expected token \"(\""); BOOST_CHECK("{ if calldatasize() {}"); CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace"); @@ -296,7 +315,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case) BOOST_AUTO_TEST_CASE(switch_invalid_expression) { CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); - CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch"); + CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected token \"(\""); CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } @@ -332,7 +351,7 @@ BOOST_AUTO_TEST_CASE(for_invalid_expression) CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected token LBrace got 'Number'"); CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected token LBrace got 'Number'"); CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected token LBrace got 'Number'"); - CHECK_PARSE_ERROR("{ for {} calldatasize {} {} }", ParserError, "Instructions are not supported as conditions for the for statement."); + CHECK_PARSE_ERROR("{ for {} mload {} {} }", ParserError, "Expected token \"(\""); CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } @@ -455,6 +474,47 @@ BOOST_AUTO_TEST_CASE(multiple_assignment) BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(LooseStrictMode) + +BOOST_AUTO_TEST_CASE(no_opcodes_in_strict) +{ + BOOST_CHECK(successParse("{ pop(callvalue) }")); + BOOST_CHECK(successParse("{ callvalue pop }")); + CHECK_STRICT_ERROR("{ pop(callvalue) }", ParserError, "Non-functional instructions are not allowed in this context."); + CHECK_STRICT_ERROR("{ callvalue pop }", ParserError, "Call or assignment expected"); + SUCCESS_STRICT("{ pop(callvalue()) }"); + BOOST_CHECK(successParse("{ switch callvalue case 0 {} }")); + CHECK_STRICT_ERROR("{ switch callvalue case 0 {} }", ParserError, "Non-functional instructions are not allowed in this context."); +} + +BOOST_AUTO_TEST_CASE(no_labels_in_strict) +{ + BOOST_CHECK(successParse("{ a: }")); + CHECK_STRICT_ERROR("{ a: }", ParserError, "Labels are not supported"); +} + +BOOST_AUTO_TEST_CASE(no_stack_assign_in_strict) +{ + BOOST_CHECK(successParse("{ let x 4 =: x }")); + CHECK_STRICT_ERROR("{ let x 4 =: x }", ParserError, "Call or assignment expected."); +} + +BOOST_AUTO_TEST_CASE(no_dup_swap_in_strict) +{ + BOOST_CHECK(successParse("{ swap1 }")); + CHECK_STRICT_ERROR("{ swap1 }", ParserError, "Call or assignment expected."); + BOOST_CHECK(successParse("{ dup1 pop }")); + CHECK_STRICT_ERROR("{ dup1 pop }", ParserError, "Call or assignment expected."); + BOOST_CHECK(successParse("{ swap2 }")); + CHECK_STRICT_ERROR("{ swap2 }", ParserError, "Call or assignment expected."); + BOOST_CHECK(successParse("{ dup2 pop }")); + CHECK_STRICT_ERROR("{ dup2 pop }", ParserError, "Call or assignment expected."); + CHECK_PARSE_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context"); + CHECK_STRICT_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context"); +} + +BOOST_AUTO_TEST_SUITE_END() + BOOST_AUTO_TEST_SUITE(Printing) BOOST_AUTO_TEST_CASE(print_smoke) @@ -540,7 +600,7 @@ BOOST_AUTO_TEST_CASE(function_calls) function g(a, b, c) { } - g(1, mul(2, address), f(mul(2, caller))) + g(1, mul(2, address()), f(mul(2, caller()))) y() })"; boost::replace_all(source, "\t", " "); @@ -712,8 +772,9 @@ BOOST_AUTO_TEST_CASE(jump_warning) { CHECK_PARSE_WARNING("{ 1 jump }", Warning, "Jump instructions"); CHECK_PARSE_WARNING("{ 1 2 jumpi }", Warning, "Jump instructions"); - CHECK_PARSE_WARNING("{ a: jump(a) }", Warning, "Jump instructions"); - CHECK_PARSE_WARNING("{ a: jumpi(a, 2) }", Warning, "Jump instructions"); + CHECK_PARSE_WARNING("{ jump(44) }", Warning, "Jump instructions"); + CHECK_PARSE_WARNING("{ jumpi(44, 2) }", Warning, "Jump instructions"); + CHECK_PARSE_WARNING("{ a: }", Warning, "Jump instructions"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp index 7dc4808b..0c904c77 100644 --- a/test/libsolidity/JSONCompiler.cpp +++ b/test/libsolidity/JSONCompiler.cpp @@ -23,7 +23,7 @@ #include <boost/test/unit_test.hpp> #include <libdevcore/JSON.h> #include <libsolidity/interface/Version.h> -#include <solc/jsonCompiler.h> +#include <libsolc/libsolc.h> #include "../Metadata.h" #include "../TestHelper.h" diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 667d666b..2a1609cc 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -94,6 +94,7 @@ BOOST_AUTO_TEST_CASE(warn_on_typecast) BOOST_AUTO_TEST_CASE(warn_on_struct) { string text = R"( + pragma experimental ABIEncoderV2; contract C { struct A { uint a; uint b; } function f() public pure returns (A) { @@ -167,9 +168,9 @@ BOOST_AUTO_TEST_CASE(function_call_does_not_clear_local_vars) CHECK_SUCCESS_NO_WARNINGS(text); } -BOOST_AUTO_TEST_CASE(branches_clear_variables) +BOOST_AUTO_TEST_CASE(branches_merge_variables) { - // Only clears accessed variables + // Branch does not touch variable a string text = R"( contract C { function f(uint x) public pure { @@ -181,7 +182,7 @@ BOOST_AUTO_TEST_CASE(branches_clear_variables) } )"; CHECK_SUCCESS_NO_WARNINGS(text); - // It is just a plain clear and will not combine branches. + // Positive branch touches variable a, but assertion should still hold. text = R"( contract C { function f(uint x) public pure { @@ -193,8 +194,8 @@ BOOST_AUTO_TEST_CASE(branches_clear_variables) } } )"; - CHECK_WARNING(text, "Assertion violation happens here"); - // Clear also works on the else branch + CHECK_SUCCESS_NO_WARNINGS(text); + // Negative branch touches variable a, but assertion should still hold. text = R"( contract C { function f(uint x) public pure { @@ -207,8 +208,8 @@ BOOST_AUTO_TEST_CASE(branches_clear_variables) } } )"; - CHECK_WARNING(text, "Assertion violation happens here"); - // Variable is not cleared, if it is only read. + CHECK_SUCCESS_NO_WARNINGS(text); + // Variable is not merged, if it is only read. text = R"( contract C { function f(uint x) public pure { @@ -223,6 +224,36 @@ BOOST_AUTO_TEST_CASE(branches_clear_variables) } )"; CHECK_SUCCESS_NO_WARNINGS(text); + // Variable is reset in both branches + text = R"( + contract C { + function f(uint x) public pure { + uint a = 2; + if (x > 10) { + a = 3; + } else { + a = 3; + } + assert(a == 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + // Variable is reset in both branches + text = R"( + contract C { + function f(uint x) public pure { + uint a = 2; + if (x > 10) { + a = 3; + } else { + a = 4; + } + assert(a >= 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); } BOOST_AUTO_TEST_CASE(branches_assert_condition) @@ -261,7 +292,7 @@ BOOST_AUTO_TEST_CASE(branches_assert_condition) CHECK_SUCCESS_NO_WARNINGS(text); } -BOOST_AUTO_TEST_CASE(ways_to_clear_variables) +BOOST_AUTO_TEST_CASE(ways_to_merge_variables) { string text = R"( contract C { @@ -274,6 +305,7 @@ BOOST_AUTO_TEST_CASE(ways_to_clear_variables) } } )"; + CHECK_WARNING(text, "Assertion violation happens here"); text = R"( contract C { function f(uint x) public pure { @@ -351,9 +383,9 @@ BOOST_AUTO_TEST_CASE(while_loop_simple) // Check that side-effects of condition are taken into account text = R"( contract C { - function f(uint x) public pure { + function f(uint x, uint y) public pure { x = 7; - while ((x = 5) > 0) { + while ((x = y) > 0) { } assert(x == 7); } @@ -458,6 +490,100 @@ BOOST_AUTO_TEST_CASE(for_loop) CHECK_WARNING(text, "Assertion violation"); } +BOOST_AUTO_TEST_CASE(division) +{ + string text = R"( + contract C { + function f(uint x, uint y) public pure returns (uint) { + return x / y; + } + } + )"; + CHECK_WARNING(text, "Division by zero"); + text = R"( + contract C { + function f(uint x, uint y) public pure returns (uint) { + require(y != 0); + return x / y; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(int x, int y) public pure returns (int) { + require(y != 0); + return x / y; + } + } + )"; + CHECK_WARNING(text, "Overflow"); + text = R"( + contract C { + function f(int x, int y) public pure returns (int) { + require(y != 0); + require(y != -1); + return x / y; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(division_truncates_correctly) +{ + string text = R"( + contract C { + function f(uint x, uint y) public pure { + x = 7; + y = 2; + assert(x / y == 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(int x, int y) public pure { + x = 7; + y = 2; + assert(x / y == 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(int x, int y) public pure { + x = -7; + y = 2; + assert(x / y == -3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(int x, int y) public pure { + x = 7; + y = -2; + assert(x / y == -3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(int x, int y) public pure { + x = -7; + y = -2; + assert(x / y == 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 33962730..26bfb6d0 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -942,6 +942,7 @@ BOOST_AUTO_TEST_CASE(function_type) BOOST_AUTO_TEST_CASE(return_structs) { char const* text = R"( + pragma experimental ABIEncoderV2; contract C { struct S { uint a; T[] sub; } struct T { uint[2] x; } @@ -991,6 +992,7 @@ BOOST_AUTO_TEST_CASE(return_structs) BOOST_AUTO_TEST_CASE(return_structs_with_contracts) { char const* text = R"( + pragma experimental ABIEncoderV2; contract C { struct S { C[] x; C y; } function f() returns (S s, C c) { @@ -1090,6 +1092,7 @@ BOOST_AUTO_TEST_CASE(event_structs) BOOST_AUTO_TEST_CASE(structs_in_libraries) { char const* text = R"( + pragma experimental ABIEncoderV2; library L { struct S { uint a; T[] sub; bytes b; } struct T { uint[2] x; } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 05dc9ba3..f5f7e64a 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2971,7 +2971,7 @@ BOOST_AUTO_TEST_CASE(event_no_arguments) { char const* sourceCode = R"( contract ClientReceipt { - event Deposit; + event Deposit(); function deposit() { Deposit(); } @@ -3013,7 +3013,7 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) { char const* sourceCode = R"( contract ClientReceipt { - event Deposit; + event Deposit(); event Deposit(address _addr); event Deposit(address _addr, uint _amount); function deposit() returns (uint) { @@ -3059,7 +3059,7 @@ BOOST_AUTO_TEST_CASE(events_with_same_name_inherited) { char const* sourceCode = R"( contract A { - event Deposit; + event Deposit(); } contract B { diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 97d359e8..eb6a440e 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -602,6 +602,7 @@ BOOST_AUTO_TEST_CASE(enum_external_type) BOOST_AUTO_TEST_CASE(external_structs) { char const* text = R"( + pragma experimental ABIEncoderV2; contract Test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } struct Empty {} @@ -629,6 +630,7 @@ BOOST_AUTO_TEST_CASE(external_structs) BOOST_AUTO_TEST_CASE(external_structs_in_libraries) { char const* text = R"( + pragma experimental ABIEncoderV2; library Test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } struct Empty {} @@ -2107,7 +2109,7 @@ BOOST_AUTO_TEST_CASE(array_with_nonconstant_length) function f(uint a) public { uint8[a] x; } } )"; - CHECK_ERROR(text, TypeError, "Identifier must be declared constant."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_with_negative_length) @@ -3511,6 +3513,7 @@ BOOST_AUTO_TEST_CASE(using_for_not_used) BOOST_AUTO_TEST_CASE(library_memory_struct) { char const* text = R"( + pragma experimental ABIEncoderV2; library c { struct S { uint x; } function f() public returns (S ) {} @@ -3605,6 +3608,20 @@ BOOST_AUTO_TEST_CASE(invalid_args_creating_memory_array) CHECK_ERROR(text, TypeError, "Wrong argument count for function call: 0 arguments given but expected 1."); } +BOOST_AUTO_TEST_CASE(invalid_args_creating_struct) +{ + char const* text = R"( + contract C { + struct S { uint a; uint b; } + + function f() public { + var s = S({a: 1}); + } + } + )"; + CHECK_ERROR(text, TypeError, "Wrong argument count for struct constructor: 1 arguments given but expected 2."); +} + BOOST_AUTO_TEST_CASE(function_overload_array_type) { char const* text = R"( @@ -4395,7 +4412,7 @@ BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_signed_fixed_type) } } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_unsigned_fixed_type) @@ -4407,7 +4424,7 @@ BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_unsigned_fixed_type) } } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(rational_to_bytes_implicit_conversion) @@ -5696,6 +5713,7 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor) BOOST_AUTO_TEST_CASE(return_structs) { char const* text = R"( + pragma experimental ABIEncoderV2; contract C { struct S { uint a; T[] sub; } struct T { uint[] x; } @@ -7250,7 +7268,7 @@ BOOST_AUTO_TEST_CASE(array_length_too_large) uint[8**90] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_length_not_convertible_to_integer) @@ -7260,7 +7278,7 @@ BOOST_AUTO_TEST_CASE(array_length_not_convertible_to_integer) uint[true] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_length_constant_var) @@ -7282,7 +7300,7 @@ BOOST_AUTO_TEST_CASE(array_length_non_integer_constant_var) uint[LEN] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_length_cannot_be_function) @@ -7293,7 +7311,7 @@ BOOST_AUTO_TEST_CASE(array_length_cannot_be_function) uint[f] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_length_can_be_recursive_constant) @@ -7317,7 +7335,7 @@ BOOST_AUTO_TEST_CASE(array_length_cannot_be_function_call) uint[LEN] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_length_const_cannot_be_fractional) @@ -7366,7 +7384,7 @@ BOOST_AUTO_TEST_CASE(array_length_cannot_be_constant_function_parameter) } } )"; - CHECK_ERROR(text, TypeError, "Constant identifier declaration must have a constant value."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_length_with_cyclic_constant) @@ -7405,7 +7423,7 @@ BOOST_AUTO_TEST_CASE(array_length_with_pure_functions) uint[LEN] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); } BOOST_AUTO_TEST_CASE(array_length_invalid_expression) @@ -7415,25 +7433,25 @@ BOOST_AUTO_TEST_CASE(array_length_invalid_expression) uint[-true] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid constant expression."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); text = R"( contract C { uint[true/1] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid constant expression."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); text = R"( contract C { uint[1/true] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid constant expression."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); text = R"( contract C { uint[1.111111E1111111111111] ids; } )"; - CHECK_ERROR(text, TypeError, "Invalid literal value."); + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal or constant expression."); text = R"( contract C { uint[3/0] ids; diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 72473c3e..861e6408 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -960,6 +960,16 @@ BOOST_AUTO_TEST_CASE(event_arguments_indexed) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(event_with_no_argument_list_fails) +{ + char const* text = R"( + contract c { + event e; + } + )"; + CHECK_PARSE_ERROR(text, "Expected token LParen got 'Semicolon'"); +} + BOOST_AUTO_TEST_CASE(visibility_specifiers) { char const* text = R"( diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 4504946b..e48624e5 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -179,6 +179,14 @@ BOOST_AUTO_TEST_CASE(basic_compilation) "fileA": { "content": "contract A { }" } + }, + "settings": { + "outputSelection": { + "fileA": { + "A": [ "abi", "devdoc", "userdoc", "evm.bytecode", "evm.assembly", "evm.gasEstimates", "metadata" ], + "": [ "legacyAST" ] + } + } } } )"; @@ -226,6 +234,46 @@ BOOST_AUTO_TEST_CASE(basic_compilation) ); } +BOOST_AUTO_TEST_CASE(compilation_error) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { + "A": [ + "abi" + ] + } + } + }, + "sources": { + "fileA": { + "content": "contract A { function }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(result.isMember("errors")); + BOOST_CHECK(result["errors"].size() >= 1); + for (auto const& error: result["errors"]) + { + BOOST_REQUIRE(error.isObject()); + BOOST_REQUIRE(error["message"].isString()); + if (error["message"].asString().find("pre-release compiler") == string::npos) + { + BOOST_CHECK_EQUAL( + dev::jsonCompactPrint(error), + "{\"component\":\"general\",\"formattedMessage\":\"fileA:1:23: ParserError: Expected identifier, got 'RBrace'\\n" + "contract A { function }\\n ^\\n\",\"message\":\"Expected identifier, got 'RBrace'\"," + "\"severity\":\"error\",\"sourceLocation\":{\"end\":22,\"file\":\"fileA\",\"start\":22},\"type\":\"ParserError\"}" + ); + } + } +} + BOOST_AUTO_TEST_CASE(output_selection_explicit) { char const* input = R"( @@ -403,6 +451,71 @@ BOOST_AUTO_TEST_CASE(output_selection_dependent_contract_with_import) BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["abi"]), "[{\"constant\":false,\"inputs\":[],\"name\":\"f\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"); } +BOOST_AUTO_TEST_CASE(filename_with_colon) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "http://github.com/ethereum/solidity/std/StandardToken.sol": { + "A": [ + "abi" + ] + } + } + }, + "sources": { + "http://github.com/ethereum/solidity/std/StandardToken.sol": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "http://github.com/ethereum/solidity/std/StandardToken.sol", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["abi"].isArray()); + BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["abi"]), "[]"); +} + +BOOST_AUTO_TEST_CASE(library_filename_with_colon) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { + "A": [ + "evm.bytecode" + ] + } + } + }, + "sources": { + "fileA": { + "content": "import \"git:library.sol\"; contract A { function f() returns (uint) { return L.g(); } }" + }, + "git:library.sol": { + "content": "library L { function g() returns (uint) { return 1; } }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["evm"]["bytecode"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["git:library.sol"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["git:library.sol"]["L"].isArray()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["git:library.sol"]["L"][0].isObject()); +} + + BOOST_AUTO_TEST_SUITE_END() } |