From 0bd8c204f04f679b4371199d876bc6160d913782 Mon Sep 17 00:00:00 2001 From: chriseth Date: Sat, 5 Nov 2016 16:58:06 +0100 Subject: [PATCH 001/414] Assembly definition. --- docs/assembly.rst | 510 +++++++++++++++++++++++++++++++++++++ docs/solidity-in-depth.rst | 1 + 2 files changed, 511 insertions(+) create mode 100644 docs/assembly.rst diff --git a/docs/assembly.rst b/docs/assembly.rst new file mode 100644 index 000000000..71fe40273 --- /dev/null +++ b/docs/assembly.rst @@ -0,0 +1,510 @@ +################# +Solidity Assembly +################# + +.. index:: ! assembly, ! asm, ! evmasm + +Solidity defines an assembly language that can also be used without Solidity. +This assembly language can also be used as "inline assembly" inside Solidity +source code. We start with describing how to use inline assembly and how it +differs from standalone assembly and then specify assembly itself. + +TODO: Write about how scoping rules of inline assembly are a bit different +and the complications that arise when for example using internal functions +of libraries. Furhermore, write about the symbols defined by the compiler. + +Inline Assembly +=============== + +For more fine-grained control especially in order to enhance the language by writing libraries, +it is possible to interleave Solidity statements with inline assembly in a language close +to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is +often hard to address the correct stack slot and provide arguments to opcodes at the correct +point on the stack. Solidity's inline assembly tries to facilitate that and other issues +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) } }`` +* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`` + +We now want to describe the inline assembly language in detail. + +.. warning:: + Inline assembly is still a relatively new feature and might change if it does not prove useful, + so please try to keep up to date. + +Example +------- + +The following example provides library code to access the code of another contract and +load it into a ``bytes`` variable. This is not possible at all with "plain Solidity" and the +idea is that assembly libraries will be used to enhance the language in such ways. + +.. code:: + + library GetCode { + function at(address _addr) returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } + } + +Inline assembly could also be beneficial in cases where the optimizer fails to produce +efficient code. Please be aware that assembly is much more difficult to write because +the compiler does not perform checks, so you should use it for complex things only if +you really know what you are doing. + +.. code:: + + 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) { + for (uint i = 0; i < _data.length; ++i) + o_sum += _data[i]; + } + + // 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) { + for (uint i = 0; i < _data.length; ++i) { + assembly { + o_sum := mload(add(add(_data, 0x20), i)) + } + } + } + } + +Standalone Assembly +=================== + +Grammar +------- + +The assembly lexer follows the one defined by Solidity itself. + +Whitespace is used to delimit tokens and it consists of the characters +Space, Tab and Linefeed. Comments as defined below, are interpreted in the +same way as Whitespace. +Furthermore, the following tokens exist: + +TODO: escapes inside strings, decimal literals, hex literals, hex string literals + +``OneLineComment := "//" [^\n]*` +``MultiLineComment := "/*" .*? "*/"`` + +``String := '"' [^"]* '"' | "'" [^']* "'"`` +``Identifier := [_$a-zA-Z][_$a-zA-Z0-9]*`` +``Opcodes := +"add" | "addmod" | "address" | "and" | "balance" | "blockhash" | "byte" | "call" | +"callcode" | "calldatacopy" | "calldataload" | "calldatasize" | "caller" | "callvalue" | +"codecopy" | "codesize" | "coinbase" | "create" | "delegatecall" | "difficulty" | +"div" | "dup1" | "dup2" | "dup3" | "dup4" | "dup5" | "dup6" | "dup7" | "dup8" | "dup9" | +"dup10" | "dup11" | "dup12" | "dup13" | "dup14" | "dup15" | "dup16" | "eq" | "exp" | +"extcodecopy" | "extcodesize" | "gas" | "gaslimit" | "gasprice" | "gt" | "iszero" | +"jump" | "jumpi" | "log0" | "log1" | "log2" | "log3" | "log4" | "lt" | "mload" | "mod" | +"msize" | "mstore" | "mstore8" | "mul" | "mulmod" | "not" | "number" | "or" | "origin" | +"pc" | "pop" | "return" | "sdiv" | "selfdestruct" | "sgt" | "sha3" | "signextend" | +"sload" | "slt" | "smod" | "sstore" | "stop" | "sub" | "swap1" | "swap2" | "swap3" | +"swap4" | "swap5" | "swap6" | "swap7" | "swap8" | "swap9" | "swap10" | "swap11" | +"swap12" | "swap13" | "swap14" | "swap15" | "swap16" | "timestamp" | "xor"`` + +TODO: Define functional instruction, label, assignment, functional assignment, +variable declaration, ... + + +Syntax +------ + +Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the +usual ``//`` and ``/* */`` comments. Inline assembly is initiated by ``assembly { ... }`` and inside +these curly braces, the following can be used (see the later sections for more details) + + - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) + - opcodes (in "instruction style"), e.g. ``mload sload dup1 sstore``, for a list see below + - opcode in functional style, e.g. ``add(1, mlod(0))`` + - labels, e.g. ``name:`` + - variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)`` + - identifiers (externals, labels or assembly-local variables), e.g. ``jump(name)``, ``3 x add`` + - assignments (in "instruction style"), e.g. ``3 =: x`` + - assignments in functional style, e.g. ``x := add(y, 3)`` + - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` + +Opcodes +------- + +This document does not want to be a full description of the Ethereum virtual machine, but the +following list can be used as a reference of its opcodes. + +If an opcode takes arguments (always from the top of the stack), they are given in parentheses. +Note that the order of arguments can be seed to be reversed in non-functional style (explained below). +Opcodes marked with ``-`` do not push an item onto the stack, those marked with ``*`` are +special and all others push exactly one item onte the stack. + +In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to +(excluding) position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``. + +The opcodes ``pushi`` and ``jumpdest`` cannot be used directly. + ++-------------------------+------+-----------------------------------------------------------------+ +| stop + `-` | stop execution, identical to return(0,0) | ++-------------------------+------+-----------------------------------------------------------------+ +| add(x, y) | | x + y | ++-------------------------+------+-----------------------------------------------------------------+ +| sub(x, y) | | x - y | ++-------------------------+------+-----------------------------------------------------------------+ +| mul(x, y) | | x * y | ++-------------------------+------+-----------------------------------------------------------------+ +| div(x, y) | | x / y | ++-------------------------+------+-----------------------------------------------------------------+ +| sdiv(x, y) | | x / y, for signed numbers in two's complement | ++-------------------------+------+-----------------------------------------------------------------+ +| mod(x, y) | | x % y | ++-------------------------+------+-----------------------------------------------------------------+ +| smod(x, y) | | x % y, for signed numbers in two's complement | ++-------------------------+------+-----------------------------------------------------------------+ +| exp(x, y) | | x to the power of y | ++-------------------------+------+-----------------------------------------------------------------+ +| not(x) | | ~x, every bit of x is negated | ++-------------------------+------+-----------------------------------------------------------------+ +| lt(x, y) | | 1 if x < y, 0 otherwise | ++-------------------------+------+-----------------------------------------------------------------+ +| gt(x, y) | | 1 if x > y, 0 otherwise | ++-------------------------+------+-----------------------------------------------------------------+ +| slt(x, y) | | 1 if x < y, 0 otherwise, for signed numbers in two's complement | ++-------------------------+------+-----------------------------------------------------------------+ +| sgt(x, y) | | 1 if x > y, 0 otherwise, for signed numbers in two's complement | ++-------------------------+------+-----------------------------------------------------------------+ +| eq(x, y) | | 1 if x == y, 0 otherwise | ++-------------------------+------+-----------------------------------------------------------------+ +| iszero(x) | | 1 if x == 0, 0 otherwise | ++-------------------------+------+-----------------------------------------------------------------+ +| and(x, y) | | bitwise and of x and y | ++-------------------------+------+-----------------------------------------------------------------+ +| or(x, y) | | bitwise or of x and y | ++-------------------------+------+-----------------------------------------------------------------+ +| xor(x, y) | | bitwise xor of x and y | ++-------------------------+------+-----------------------------------------------------------------+ +| byte(n, x) | | nth byte of x, where the most significant byte is the 0th byte | ++-------------------------+------+-----------------------------------------------------------------+ +| addmod(x, y, m) | | (x + y) % m with arbitrary precision arithmetics | ++-------------------------+------+-----------------------------------------------------------------+ +| mulmod(x, y, m) | | (x * y) % m with arbitrary precision arithmetics | ++-------------------------+------+-----------------------------------------------------------------+ +| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant | ++-------------------------+------+-----------------------------------------------------------------+ +| sha3(p, n) | | keccak(mem[p...(p+n))) | ++-------------------------+------+-----------------------------------------------------------------+ +| jump(label) | `-` | jump to label / code position | ++-------------------------+------+-----------------------------------------------------------------+ +| jumpi(label, cond) | `-` | jump to label if cond is nonzero | ++-------------------------+------+-----------------------------------------------------------------+ +| pc | | current position in code | ++-------------------------+------+-----------------------------------------------------------------+ +| pop | `*` | remove topmost stack slot | ++-------------------------+------+-----------------------------------------------------------------+ +| dup1 ... dup16 | | copy ith stack slot to the top (counting from top) | ++-------------------------+------+-----------------------------------------------------------------+ +| swap1 ... swap16 | `*` | swap topmost and ith stack slot below it | ++-------------------------+------+-----------------------------------------------------------------+ +| mload(p) | | mem[p..(p+32)) | ++-------------------------+------+-----------------------------------------------------------------+ +| mstore(p, v) | `-` | mem[p..(p+32)) := v | ++-------------------------+------+-----------------------------------------------------------------+ +| mstore8(p, v) | `-` | mem[p] := v & 0xff - only modifies a single byte | ++-------------------------+------+-----------------------------------------------------------------+ +| sload(p) | | storage[p] | ++-------------------------+------+-----------------------------------------------------------------+ +| sstore(p, v) | `-` | storage[p] := v | ++-------------------------+------+-----------------------------------------------------------------+ +| msize | | size of memory, i.e. largest accessed memory index | ++-------------------------+------+-----------------------------------------------------------------+ +| gas | | gas still available to execution | ++-------------------------+------+-----------------------------------------------------------------+ +| address | | address of the current contract / execution context | ++-------------------------+------+-----------------------------------------------------------------+ +| balance(a) | | wei balance at address a | ++-------------------------+------+-----------------------------------------------------------------+ +| caller | | call sender (excluding delegatecall) | ++-------------------------+------+-----------------------------------------------------------------+ +| callvalue | | wei sent together with the current call | ++-------------------------+------+-----------------------------------------------------------------+ +| calldataload(p) | | call data starting from position p (32 bytes) | ++-------------------------+------+-----------------------------------------------------------------+ +| calldatasize | | size of call data in bytes | ++-------------------------+------+-----------------------------------------------------------------+ +| calldatacopy(t, f, s) | `-` | copy s bytes from calldata at position f to mem at position t | ++-------------------------+------+-----------------------------------------------------------------+ +| codesize | | size of the code of the current contract / execution context | ++-------------------------+------+-----------------------------------------------------------------+ +| codecopy(t, f, s) | `-` | copy s bytes from code at position f to mem at position t | ++-------------------------+------+-----------------------------------------------------------------+ +| extcodesize(a) | | size of the code at address a | ++-------------------------+------+-----------------------------------------------------------------+ +| extcodecopy(a, t, f, s) | `-` | like codecopy(t, f, s) but take code at address a | ++-------------------------+------+-----------------------------------------------------------------+ +| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei | +| | | and return the new address | ++-------------------------+------+-----------------------------------------------------------------+ +| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)] | +| insize, out, outsize) | | providing g gas and v wei and output area | +| | | mem[out..(out+outsize)] returting 1 on error (out of gas) | ++-------------------------+------+-----------------------------------------------------------------+ +| callcode(g, a, v, in, | | identical to call but only use the code from a and stay | +| insize, out, outsize) | | in the context of the current contract otherwise | ++-------------------------+------+-----------------------------------------------------------------+ +| delegatecall(g, a, in, | | identical to callcode but also keep ``caller`` | +| insize, out, outsize) | | and ``callvalue`` | ++-------------------------+------+-----------------------------------------------------------------+ +| return(p, s) | `*` | end execution, return data mem[p..(p+s)) | ++-------------------------+------+-----------------------------------------------------------------+ +| selfdestruct(a) | `*` | end execution, destroy current contract and send funds to a | ++-------------------------+------+-----------------------------------------------------------------+ +| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) | ++-------------------------+------+-----------------------------------------------------------------+ +| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) | ++-------------------------+------+-----------------------------------------------------------------+ +| log2(p, s, t1, t2) | `-` | log with topics t1, t2 and data mem[p..(p+s)) | ++-------------------------+------+-----------------------------------------------------------------+ +| log3(p, s, t1, t2, t3) | `-` | log with topics t1, t2, t3 and data mem[p..(p+s)) | ++-------------------------+------+-----------------------------------------------------------------+ +| log4(p, s, t1, t2, t3, | `-` | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | +| t4) | | | ++-------------------------+------+-----------------------------------------------------------------+ +| origin | | transaction sender | ++-------------------------+------+-----------------------------------------------------------------+ +| gasprice | | gas price of the transaction | ++-------------------------+------+-----------------------------------------------------------------+ +| blockhash(b) | | hash of block nr b - only for last 256 blocks excluding current | ++-------------------------+------+-----------------------------------------------------------------+ +| coinbase | | current mining beneficiary | ++-------------------------+------+-----------------------------------------------------------------+ +| timestamp | | timestamp of the current block in seconds since the epoch | ++-------------------------+------+-----------------------------------------------------------------+ +| number | | current block number | ++-------------------------+------+-----------------------------------------------------------------+ +| difficulty | | difficulty of the current block | ++-------------------------+------+-----------------------------------------------------------------+ +| gaslimit | | block gas limit of the current block | ++-------------------------+------+-----------------------------------------------------------------+ + +Literals +-------- + +You can use integer constants by typing them in decimal or hexadecimal notation and an +appropriate ``PUSHi`` instruction will automatically be generated. The following creates code +to add 2 and 3 resulting in 5 and then computes the bitwise and with the string "abc". +Strings are stored left-aligned and cannot be longer than 32 bytes. + +.. code:: + + assembly { 2 3 add "abc" and } + +Functional Style +----------------- + +You can type opcode after opcode in the same way they will end up in bytecode. For example +adding ``3`` to the contents in memory at position ``0x80`` would be + +.. code:: + + 3 0x80 mload add 0x80 mstore + +As it is often hard to see what the actual arguments for certain opcodes are, +Solidity inline assembly also provides a "functional style" notation where the same code +would be written as follows + +.. code:: + + mstore(0x80, add(mload(0x80), 3)) + +Functional style and instructional style can be mixed, but any opcode inside a +functional style expression has to return exactly one stack slot (most of the opcodes do). + +Note that the order of arguments is reversed in functional-style as opposed to the instruction-style +way. If you use functional-style, the first argument will end up on the stack top. + + +Access to External Variables and Functions +------------------------------------------ + +Solidity variables and other identifiers can be accessed by simply using their name. +For storage and memory variables, this will push the address and not the value onto the +stack. Also note that non-struct and non-array storage variable addresses occupy two slots +on the stack: One for the address and one for the byte offset inside the storage slot. +In assignments (see below), we can even use local Solidity variables to assign to. + +Functions external to inline assembly can also be accessed: The assembly will +push their entry label (with virtual function resolution applied). The calling semantics +in solidity are: + + - the caller pushes return label, arg1, arg2, ..., argn + - the call returns with ret1, ret2, ..., retn + +This feature is still a bit cumbersome to use, because the stack offset essentially +changes during the call, and thus references to local variables will be wrong. +It is planned that the stack height changes can be specified in inline assembly. + +.. code:: + + contract C { + uint b; + function f(uint x) returns (uint r) { + assembly { + b pop // remove the offset, we know it is zero + sload + x + mul + =: r // assign to return variable r + } + } + } + +Labels +------ + +Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses +which can change easily. Solidity inline assembly provides labels to make the use of +jumps easier. The following code computes an element in the Fibonacci series. + +.. code:: + + { + let n := calldataload(4) + let a := 1 + let b := a + loop: + jumpi(loopend, eq(n, 0)) + a add swap1 + n := sub(n, 1) + jump(loop) + loopend: + mstore(0, a) + return(0, 0x20) + } + +Please note that automatically accessing stack variables can only work if the +assembler knows the current stack height. This fails to work if the jump source +and target have different stack heights. It is still fine to use such jumps, +you should just not access any stack variables (even assembly variables) in that case. + +Furthermore, the stack height analyser goes through the code opcode by opcode +(and not according to control flow), so in the following case, the assembler +will have a wrong impression about the stack height at label ``two``: + +.. code:: + + { + jump(two) + one: + // Here the stack height is 1 (because we pushed 7), + // but the assembler thinks it is 0 because it reads + // from top to bottom. + // Accessing stack variables here will lead to errors. + jump(three) + two: + 7 // push something onto the stack + jump(one) + three: + } + + +Declaring Assembly-Local Variables +---------------------------------- + +You can use the ``let`` keyword to declare variables that are only visible in +inline assembly and actually only in the current ``{...}``-block. What happens +is that the ``let`` instruction will create a new stack slot that is reserved +for the variable and automatically removed again when the end of the block +is reached. You need to provide an initial value for the variable which can +be just ``0``, but it can also be a complex functional-style expression. + +.. code:: + + contract C { + function f(uint x) returns (uint b) { + assembly { + let v := add(x, 1) + mstore(0x80, v) + { + let y := add(sload(v), 1) + b := y + } // y is "deallocated" here + b := add(b, v) + } // v is "deallocated" here + } + } + + +Assignments +----------- + +Assignments are possible to assembly-local variables and to function-local +variables. Take care that when you assign to variables that point to +memory or storage, you will only change the pointer and not the data. + +There are two kinds of assignments: Functional-style and instruction-style. +For functional-style assignments (``variable := value``), you need to provide a value in a +functional-style expression that results in exactly one stack value +and for instruction-style (``=: variable``), the value is just taken from the stack top. +For both ways, the colon points to the name of the variable. + +.. code:: + + assembly { + let v := 0 // functional-style assignment as part of variable declaration + let g := add(v, 2) + sload(10) + =: v // instruction style assignment, puts the result of sload(10) into v + } + + +Things to Avoid +--------------- + +Inline assembly might have a quite high-level look, but it actually is extremely +low-level. The only thing the assembler does for you is re-arranging +functional-style opcodes, managing jump labels, counting stack height for +variable access and removing stack slots for assembly-local variables when the end +of their block is reached. Especially for those two last cases, it is important +to know that the assembler only counts stack height from top to bottom, not +necessarily following control flow. Furthermore, operations like swap will only +swap the contents of the stack but not the location of variables. + +Conventions in Solidity +----------------------- + +In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits, +e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just +treat them as 256 bit numbers and the higher-order bits are only cleaned at the +point where it is necessary, i.e. just shortly before they are written to memory +or before comparisons are performed. This means that if you access such a variable +from within inline assembly, you might have to manually clean the higher order bits +first. + +Solidity manages memory in a very simple way: There is a "free memory pointer" +at position ``0x40`` in memory. If you want to allocate memory, just use the memory +from that point on and update the pointer accordingly. + +Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is +even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory +arrays are pointers to memory arrays. The length of a dynamic array is stored at the +first slot of the array and then only the array elements follow. + +.. warning:: + Statically-sized memory arrays do not have a length field, but it will be added soon + to allow better convertibility between statically- and dynamically-sized arrays, so + please do not rely on that. diff --git a/docs/solidity-in-depth.rst b/docs/solidity-in-depth.rst index 40704698c..b6217b479 100644 --- a/docs/solidity-in-depth.rst +++ b/docs/solidity-in-depth.rst @@ -16,4 +16,5 @@ If something is missing here, please contact us on units-and-global-variables.rst control-structures.rst contracts.rst + assembly.rst miscellaneous.rst From a285ca44f228236ab92eb8dc9d878053829276b4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 15 Nov 2016 14:01:11 +0100 Subject: [PATCH 002/414] Assembly output for Assembly object. --- libevmasm/Assembly.cpp | 69 +++++++++----------------------- libevmasm/AssemblyItem.cpp | 82 ++++++++++++++++++++++++++++++++++++++ libevmasm/AssemblyItem.h | 5 +++ 3 files changed, 105 insertions(+), 51 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index a9ca24dc4..f50a38a67 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -117,69 +117,36 @@ string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocati ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const { - _out << _prefix << ".code:" << endl; - for (AssemblyItem const& i: m_items) + for (size_t i = 0; i < m_items.size(); ++i) { - _out << _prefix; - switch (i.type()) + AssemblyItem const& item = m_items[i]; + if (!item.location().isEmpty() && (i == 0 || m_items[i - 1].location() != item.location())) { - case Operation: - _out << " " << instructionInfo(i.instruction()).name << "\t" << i.getJumpTypeAsString(); - break; - case Push: - _out << " PUSH" << dec << max(1, dev::bytesRequired(i.data())) << " 0x" << hex << i.data(); - break; - case PushString: - _out << " PUSH \"" << m_strings.at((h256)i.data()) << "\""; - break; - case PushTag: - if (i.data() == 0) - _out << " PUSH [ErrorTag]"; - else - { - size_t subId = i.splitForeignPushTag().first; - if (subId == size_t(-1)) - _out << " PUSH [tag" << dec << i.splitForeignPushTag().second << "]"; - else - _out << " PUSH [tag" << dec << subId << ":" << i.splitForeignPushTag().second << "]"; - } - break; - case PushSub: - _out << " PUSH [$" << size_t(i.data()) << "]"; - break; - case PushSubSize: - _out << " PUSH #[$" << size_t(i.data()) << "]"; - break; - case PushProgramSize: - _out << " PUSHSIZE"; - break; - case PushLibraryAddress: - _out << " PUSHLIB \"" << m_libraries.at(h256(i.data())) << "\""; - break; - case Tag: - _out << "tag" << dec << i.data() << ": " << endl << _prefix << " JUMPDEST"; - break; - case PushData: - _out << " PUSH [" << hex << (unsigned)i.data() << "]"; - break; - default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + _out << _prefix << " /*"; + if (item.location().sourceName) + _out << " \"" + *item.location().sourceName + "\""; + if (!item.location().isEmpty()) + _out << ":" << to_string(item.location().start) + ":" + to_string(item.location().end); + _out << " */" << endl; } - _out << "\t\t" << locationFromSources(_sourceCodes, i.location()) << endl; + _out << _prefix << (item.type() == Tag ? "" : " ") << item.toAssemblyText() << endl; } if (!m_data.empty() || !m_subs.empty()) { - _out << _prefix << ".data:" << endl; + _out << _prefix << "stop" << endl; + Json::Value data; for (auto const& i: m_data) - if (u256(i.first) >= m_subs.size()) - _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << dev::toHex(i.second) << endl; + assertThrow(u256(i.first) < m_subs.size(), AssemblyException, "Data not yet implemented."); + for (size_t i = 0; i < m_subs.size(); ++i) { - _out << _prefix << " " << hex << i << ": " << endl; - m_subs[i]->stream(_out, _prefix + " ", _sourceCodes); + _out << endl << _prefix << "sub_" << i << ": assembly {\n"; + m_subs[i]->streamAsm(_out, _prefix + " ", _sourceCodes); + _out << _prefix << "}" << endl; } } + return _out; } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 54e38de80..8b3f920ab 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -20,6 +20,7 @@ */ #include "AssemblyItem.h" +#include #include using namespace std; @@ -97,6 +98,28 @@ int AssemblyItem::deposit() const return 0; } +bool AssemblyItem::canBeFunctional() const +{ + switch (m_type) + { + case Operation: + return !SemanticInformation::isDupInstruction(*this) && !SemanticInformation::isSwapInstruction(*this); + case Push: + case PushString: + case PushTag: + case PushData: + case PushSub: + case PushSubSize: + case PushProgramSize: + case PushLibraryAddress: + return true; + case Tag: + return false; + default:; + } + return 0; +} + string AssemblyItem::getJumpTypeAsString() const { switch (m_jumpType) @@ -111,6 +134,65 @@ string AssemblyItem::getJumpTypeAsString() const } } +string AssemblyItem::toAssemblyText() const +{ + string text; + switch (type()) + { + case Operation: + { + assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction."); + string name = instructionInfo(instruction()).name; + transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); + text = name; + break; + } + case Push: + text = toHex(toCompactBigEndian(data(), 1), 1, HexPrefix::Add); + break; + case PushString: + assertThrow(false, AssemblyException, "Push string assembly output not implemented."); + break; + case PushTag: + assertThrow(data() < 0x10000, AssemblyException, "Sub-assembly tags not yet implemented."); + text = string("tag_") + to_string(size_t(data())); + break; + case Tag: + assertThrow(data() < 0x10000, AssemblyException, "Sub-assembly tags not yet implemented."); + text = string("tag_") + to_string(size_t(data())) + ":"; + break; + case PushData: + assertThrow(false, AssemblyException, "Push data not implemented."); + break; + case PushSub: + text = string("dataOffset(sub_") + to_string(size_t(data())) + ")"; + break; + case PushSubSize: + text = string("dataSize(sub_") + to_string(size_t(data())) + ")"; + break; + case PushProgramSize: + text = string("programSize"); + break; + case PushLibraryAddress: + text = string("linkerSymbol(\"") + toHex(data()) + string("\")"); + break; + case UndefinedItem: + assertThrow(false, AssemblyException, "Invalid assembly item."); + break; + default: + BOOST_THROW_EXCEPTION(InvalidOpcode()); + } + if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) + { + text += "\t//"; + if (m_jumpType == JumpType::IntoFunction) + text += " in"; + else + text += " out"; + } + return text; +} + ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) { switch (_item.type()) diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index b5bd3ed89..cddfb17cf 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -97,6 +97,9 @@ public: unsigned bytesRequired(unsigned _addressLength) const; int deposit() const; + /// @returns true if the assembly item can be used in a functional context. + bool canBeFunctional() const; + bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); } void setLocation(SourceLocation const& _location) { m_location = _location; } SourceLocation const& location() const { return m_location; } @@ -108,6 +111,8 @@ public: void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared(_value); } u256 const* pushedValue() const { return m_pushedValue.get(); } + std::string toAssemblyText() const; + private: AssemblyItemType m_type; u256 m_data; From 49ac2a1ee5005f641e875823a84ee73c9e93a7c5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 9 Dec 2016 11:03:29 +0100 Subject: [PATCH 003/414] Rename programSize to bytecodeSize. --- libevmasm/AssemblyItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 8b3f920ab..b790e094c 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -171,7 +171,7 @@ string AssemblyItem::toAssemblyText() const text = string("dataSize(sub_") + to_string(size_t(data())) + ")"; break; case PushProgramSize: - text = string("programSize"); + text = string("bytecodeSize"); break; case PushLibraryAddress: text = string("linkerSymbol(\"") + toHex(data()) + string("\")"); From 9683cfea6dbbdf8f82e6cd58d52360f958b2322c Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 3 Jan 2017 15:19:14 +0100 Subject: [PATCH 004/414] Update to new assembly specification. --- docs/assembly.rst | 406 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 378 insertions(+), 28 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 71fe40273..8ba3f56cf 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -27,6 +27,12 @@ arising when writing manual assembly by the following features: * 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) } }`` * 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) }`` +* switch statements: ``switch x case 0: { y := mul(x, 2) } default: { y := 0 }`` +* function calls: ``function f(x) -> (y) { switch x case 0: { y := 1 } default: y := mul(x, f(sub(x, 1))) }`` + +.. note:: + Of the above, loops, function calls and switch statements are not yet implemented. We now want to describe the inline assembly language in detail. @@ -91,40 +97,165 @@ you really know what you are doing. Standalone Assembly =================== -Grammar -------- +This assembly language tries to achieve several goals: -The assembly lexer follows the one defined by Solidity itself. +1. Programs written in it should be readable, even if the code is generated by a compiler from Solidity. +2. The translation from assembly to bytecode should contain as few "surprises" as possible. +3. Control flow should be easy to detect to help in formal verification and optimization. -Whitespace is used to delimit tokens and it consists of the characters -Space, Tab and Linefeed. Comments as defined below, are interpreted in the -same way as Whitespace. -Furthermore, the following tokens exist: +In order to achieve the first and last goal, assembly provides high-level constructs +like ``for`` loops, ``switch`` statements and function calls. It should be possible +to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, +``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow +and the last two obfuscate control flow. Furthermore, functional statements of +the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like +``7 x y add mul`` because in the first form, it is much easier to see which +operand is used for which opcode. -TODO: escapes inside strings, decimal literals, hex literals, hex string literals +The second goal is achieved by introducing a desugaring phase that only removes +the higher level constructs in a very regular way and still allows inspecting +the generated low-level assembly code. The only non-local operation performed +by the assembler is name lookup of user-defined identifiers (functions, variables, ...), +which follow very simple and regular scoping rules and cleanup of local variables from the stack. -``OneLineComment := "//" [^\n]*` -``MultiLineComment := "/*" .*? "*/"`` - -``String := '"' [^"]* '"' | "'" [^']* "'"`` -``Identifier := [_$a-zA-Z][_$a-zA-Z0-9]*`` -``Opcodes := -"add" | "addmod" | "address" | "and" | "balance" | "blockhash" | "byte" | "call" | -"callcode" | "calldatacopy" | "calldataload" | "calldatasize" | "caller" | "callvalue" | -"codecopy" | "codesize" | "coinbase" | "create" | "delegatecall" | "difficulty" | -"div" | "dup1" | "dup2" | "dup3" | "dup4" | "dup5" | "dup6" | "dup7" | "dup8" | "dup9" | -"dup10" | "dup11" | "dup12" | "dup13" | "dup14" | "dup15" | "dup16" | "eq" | "exp" | -"extcodecopy" | "extcodesize" | "gas" | "gaslimit" | "gasprice" | "gt" | "iszero" | -"jump" | "jumpi" | "log0" | "log1" | "log2" | "log3" | "log4" | "lt" | "mload" | "mod" | -"msize" | "mstore" | "mstore8" | "mul" | "mulmod" | "not" | "number" | "or" | "origin" | -"pc" | "pop" | "return" | "sdiv" | "selfdestruct" | "sgt" | "sha3" | "signextend" | -"sload" | "slt" | "smod" | "sstore" | "stop" | "sub" | "swap1" | "swap2" | "swap3" | -"swap4" | "swap5" | "swap6" | "swap7" | "swap8" | "swap9" | "swap10" | "swap11" | -"swap12" | "swap13" | "swap14" | "swap15" | "swap16" | "timestamp" | "xor"`` +Scoping: An identifier that is declared (label, variable, function, assembly) +is only visible in the block where it was declared (including nested blocks +inside the current block). It is not legal to access local variables across +function borders, even if they would be in scope. Shadowing is allowed, but +two identifiers with the same name cannot be declared in the same block. +Local variables cannot be accessed before they were declared, but labels, +functions and assemblies can. Assemblies are special blocks that are used +for e.g. returning runtime code or creating contracts. No identifier from an +outer assembly is visible in a sub-assembly. -TODO: Define functional instruction, label, assignment, functional assignment, -variable declaration, ... +If control flow passes over the end of a block, pop instructions are inserted +that match the number of local variables declared in that block, unless the +``}`` is directly preceded by an opcode that does not have a continuing control +flow path. The stack height is reduced by the number of local variables +regardless of that. This mean that labels in the next block will have the +same height as before the block that just ended. +If at the end of a block, the stack is not balanced, a warning is issued, +unless the last instruction in the block did not have a continuing control flow path. + +Why do we use higher-level constructs like ``switch``, ``for`` and functions: + +Using ``switch``, ``for`` and functions, it should be possible to write +complex code without using ``jump`` or ``jumpi`` manually. This makes it much +easier to analyze the control flow, which allows for improved formal +verification and optimization. + +Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. +The position of all local variables on the stack needs to be known, otherwise +neither references to local variables nor removing local variables automatically +from the stack at the end of a block will work properly. Because of that, +every label that is preceded by an instruction that ends or diverts control flow +should be annotated with the current stack layout. This annotation is performed +automatically during the desugaring phase. + +Example: + +We will follow an example compilation from Solidity to desugared assembly. +We consider the runtime bytecode of the following Solidity program:: + + contract C { + function f(uint x) returns (uint y) { + y = 1 + for (uint i = 0; i < x; i++) + y = 2 * y; + } + } + +The following assembly will be generated:: + + { + mstore(0x40, 0x60) // store the "free memory pointer" + // function dispatcher + switch div(calldataload(0), exp(2, 226)) + case 0xb3de648b: { + let (r,) = f(calldataload(4)) + let ret := $allocate(0x20) + mstore(ret, r) + return(ret, 0x20) + } + default: { jump(invalidJumpLabel) } + // memory allocator + function $allocate(size) -> (pos) { + pos := mload(0x40) + mstore(0x40, add(pos, size)) + } + // the contract function + function f(x) -> (y) { + y := 1 + for { let i := 0 } lt(i, x) { i := add(i, 1) } { + y := mul(2, y) + } + } + } + +After the desugaring phase it looks as follows:: + + { + mstore(0x40, 0x60) + { + let $0 := div(calldataload(0), exp(2, 226)) + jumpi($case1, eq($0, 0xb3de648b)) + jump($caseDefault) + $case1: + { + // the function call - we put return label and arguments on the stack + $ret1 calldataload(4) jump($fun_f) + $ret1 [r]: // a label with a [...]-annotation resets the stack height + // to "current block + number of local variables". It also + // introduces a variable, r: + // r is at top of stack, $0 is below (from enclosing block) + $ret2 0x20 jump($fun_allocate) + $ret2 [ret]: // stack here: $0, r, ret (top) + mstore(ret, r) + return(ret, 0x20) + // although it is useless, the jump is automatically inserted, + // since the desugaring process does not analyze control-flow + jump($endswitch) + } + $caseDefault: + { + jump(invalidJumpLabel) + jump($endswitch) + } + $endswitch: + } + jump($afterFunction) + $fun_allocate: + { + $start[$retpos, size]: + let pos := 0 + { + pos := mload(0x40) + mstore(0x40, add(pos, size)) + } + swap1 pop swap1 jump + } + $fun_f: + { + start [$retpos, x]: + let y := 0 + { + let i := 0 + $for_begin: + jumpi($for_end, iszero(lt(i, x))) + { + y := mul(2, y) + } + $for_continue: + { i := add(i, 1) } + jump($for_begin) + $for_end: + } // Here, a pop instruction is inserted for i + swap1 pop swap1 jump + } + $afterFunction: + stop + } Syntax ------ @@ -159,6 +290,8 @@ In the following, ``mem[a...b)`` signifies the bytes of memory starting at posit The opcodes ``pushi`` and ``jumpdest`` cannot be used directly. +In the grammar, opcodes are represented as pre-defined identifiers. + +-------------------------+------+-----------------------------------------------------------------+ | stop + `-` | stop execution, identical to return(0,0) | +-------------------------+------+-----------------------------------------------------------------+ @@ -508,3 +641,220 @@ first slot of the array and then only the array elements follow. Statically-sized memory arrays do not have a length field, but it will be added soon to allow better convertibility between statically- and dynamically-sized arrays, so please do not rely on that. + + +Specification +============= + +Assembly happens in four stages: + +1. Parsing +2. Desugaring (removes switch, for and functions) +3. Opcode stream generation +4. Bytecode generation + + +Parsing / Grammar +----------------- + +The tasks of the parser are the following: + +- Turn the byte stream into a token stream, discarding C++-style comments + (a special comment exists for source references, but we will not explain it here). +- Turn the token stream into an AST according to the grammar below +- Register identifiers with the block they are defined in (annotation to the + AST node) and note from which point on, variables can be accessed. + +The assembly lexer follows the one defined by Solidity itself. + +Whitespace is used to delimit tokens and it consists of the characters +Space, Tab and Linefeed. Comments are regular JavaScript/C++ comments and +are interpreted in the same way as Whitespace. + +Grammar:: + + AssemblyBlock = '{' AssemblyItem* '}' + AssemblyItem = + Identifier | + AssemblyBlock | + FunctionalAssemblyExpression | + AssemblyLocalDefinition | + FunctionalAssemblyAssignment | + AssemblyAssignment | + LabelDefinition | + AssemblySwitch | + AssemblyFunctionDefinition | + AssemblyFor | + 'break' | 'continue' | + SubAssembly | 'dataSize' '(' Identifier ')' | + LinkerSymbol | + 'errorLabel' | 'bytecodeSize' | + NumberLiteral | StringLiteral | HexLiteral + Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* + FunctionalAssemblyExpression = Identifier '(' ( AssemblyItem ( ',' AssemblyItem )* )? ')' + AssemblyLocalDefinition = 'let' IdentifierOrList ':=' FunctionalAssemblyExpression + FunctionalAssemblyAssignment = IdentifierOrList ':=' FunctionalAssemblyExpression + IdentifierOrList = Identifier | '(' IdentifierList ')' + IdentifierList = Identifier ( ',' Identifier)* + AssemblyAssignment = '=:' Identifier + LabelDefinition = Identifier ( '[' ( IdentifierList | NumberLiteral ) ']' )? ':' + AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase* + ( 'default' ':' AssemblyBlock )? + AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock + AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')' '->' + ( '(' IdentifierList ')' AssemblyBlock + AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression) + FunctionalAssemblyExpression ( AssemblyBlock | FunctionalAssemblyExpression) AssemblyBlock + SubAssembly = 'assembly' Identifier AssemblyBlock + LinkerSymbol = 'linkerSymbol' '(' StringLiteral ')' + NumberLiteral = HexNumber | DecimalNumber + HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') + StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' + HexNumber = '0x' [0-9a-fA-F]+ + DecimalNumber = [0-9]+ + + +Desugaring +---------- + +An AST transformation removes for, switch and function constructs. The result +is still parseable by the same parser, but it will not use certain constructs. +If jumpdests are added that are only jumped to and not continued at, information +about the stack content is added, unless no local variables of outer scopes are +accessed or the stack height is the same as for the previous instruction. + +Pseudocode:: + + desugar item: AST -> AST = + match item { + AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) -> + : + { + $_start [$retPC, $argn, ..., arg1]: + let ret1 := 0 ... let retm := 0 + { desugar(body) } + swap and pop items so that only ret1, ... retn, $retPC are left on the stack + jump + } + AssemblyFor('for' { init } condition post body) -> + { + init // cannot be its own block because we want variable scope to extend into the body + // find I such that there are no labels $forI_* + $forI_begin: + jumpi($forI_end, iszero(condition)) + { body } + $forI_continue: + { post } + jump($forI_begin) + $forI_end: + } + 'break' -> + { + // find nearest enclosing scope with label $forI_end + pop all local variables that are defined at the current point + but not at $forI_end + jump($forI_end) + } + 'continue' -> + { + // find nearest enclosing scope with label $forI_continue + pop all local variables that are defined at the current point + but not at $forI_continue + jump($forI_continue) + } + AssemblySwitch(switch condition cases ( default: defaultBlock )? ) -> + { + // find I such that there is no $switchI* label or variable + let $switchI_value := condition + for each of cases match { + case val: -> jumpi($switchI_caseJ, eq($switchI_value, val)) + } + if default block present: -> + { defaultBlock jump($switchI_end) } + for each of cases match { + case val: { body } -> $switchI_caseJ: { body jump($switchI_end) } + } + $switchI_end: + } + FunctionalAssemblyExpression( identifier(arg1, arg2, ..., argn) ) -> + { + if identifier is function with n args and m ret values -> + { + // find I such that $funcallI_* does not exist + $funcallI_return argn ... arg2 arg1 jump() + if the current context is `let (id1, ..., idm) := f(...)` -> + $funcallI_return [id1, ..., idm]: + else -> + $funcallI_return[m - n - 1]: + turn the functional expression that leads to the function call + into a statement stream + } + else -> desugar(children of node) + } + default node -> + desugar(children of node) + } + +Opcode Stream Generation +------------------------ + +During opcode stream generation, we keep track of the current stack height, +so that accessing stack variables by name is possible. + +Pseudocode:: + + codegen item: AST -> opcode_stream = + match item { + AssemblyBlock({ items }) -> + join(codegen(item) for item in items) + if last generated opcode has continuing control flow: + POP for all local variables registered at the block (including variables + introduced by labels) + warn if the stack height at this point is not the same as at the start of the block + Identifier(id) -> + lookup id in the syntactic stack of blocks + match type of id + Local Variable -> + DUPi where i = 1 + stack_height - stack_height_of_identifier(id) + Label -> + // reference to be resolved during bytecode generation + PUSH + SubAssembly -> + PUSH + FunctionalAssemblyExpression(id ( arguments ) ) -> + join(codegen(arg) for arg in arguments.reversed()) + id (which has to be an opcode, might be a function name later) + AssemblyLocalDefinition(let (id1, ..., idn) := expr) -> + register identifiers id1, ..., idn as locals in current block at current stack height + codegen(expr) - assert that expr returns n items to the stack + FunctionalAssemblyAssignment((id1, ..., idn) := expr) -> + lookup id1, ..., idn in the syntactic stack of blocks, assert that they are variables + codegen(expr) + for j = n, ..., i: + SWAPi where i = 1 + stack_height - stack_height_of_identifier(idj) + POP + AssemblyAssignment(=: id) -> + look up id in the syntactic stack of blocks, assert that it is a variable + SWAPi where i = 1 + stack_height - stack_height_of_identifier(id) + POP + LabelDefinition(name [id1, ..., idn] :) -> + JUMPDEST + // register new variables id1, ..., idn and set the stack height to + // stack_height_at_block_start + number_of_local_variables + LabelDefinition(name [number] :) -> + JUMPDEST + // adjust stack height by +number (can be negative) + NumberLiteral(num) -> + PUSH + HexLiteral(lit) -> + PUSH32 + StringLiteral(lit) -> + PUSH32 + SubAssembly(assembly block) -> + append codegen(block) at the end of the code + dataSize() -> + assert that is a subassembly -> + PUSH32> + linkerSymbol() -> + PUSH32 and append position to linker table + } From 9128e73b03856c72e90c581cb36e931e971af29e Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 2 Jan 2017 16:13:57 +0100 Subject: [PATCH 005/414] docs: clarify what are considered as absolute paths --- docs/layout-of-source-files.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index dff48be31..1e27b7c04 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -79,8 +79,9 @@ Paths ----- In the above, ``filename`` is always treated as a path with ``/`` as directory separator, -``.`` as the current and ``..`` as the parent directory. Path names that do not start -with ``.`` are treated as absolute paths. +``.`` as the current and ``..`` as the parent directory. When ``.`` or ``..`` is followed by a character except ``/``, +it is not considered as the current or the parent directory. +All path names are treated as absolute paths unless they start with the current ``.`` or the parent directory ``..``. To import a file ``x`` from the same directory as the current file, use ``import "./x" as x;``. If you use ``import "x" as x;`` instead, a different file could be referenced From 41fe412389645fd423c8fb2422db99892f829030 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 2 Jan 2017 16:47:45 +0100 Subject: [PATCH 006/414] interface: change absolutePath() so that ".dir" is considered as an absolute path fixes #1534 --- libsolidity/interface/CompilerStack.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 4095844f7..ee55f41af 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -593,11 +593,11 @@ bool CompilerStack::checkLibraryNameClashes() string CompilerStack::absolutePath(string const& _path, string const& _reference) const { - // Anything that does not start with `.` is an absolute path. - if (_path.empty() || _path.front() != '.') - return _path; using path = boost::filesystem::path; path p(_path); + // Anything that does not start with `.` is an absolute path. + if (p.begin() == p.end() || (*p.begin() != "." && *p.begin() != "..")) + return _path; path result(_reference); result.remove_filename(); for (path::iterator it = p.begin(); it != p.end(); ++it) From 07f34e0023c395b43c5d63ce5a4d2cf5160025fa Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 3 Jan 2017 18:16:33 +0100 Subject: [PATCH 007/414] test: add a test importing a file name starting with a period --- test/libsolidity/Imports.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index bc6adc265..e3f0b281b 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -164,6 +164,14 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings) BOOST_CHECK(c.compile()); } +BOOST_AUTO_TEST_CASE(filename_with_period) +{ + CompilerStack c; + c.addSource("a/a.sol", "import \".b.sol\"; contract A is B {} pragma solidity >=0.0;"); + c.addSource("a/.b.sol", "contract B {} pragma solidity >=0.0;"); + BOOST_CHECK(!c.compile()); +} + BOOST_AUTO_TEST_SUITE_END() } From 4bc934abce48a6c30cdaa9f4cbf7dae15f292831 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 4 Jan 2017 12:34:44 +0100 Subject: [PATCH 008/414] More information about switch, loops and functions. --- docs/assembly.rst | 79 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 8ba3f56cf..255ad3e6d 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -604,12 +604,89 @@ For both ways, the colon points to the name of the variable. =: v // instruction style assignment, puts the result of sload(10) into v } +Switch +------ + +You can use a switch statement as a very basic version of "if/else". +It takes the value of an expression and compares it to several constants. +The branch corresponding to the matching constant is taken. Contrary to the +error-prone behaviour of some programming languages, control flow does +not continue from one case to the next. There is a fallback or default +case called ``default``. + +.. code:: + + assembly { + let x := 0 + switch calldataload(4) + case 0: { x := calldataload(0x24) } + default: { x := calldataload(0x44) } + sstore(0, div(x, 2)) + } + +The list of cases does not require curly braces, but the body of a +case does require them. + +Loops +----- + +Assembly supports a simple for-style loop. For-style loops have +a header containing an initializing part, a condition and a post-iteration +part. The condition has to be a functional-style expression, while +the other two can also be blocks. If the initializing part is a block that +declares any variables, the scope of these variables is extended into the +body (including the condition and the post-iteration part). + +The following example computes the sum of an area in memory. + +.. code:: + + assembly { + let x := 0 + for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } { + x := add(x, mload(i)) + } + } + +Functions +--------- + +Assembly allows the definition of low-level functions. These take their +arguments (and a return PC) from the stack and also put the results onto the +stack. Calling a function looks the same way as executing a functional-style +opcode. + +Functions can be defined anywhere and are visible in the block they are +declared in. Inside a function, you cannot access local variables +defined outside of that function. There is no explicit ``return`` +statement. + +If you call a function that returns multiple values, you have to assign +them to a tuple using ``(a, b) := f(x)`` or ``let (a, b) := f(x)``. + +The following example implements the power function by square-and-multiply. + +.. code:: + + assembly { + function power(base, exponent) -> (result) { + switch exponent + 0: { result := 1 } + 1: { result := base } + default: { + result := power(mul(base, base), div(exponent, 2)) + switch mod(exponent, 2) + 1: { result := mul(base, result) } + } + } + } Things to Avoid --------------- Inline assembly might have a quite high-level look, but it actually is extremely -low-level. The only thing the assembler does for you is re-arranging +low-level. Function calls, loops and switches are converted by simple +rewriting rules and after that, the only thing the assembler does for you is re-arranging functional-style opcodes, managing jump labels, counting stack height for variable access and removing stack slots for assembly-local variables when the end of their block is reached. Especially for those two last cases, it is important From e92af89ec8563334ddb9aa8f208a249c149b3092 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 4 Jan 2017 12:58:35 +0100 Subject: [PATCH 009/414] Typos and clarifications. --- docs/assembly.rst | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 255ad3e6d..2d868e4dc 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -29,7 +29,7 @@ arising when writing manual assembly by the following features: * 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) }`` * switch statements: ``switch x case 0: { y := mul(x, 2) } default: { y := 0 }`` -* function calls: ``function f(x) -> (y) { switch x case 0: { y := 1 } default: y := mul(x, f(sub(x, 1))) }`` +* function calls: ``function f(x) -> (y) { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }`` .. note:: Of the above, loops, function calls and switch statements are not yet implemented. @@ -69,7 +69,7 @@ idea is that assembly libraries will be used to enhance the language in such way Inline assembly could also be beneficial in cases where the optimizer fails to produce efficient code. Please be aware that assembly is much more difficult to write because -the compiler does not perform checks, so you should use it for complex things only if +the compiler does not perform checks, so you should use it only if you really know what you are doing. .. code:: @@ -88,7 +88,7 @@ you really know what you are doing. function sumAsm(uint[] _data) returns (uint o_sum) { for (uint i = 0; i < _data.length; ++i) { assembly { - o_sum := mload(add(add(_data, 0x20), i)) + o_sum := mload(add(add(_data, 0x20), mul(i, 0x20))) } } } @@ -109,7 +109,7 @@ to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, ``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow and the last two obfuscate control flow. Furthermore, functional statements of the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like -``7 x y add mul`` because in the first form, it is much easier to see which +``7 y x add mul`` because in the first form, it is much easier to see which operand is used for which opcode. The second goal is achieved by introducing a desugaring phase that only removes @@ -131,11 +131,14 @@ outer assembly is visible in a sub-assembly. If control flow passes over the end of a block, pop instructions are inserted that match the number of local variables declared in that block, unless the ``}`` is directly preceded by an opcode that does not have a continuing control -flow path. The stack height is reduced by the number of local variables -regardless of that. This mean that labels in the next block will have the -same height as before the block that just ended. +flow path. Whenever a local variable is referenced, the code generator needs +to know its current relative position in the stack and thus it needs to +keep track of the current so-called stack height. +At the end of a block, this implicit stack height is always reduced by the number +of local variables whether ther is a continuing control flow or not. -If at the end of a block, the stack is not balanced, a warning is issued, +This means that the stack height before and after the block should be the same. +If this is not the case, a warning is issued, unless the last instruction in the block did not have a continuing control flow path. Why do we use higher-level constructs like ``switch``, ``for`` and functions: @@ -160,7 +163,7 @@ We consider the runtime bytecode of the following Solidity program:: contract C { function f(uint x) returns (uint y) { - y = 1 + y = 1; for (uint i = 0; i < x; i++) y = 2 * y; } @@ -228,6 +231,7 @@ After the desugaring phase it looks as follows:: $fun_allocate: { $start[$retpos, size]: + // output variables live in the same scope as the arguments. let pos := 0 { pos := mload(0x40) @@ -261,7 +265,7 @@ Syntax ------ Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the -usual ``//`` and ``/* */`` comments. Inline assembly is initiated by ``assembly { ... }`` and inside +usual ``//`` and ``/* */`` comments. Inline assembly is marked by ``assembly { ... }`` and inside these curly braces, the following can be used (see the later sections for more details) - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) @@ -778,8 +782,8 @@ Grammar:: AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase* ( 'default' ':' AssemblyBlock )? AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock - AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')' '->' - ( '(' IdentifierList ')' AssemblyBlock + AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')' + ( '->' '(' IdentifierList ')' )? AssemblyBlock AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression) FunctionalAssemblyExpression ( AssemblyBlock | FunctionalAssemblyExpression) AssemblyBlock SubAssembly = 'assembly' Identifier AssemblyBlock From 584356e7f63bbbd14a073760e2b5091f8b0919ef Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Wed, 4 Jan 2017 23:09:10 -0300 Subject: [PATCH 010/414] Rename contractName to typeName when parsing new expression --- libsolidity/parsing/Parser.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index f02a4a45f..e26e29081 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1153,9 +1153,9 @@ ASTPointer Parser::parseLeftHandSideExpression( else if (m_scanner->currentToken() == Token::New) { expectToken(Token::New); - ASTPointer contractName(parseTypeName(false)); - nodeFactory.setEndPositionFromNode(contractName); - expression = nodeFactory.createNode(contractName); + ASTPointer typeName(parseTypeName(false)); + nodeFactory.setEndPositionFromNode(typeName); + expression = nodeFactory.createNode(typeName); } else expression = parsePrimaryExpression(); From 779a01faa9b18a58a133a120d6aebe1df612cb00 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 6 Jan 2017 16:21:16 +0100 Subject: [PATCH 011/414] Changelog: document #1537 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index a82e8744d..0ac0cb2f0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ BugFixes: * Type checker, code generator: enable access to events of base contracts' names. + * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. ### 0.4.7 (2016-12-15) From 4f62980d52daa58f21dad3ef7caca5f854395c38 Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Wed, 4 Jan 2017 18:29:54 -0600 Subject: [PATCH 012/414] added test Signed-off-by: VoR0220 --- test/libsolidity/Imports.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index e3f0b281b..33978b1fc 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -172,6 +172,17 @@ BOOST_AUTO_TEST_CASE(filename_with_period) BOOST_CHECK(!c.compile()); } +BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_preserved) +{ + CompilerStack c; + c.setRemappings(vector{"foo=vendor/foo_2.0.0", "vendor/bar:foo=vendor/foo_1.0.0", "bar=vendor/bar"}); + c.addSource("main.sol", "import \"foo/foo.sol\"; import {Bar} \"bar/bar.sol\"; contract Main is Foo2, Bar {} pragma solidity >=0.0;"); + c.addSource("vendor/bar/bar.sol", "import \"foo/foo.sol\"; contract Bar is Foo1 {} pragma solidity >=0.0;"); + c.addSource("vendor/foo_1.0.0/foo.sol", "contract Foo1 {} pragma solidity >=0.0;"); + c.addSource("vendor/foo_2.0.0/foo.sol", "contract Foo2 {} pragma solidity >=0.0;"); + BOOST_CHECK(c.compile()); +} + BOOST_AUTO_TEST_SUITE_END() } From 6d9020b3b80aff0baf7d6e023460cfbcd930de6b Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Thu, 5 Jan 2017 12:01:27 -0600 Subject: [PATCH 013/414] fixed test and added solution Signed-off-by: VoR0220 --- libsolidity/interface/CompilerStack.cpp | 29 ++++++++++++++++++++++--- test/libsolidity/Imports.cpp | 4 ++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index ee55f41af..a097e4c8b 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -507,21 +507,44 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context return false; return std::equal(_a.begin(), _a.end(), _b.begin()); }; + // Try to find whether _a is a closer match for context _reference than _b + // Defaults to longest prefix in case of a tie. + auto isClosestContext = [](string const& _a, string const& _b, string const& _reference) + { + int a = _reference.compare(_a); + int b = _reference.compare(_b); + if (a == 0) + return true; + else if (b == 0) + return false; + else if (abs(a) == abs(b)) { + return a > 0; + } + return abs(a) < abs(b); + }; + using filepath = boost::filesystem::path; + filepath context(_context); size_t longestPrefix = 0; string longestPrefixTarget; + string currentClosestContext; + string referenceContext = context.parent_path().generic_string(); for (auto const& redir: m_remappings) { + filepath redirContext(redir.context); // Skip if we already have a closer match. - if (longestPrefix > 0 && redir.prefix.length() <= longestPrefix) + if (longestPrefix > 0 && redir.prefix.length() < longestPrefix) continue; // Skip if redir.context is not a prefix of _context - if (!isPrefixOf(redir.context, _context)) + if (!isPrefixOf(redirContext.generic_string(), _context)) continue; // Skip if the prefix does not match. if (!isPrefixOf(redir.prefix, _path)) continue; - + // Skip if there is a prefix collision and the current context is closer + if (redir.prefix.length() == longestPrefix && !isClosestContext(redirContext.generic_string(), currentClosestContext, referenceContext)) + continue; + currentClosestContext = redir.context; longestPrefix = redir.prefix.length(); longestPrefixTarget = redir.target; } diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 33978b1fc..7945f7299 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -176,8 +176,8 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_pres { CompilerStack c; c.setRemappings(vector{"foo=vendor/foo_2.0.0", "vendor/bar:foo=vendor/foo_1.0.0", "bar=vendor/bar"}); - c.addSource("main.sol", "import \"foo/foo.sol\"; import {Bar} \"bar/bar.sol\"; contract Main is Foo2, Bar {} pragma solidity >=0.0;"); - c.addSource("vendor/bar/bar.sol", "import \"foo/foo.sol\"; contract Bar is Foo1 {} pragma solidity >=0.0;"); + c.addSource("main.sol", "import \"foo/foo.sol\"; import {Bar} from \"bar/bar.sol\"; contract Main is Foo2, Bar {} pragma solidity >=0.0;"); + c.addSource("vendor/bar/bar.sol", "import \"foo/foo.sol\"; contract Bar {Foo1 foo;} pragma solidity >=0.0;"); c.addSource("vendor/foo_1.0.0/foo.sol", "contract Foo1 {} pragma solidity >=0.0;"); c.addSource("vendor/foo_2.0.0/foo.sol", "contract Foo2 {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); From 79dbd40096006eac2861dd86dd22fd0c9ab55c6c Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Sat, 7 Jan 2017 12:11:43 -0600 Subject: [PATCH 014/414] can do this purely on length. Also made prefix filesystem string for more accurate readings. Signed-off-by: VoR0220 --- libsolidity/interface/CompilerStack.cpp | 43 +++++++++---------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index a097e4c8b..1a95f5a65 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -507,47 +507,36 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context return false; return std::equal(_a.begin(), _a.end(), _b.begin()); }; - // Try to find whether _a is a closer match for context _reference than _b - // Defaults to longest prefix in case of a tie. - auto isClosestContext = [](string const& _a, string const& _b, string const& _reference) - { - int a = _reference.compare(_a); - int b = _reference.compare(_b); - if (a == 0) - return true; - else if (b == 0) - return false; - else if (abs(a) == abs(b)) { - return a > 0; - } - return abs(a) < abs(b); - }; using filepath = boost::filesystem::path; - filepath context(_context); size_t longestPrefix = 0; + size_t longestContext = 0; string longestPrefixTarget; - string currentClosestContext; - string referenceContext = context.parent_path().generic_string(); + for (auto const& redir: m_remappings) { filepath redirContext(redir.context); - // Skip if we already have a closer match. - if (longestPrefix > 0 && redir.prefix.length() < longestPrefix) + filepath redirPrefix(redir.prefix); + string contextFileString = redirContext.generic_string(); + string prefixFileString = redirPrefix.generic_string(); + // Skip if there is a prefix collision and the current context is closer + if (longestContext > 0 && contextFileString.length() < longestContext) continue; // Skip if redir.context is not a prefix of _context - if (!isPrefixOf(redirContext.generic_string(), _context)) + if (!isPrefixOf(contextFileString, _context)) + continue; + // Skip if we already have a closer match. + if (longestPrefix > 0 && prefixFileString.length() < longestPrefix) continue; // Skip if the prefix does not match. - if (!isPrefixOf(redir.prefix, _path)) + if (!isPrefixOf(prefixFileString, _path)) continue; - // Skip if there is a prefix collision and the current context is closer - if (redir.prefix.length() == longestPrefix && !isClosestContext(redirContext.generic_string(), currentClosestContext, referenceContext)) - continue; - currentClosestContext = redir.context; - longestPrefix = redir.prefix.length(); + + longestContext = contextFileString.length(); + longestPrefix = prefixFileString.length(); longestPrefixTarget = redir.target; } + string path = longestPrefixTarget; path.append(_path.begin() + longestPrefix, _path.end()); return path; From ceac5c5a0c75e92040749e4dca794b2a07a431f0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 9 Jan 2017 15:15:30 +0100 Subject: [PATCH 015/414] Move explanatory sections and other small changes. --- docs/assembly.rst | 375 ++++++++++++++++++++++++---------------------- 1 file changed, 192 insertions(+), 183 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 2d868e4dc..57c0bf9b3 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -94,177 +94,11 @@ you really know what you are doing. } } -Standalone Assembly -=================== - -This assembly language tries to achieve several goals: - -1. Programs written in it should be readable, even if the code is generated by a compiler from Solidity. -2. The translation from assembly to bytecode should contain as few "surprises" as possible. -3. Control flow should be easy to detect to help in formal verification and optimization. - -In order to achieve the first and last goal, assembly provides high-level constructs -like ``for`` loops, ``switch`` statements and function calls. It should be possible -to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, -``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow -and the last two obfuscate control flow. Furthermore, functional statements of -the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like -``7 y x add mul`` because in the first form, it is much easier to see which -operand is used for which opcode. - -The second goal is achieved by introducing a desugaring phase that only removes -the higher level constructs in a very regular way and still allows inspecting -the generated low-level assembly code. The only non-local operation performed -by the assembler is name lookup of user-defined identifiers (functions, variables, ...), -which follow very simple and regular scoping rules and cleanup of local variables from the stack. - -Scoping: An identifier that is declared (label, variable, function, assembly) -is only visible in the block where it was declared (including nested blocks -inside the current block). It is not legal to access local variables across -function borders, even if they would be in scope. Shadowing is allowed, but -two identifiers with the same name cannot be declared in the same block. -Local variables cannot be accessed before they were declared, but labels, -functions and assemblies can. Assemblies are special blocks that are used -for e.g. returning runtime code or creating contracts. No identifier from an -outer assembly is visible in a sub-assembly. - -If control flow passes over the end of a block, pop instructions are inserted -that match the number of local variables declared in that block, unless the -``}`` is directly preceded by an opcode that does not have a continuing control -flow path. Whenever a local variable is referenced, the code generator needs -to know its current relative position in the stack and thus it needs to -keep track of the current so-called stack height. -At the end of a block, this implicit stack height is always reduced by the number -of local variables whether ther is a continuing control flow or not. - -This means that the stack height before and after the block should be the same. -If this is not the case, a warning is issued, -unless the last instruction in the block did not have a continuing control flow path. - -Why do we use higher-level constructs like ``switch``, ``for`` and functions: - -Using ``switch``, ``for`` and functions, it should be possible to write -complex code without using ``jump`` or ``jumpi`` manually. This makes it much -easier to analyze the control flow, which allows for improved formal -verification and optimization. - -Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. -The position of all local variables on the stack needs to be known, otherwise -neither references to local variables nor removing local variables automatically -from the stack at the end of a block will work properly. Because of that, -every label that is preceded by an instruction that ends or diverts control flow -should be annotated with the current stack layout. This annotation is performed -automatically during the desugaring phase. - -Example: - -We will follow an example compilation from Solidity to desugared assembly. -We consider the runtime bytecode of the following Solidity program:: - - contract C { - function f(uint x) returns (uint y) { - y = 1; - for (uint i = 0; i < x; i++) - y = 2 * y; - } - } - -The following assembly will be generated:: - - { - mstore(0x40, 0x60) // store the "free memory pointer" - // function dispatcher - switch div(calldataload(0), exp(2, 226)) - case 0xb3de648b: { - let (r,) = f(calldataload(4)) - let ret := $allocate(0x20) - mstore(ret, r) - return(ret, 0x20) - } - default: { jump(invalidJumpLabel) } - // memory allocator - function $allocate(size) -> (pos) { - pos := mload(0x40) - mstore(0x40, add(pos, size)) - } - // the contract function - function f(x) -> (y) { - y := 1 - for { let i := 0 } lt(i, x) { i := add(i, 1) } { - y := mul(2, y) - } - } - } - -After the desugaring phase it looks as follows:: - - { - mstore(0x40, 0x60) - { - let $0 := div(calldataload(0), exp(2, 226)) - jumpi($case1, eq($0, 0xb3de648b)) - jump($caseDefault) - $case1: - { - // the function call - we put return label and arguments on the stack - $ret1 calldataload(4) jump($fun_f) - $ret1 [r]: // a label with a [...]-annotation resets the stack height - // to "current block + number of local variables". It also - // introduces a variable, r: - // r is at top of stack, $0 is below (from enclosing block) - $ret2 0x20 jump($fun_allocate) - $ret2 [ret]: // stack here: $0, r, ret (top) - mstore(ret, r) - return(ret, 0x20) - // although it is useless, the jump is automatically inserted, - // since the desugaring process does not analyze control-flow - jump($endswitch) - } - $caseDefault: - { - jump(invalidJumpLabel) - jump($endswitch) - } - $endswitch: - } - jump($afterFunction) - $fun_allocate: - { - $start[$retpos, size]: - // output variables live in the same scope as the arguments. - let pos := 0 - { - pos := mload(0x40) - mstore(0x40, add(pos, size)) - } - swap1 pop swap1 jump - } - $fun_f: - { - start [$retpos, x]: - let y := 0 - { - let i := 0 - $for_begin: - jumpi($for_end, iszero(lt(i, x))) - { - y := mul(2, y) - } - $for_continue: - { i := add(i, 1) } - jump($for_begin) - $for_end: - } // Here, a pop instruction is inserted for i - swap1 pop swap1 jump - } - $afterFunction: - stop - } Syntax ------ -Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the +Assembly parses comments, literals and identifiers exactly as Solidity, so you can use the usual ``//`` and ``/* */`` comments. Inline assembly is marked by ``assembly { ... }`` and inside these curly braces, the following can be used (see the later sections for more details) @@ -273,7 +107,7 @@ these curly braces, the following can be used (see the later sections for more d - opcode in functional style, e.g. ``add(1, mlod(0))`` - labels, e.g. ``name:`` - variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)`` - - identifiers (externals, labels or assembly-local variables), e.g. ``jump(name)``, ``3 x add`` + - identifiers (labels or assembly-local variables and externals if used as inline assembly), e.g. ``jump(name)``, ``3 x add`` - assignments (in "instruction style"), e.g. ``3 =: x`` - assignments in functional style, e.g. ``x := add(y, 3)`` - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` @@ -535,7 +369,7 @@ jumps easier. The following code computes an element in the Fibonacci series. Please note that automatically accessing stack variables can only work if the assembler knows the current stack height. This fails to work if the jump source -and target have different stack heights. It is still fine to use such jumps, +and target have different stack heights. It is still fine to use such jumps, but you should just not access any stack variables (even assembly variables) in that case. Furthermore, the stack height analyser goes through the code opcode by opcode @@ -593,11 +427,12 @@ Assignments are possible to assembly-local variables and to function-local variables. Take care that when you assign to variables that point to memory or storage, you will only change the pointer and not the data. -There are two kinds of assignments: Functional-style and instruction-style. +There are two kinds of assignments: functional-style and instruction-style. For functional-style assignments (``variable := value``), you need to provide a value in a functional-style expression that results in exactly one stack value and for instruction-style (``=: variable``), the value is just taken from the stack top. -For both ways, the colon points to the name of the variable. +For both ways, the colon points to the name of the variable. The assignment +is performed by replacing the variable's value on the stack by the new value. .. code:: @@ -615,7 +450,7 @@ You can use a switch statement as a very basic version of "if/else". It takes the value of an expression and compares it to several constants. The branch corresponding to the matching constant is taken. Contrary to the error-prone behaviour of some programming languages, control flow does -not continue from one case to the next. There is a fallback or default +not continue from one case to the next. There can be a fallback or default case called ``default``. .. code:: @@ -623,8 +458,12 @@ case called ``default``. assembly { let x := 0 switch calldataload(4) - case 0: { x := calldataload(0x24) } - default: { x := calldataload(0x44) } + case 0: { + x := calldataload(0x24) + } + default: { + x := calldataload(0x44) + } sstore(0, div(x, 2)) } @@ -675,13 +514,13 @@ The following example implements the power function by square-and-multiply. assembly { function power(base, exponent) -> (result) { switch exponent - 0: { result := 1 } - 1: { result := base } - default: { - result := power(mul(base, base), div(exponent, 2)) - switch mod(exponent, 2) - 1: { result := mul(base, result) } - } + 0: { result := 1 } + 1: { result := base } + default: { + result := power(mul(base, base), div(exponent, 2)) + switch mod(exponent, 2) + 1: { result := mul(base, result) } + } } } @@ -724,8 +563,175 @@ first slot of the array and then only the array elements follow. please do not rely on that. -Specification -============= +Standalone Assembly +=================== + +The assembly language described as inline assembly above can also be used +standalone and in fact, the plan is to use it as an intermediate language +for the Solidity compiler. In this form, it tries to achieve several goals: + +1. Programs written in it should be readable, even if the code is generated by a compiler from Solidity. +2. The translation from assembly to bytecode should contain as few "surprises" as possible. +3. Control flow should be easy to detect to help in formal verification and optimization. + +In order to achieve the first and last goal, assembly provides high-level constructs +like ``for`` loops, ``switch`` statements and function calls. It should be possible +to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, +``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow +and the last two obfuscate control flow. Furthermore, functional statements of +the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like +``7 y x add mul`` because in the first form, it is much easier to see which +operand is used for which opcode. + +The second goal is achieved by introducing a desugaring phase that only removes +the higher level constructs in a very regular way and still allows inspecting +the generated low-level assembly code. The only non-local operation performed +by the assembler is name lookup of user-defined identifiers (functions, variables, ...), +which follow very simple and regular scoping rules and cleanup of local variables from the stack. + +Scoping: An identifier that is declared (label, variable, function, assembly) +is only visible in the block where it was declared (including nested blocks +inside the current block). It is not legal to access local variables across +function borders, even if they would be in scope. Shadowing is allowed, but +two identifiers with the same name cannot be declared in the same block. +Local variables cannot be accessed before they were declared, but labels, +functions and assemblies can. Assemblies are special blocks that are used +for e.g. returning runtime code or creating contracts. No identifier from an +outer assembly is visible in a sub-assembly. + +If control flow passes over the end of a block, pop instructions are inserted +that match the number of local variables declared in that block, unless the +``}`` is directly preceded by an opcode that does not have a continuing control +flow path. Whenever a local variable is referenced, the code generator needs +to know its current relative position in the stack and thus it needs to +keep track of the current so-called stack height. +At the end of a block, this implicit stack height is always reduced by the number +of local variables whether ther is a continuing control flow or not. + +This means that the stack height before and after the block should be the same. +If this is not the case, a warning is issued, +unless the last instruction in the block did not have a continuing control flow path. + +Why do we use higher-level constructs like ``switch``, ``for`` and functions: + +Using ``switch``, ``for`` and functions, it should be possible to write +complex code without using ``jump`` or ``jumpi`` manually. This makes it much +easier to analyze the control flow, which allows for improved formal +verification and optimization. + +Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. +The position of all local variables on the stack needs to be known, otherwise +neither references to local variables nor removing local variables automatically +from the stack at the end of a block will work properly. Because of that, +every label that is preceded by an instruction that ends or diverts control flow +should be annotated with the current stack layout. This annotation is performed +automatically during the desugaring phase. + +Example: + +We will follow an example compilation from Solidity to desugared assembly. +We consider the runtime bytecode of the following Solidity program:: + + contract C { + function f(uint x) returns (uint y) { + y = 1; + for (uint i = 0; i < x; i++) + y = 2 * y; + } + } + +The following assembly will be generated:: + + { + mstore(0x40, 0x60) // store the "free memory pointer" + // function dispatcher + switch div(calldataload(0), exp(2, 226)) + case 0xb3de648b: { + let (r) = f(calldataload(4)) + let ret := $allocate(0x20) + mstore(ret, r) + return(ret, 0x20) + } + default: { jump(invalidJumpLabel) } + // memory allocator + function $allocate(size) -> (pos) { + pos := mload(0x40) + mstore(0x40, add(pos, size)) + } + // the contract function + function f(x) -> (y) { + y := 1 + for { let i := 0 } lt(i, x) { i := add(i, 1) } { + y := mul(2, y) + } + } + } + +After the desugaring phase it looks as follows:: + + { + mstore(0x40, 0x60) + { + let $0 := div(calldataload(0), exp(2, 226)) + jumpi($case1, eq($0, 0xb3de648b)) + jump($caseDefault) + $case1: + { + // the function call - we put return label and arguments on the stack + $ret1 calldataload(4) jump($fun_f) + $ret1 [r]: // a label with a [...]-annotation resets the stack height + // to "current block + number of local variables". It also + // introduces a variable, r: + // r is at top of stack, $0 is below (from enclosing block) + $ret2 0x20 jump($fun_allocate) + $ret2 [ret]: // stack here: $0, r, ret (top) + mstore(ret, r) + return(ret, 0x20) + // although it is useless, the jump is automatically inserted, + // since the desugaring process does not analyze control-flow + jump($endswitch) + } + $caseDefault: + { + jump(invalidJumpLabel) + jump($endswitch) + } + $endswitch: + } + jump($afterFunction) + $fun_allocate: + { + $start[$retpos, size]: + // output variables live in the same scope as the arguments. + let pos := 0 + { + pos := mload(0x40) + mstore(0x40, add(pos, size)) + } + swap1 pop swap1 jump + } + $fun_f: + { + start [$retpos, x]: + let y := 0 + { + let i := 0 + $for_begin: + jumpi($for_end, iszero(lt(i, x))) + { + y := mul(2, y) + } + $for_continue: + { i := add(i, 1) } + jump($for_begin) + $for_end: + } // Here, a pop instruction is inserted for i + swap1 pop swap1 jump + } + $afterFunction: + stop + } + Assembly happens in four stages: @@ -734,6 +740,9 @@ Assembly happens in four stages: 3. Opcode stream generation 4. Bytecode generation +We will specify steps one to three in a pseudo-formal way. More formal +specifications will follow. + Parsing / Grammar ----------------- From 8ace851831f63035174ae086bb415a0d2efb8277 Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Tue, 10 Jan 2017 07:15:48 -0600 Subject: [PATCH 016/414] much smaller helper function Signed-off-by: VoR0220 --- libsolidity/interface/CompilerStack.cpp | 6 ++---- libsolidity/interface/CompilerStack.h | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1a95f5a65..094360a3a 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -515,10 +515,8 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context for (auto const& redir: m_remappings) { - filepath redirContext(redir.context); - filepath redirPrefix(redir.prefix); - string contextFileString = redirContext.generic_string(); - string prefixFileString = redirPrefix.generic_string(); + string contextFileString = sanitizePath(redir.context); + string prefixFileString = sanitizePath(redir.prefix); // Skip if there is a prefix collision and the current context is closer if (longestContext > 0 && contextFileString.length() < longestContext) continue; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index f98a457a4..ef0aa6fef 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -239,7 +240,8 @@ private: ContractDefinition const& _contract, std::map& _compiledContracts ); - + /// Helper function to return path converted strings. + std::string sanitizePath(std::string const& _path) { return boost::filesystem::path(_path).generic_string(); } void link(); Contract const& contract(std::string const& _contractName = "") const; From d61b911a3a02962d5c38ad8b77ab650352529659 Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Wed, 11 Jan 2017 00:51:28 -0300 Subject: [PATCH 017/414] grammar.txt: Move StorageLocation? up to VariableDeclaration --- docs/grammar.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/grammar.txt b/docs/grammar.txt index b5d2b7803..2300dd00d 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -35,10 +35,10 @@ TypeNameList = '(' ( TypeName (',' TypeName )* )? ')' // semantic restriction: mappings and structs (recursively) containing mappings // are not allowed in argument lists -VariableDeclaration = TypeName Identifier +VariableDeclaration = TypeName StorageLocation? Identifier TypeName = ElementaryTypeName - | UserDefinedTypeName StorageLocation? + | UserDefinedTypeName | Mapping | ArrayTypeName | FunctionTypeName @@ -46,7 +46,7 @@ TypeName = ElementaryTypeName UserDefinedTypeName = Identifier ( '.' Identifier )* Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' -ArrayTypeName = TypeName '[' Expression? ']' StorageLocation? +ArrayTypeName = TypeName '[' Expression? ']' FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' | 'payable' )* ( 'returns' TypeNameList )? StorageLocation = 'memory' | 'storage' From c15b0fb596a3466a8f31f9b4f9cd0289091264aa Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Wed, 11 Jan 2017 01:33:17 -0300 Subject: [PATCH 018/414] grammar.txt: Add named function arguments --- docs/grammar.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/grammar.txt b/docs/grammar.txt index 2300dd00d..f45a62e7f 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -97,7 +97,13 @@ PrimaryExpression = Identifier | StringLiteral | ElementaryTypeNameExpression -FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' Expression? ( ',' Expression )* ')' +ExpressionList = Expression ( ',' Expression )* +NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )* + +FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' FunctionCallArguments ')' +FunctionCallArguments = '{' NameValueList? '}' + | ExpressionList? + NewExpression = 'new' Identifier MemberAccess = Expression '.' Identifier IndexAccess = Expression '[' Expression? ']' From 26eff0e0d8c0c34a614e6c7151e248168f72cab7 Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Wed, 11 Jan 2017 01:35:29 -0300 Subject: [PATCH 019/414] grammar.txt: Replace Identifier for Typename in NewExpression --- docs/grammar.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/grammar.txt b/docs/grammar.txt index f45a62e7f..62b4a0212 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -104,7 +104,7 @@ FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifi FunctionCallArguments = '{' NameValueList? '}' | ExpressionList? -NewExpression = 'new' Identifier +NewExpression = 'new' TypeName MemberAccess = Expression '.' Identifier IndexAccess = Expression '[' Expression? ']' From e02270bbb4a9f151bc25e0e0f96f1e4505542de5 Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Wed, 11 Jan 2017 09:03:41 -0600 Subject: [PATCH 020/414] fixed unused filepath bug Signed-off-by: VoR0220 --- libsolidity/interface/CompilerStack.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 094360a3a..30abad075 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -508,7 +508,6 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context return std::equal(_a.begin(), _a.end(), _b.begin()); }; - using filepath = boost::filesystem::path; size_t longestPrefix = 0; size_t longestContext = 0; string longestPrefixTarget; @@ -524,7 +523,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context if (!isPrefixOf(contextFileString, _context)) continue; // Skip if we already have a closer match. - if (longestPrefix > 0 && prefixFileString.length() < longestPrefix) + if (prefixFileString.length() < longestPrefix) continue; // Skip if the prefix does not match. if (!isPrefixOf(prefixFileString, _path)) From 643182865bc70d654512c45b90363c838e09bef6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 11 Jan 2017 16:20:13 +0100 Subject: [PATCH 021/414] Changelog entry about assembly output --- Changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog.md b/Changelog.md index 0ac0cb2f0..8a005eed2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ ### 0.4.8 (unreleased) +Features: + * Output: Print assembly in new standardized Solidity assembly format. + BugFixes: * Type checker, code generator: enable access to events of base contracts' names. * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. From e96c32a072160428a93720a21c51cd8670d5e664 Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Wed, 11 Jan 2017 09:32:07 -0600 Subject: [PATCH 022/414] changelog entry Signed-off-by: VoR0220 --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 0ac0cb2f0..c06313ba4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,8 @@ ### 0.4.8 (unreleased) BugFixes: + * Remappings: a=b would overwrite c:a=d. This has now been fixed to all modules except + c using b as their target, with c using d as the target. * Type checker, code generator: enable access to events of base contracts' names. * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. From 4585bfdce7716cd4837f71b565bb9a6dff8c2d7e Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Wed, 11 Jan 2017 10:56:35 -0600 Subject: [PATCH 023/414] cleanup Signed-off-by: VoR0220 fixup Signed-off-by: VoR0220 --- Changelog.md | 3 +-- libsolidity/interface/CompilerStack.cpp | 24 ++++++++++++------------ libsolidity/interface/CompilerStack.h | 5 +++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Changelog.md b/Changelog.md index c06313ba4..52f86d405 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,8 +1,7 @@ ### 0.4.8 (unreleased) BugFixes: - * Remappings: a=b would overwrite c:a=d. This has now been fixed to all modules except - c using b as their target, with c using d as the target. + * Remappings: Prefer longer context over longer prefix. * Type checker, code generator: enable access to events of base contracts' names. * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 30abad075..1e571caec 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -510,28 +510,28 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context size_t longestPrefix = 0; size_t longestContext = 0; - string longestPrefixTarget; + string bestMatchTarget; for (auto const& redir: m_remappings) { - string contextFileString = sanitizePath(redir.context); - string prefixFileString = sanitizePath(redir.prefix); - // Skip if there is a prefix collision and the current context is closer - if (longestContext > 0 && contextFileString.length() < longestContext) + string context = sanitizePath(redir.context); + string prefix = sanitizePath(redir.prefix); + // Skip if current context is closer + if (context.length() < longestContext) continue; // Skip if redir.context is not a prefix of _context - if (!isPrefixOf(contextFileString, _context)) + if (!isPrefixOf(context, _context)) continue; - // Skip if we already have a closer match. - if (prefixFileString.length() < longestPrefix) + // Skip if we already have a closer prefix match. + if (prefix.length() < longestPrefix) continue; // Skip if the prefix does not match. - if (!isPrefixOf(prefixFileString, _path)) + if (!isPrefixOf(prefix, _path)) continue; - longestContext = contextFileString.length(); - longestPrefix = prefixFileString.length(); - longestPrefixTarget = redir.target; + longestContext = context.length(); + longestPrefix = prefix.length(); + bestMatchTarget = redir.target; } string path = longestPrefixTarget; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index ef0aa6fef..d49a8df14 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -235,13 +235,14 @@ private: bool checkLibraryNameClashes(); /// @returns the absolute path corresponding to @a _path relative to @a _reference. std::string absolutePath(std::string const& _path, std::string const& _reference) const; + /// Helper function to return path converted strings. + std::string sanitizePath(std::string const& _path) const { return boost::filesystem::path(_path).generic_string(); } + /// Compile a single contract and put the result in @a _compiledContracts. void compileContract( ContractDefinition const& _contract, std::map& _compiledContracts ); - /// Helper function to return path converted strings. - std::string sanitizePath(std::string const& _path) { return boost::filesystem::path(_path).generic_string(); } void link(); Contract const& contract(std::string const& _contractName = "") const; From bff76c1ca0433f88874fded8990f39292483d9f0 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 1 Nov 2016 17:18:00 +0100 Subject: [PATCH 024/414] analysis: report errors when inheritance causes collision --- libsolidity/analysis/NameAndTypeResolver.cpp | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 2a33a5015..fc107ba01 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -289,7 +289,31 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) for (auto const& declaration: nameAndDeclaration.second) // Import if it was declared in the base, is not the constructor and is visible in derived classes if (declaration->scope() == &_base && declaration->isVisibleInDerivedContracts()) - m_currentScope->registerDeclaration(*declaration); + if (!m_currentScope->registerDeclaration(*declaration)) + { + SourceLocation firstDeclarationLocation; + SourceLocation secondDeclarationLocation; + Declaration const* conflictingDeclaration = m_currentScope->conflictingDeclaration(*declaration); + solAssert(conflictingDeclaration, ""); + + if (declaration->location().start < conflictingDeclaration->location().start) + { + firstDeclarationLocation = declaration->location(); + secondDeclarationLocation = conflictingDeclaration->location(); + } + else + { + firstDeclarationLocation = conflictingDeclaration->location(); + secondDeclarationLocation = declaration->location(); + } + + reportDeclarationError( + secondDeclarationLocation, + "Identifier already declared.", + firstDeclarationLocation, + "The previous declaration is here:" + ); + } } void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) From 42b672617392ecb294447f73fd011092cd58702b Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 2 Dec 2016 16:06:01 +0100 Subject: [PATCH 025/414] analysis: allow some shadowings explicitly --- libsolidity/analysis/NameAndTypeResolver.cpp | 8 +++ test/libsolidity/SolidityEndToEndTest.cpp | 54 ------------------- .../SolidityNameAndTypeResolution.cpp | 23 +++++++- 3 files changed, 29 insertions(+), 56 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index fc107ba01..002ff8116 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -296,6 +296,14 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) Declaration const* conflictingDeclaration = m_currentScope->conflictingDeclaration(*declaration); solAssert(conflictingDeclaration, ""); + // Usual shadowing is not an error + if (dynamic_cast(declaration) && dynamic_cast(conflictingDeclaration)) + continue; + + // Usual shadowing is not an error + if (dynamic_cast(declaration) && dynamic_cast(conflictingDeclaration)) + continue; + if (declaration->location().start < conflictingDeclaration->location().start) { firstDeclarationLocation = declaration->location(); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index d85395240..191618317 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4869,60 +4869,6 @@ BOOST_AUTO_TEST_CASE(proper_order_of_overwriting_of_attributes) BOOST_CHECK(callContractFunction("ok()") == encodeArgs(false)); } -BOOST_AUTO_TEST_CASE(proper_overwriting_accessor_by_function) -{ - // bug #1798 - char const* sourceCode = R"( - contract attribute { - bool ok = false; - } - contract func { - function ok() returns (bool) { return true; } - } - - contract attr_func is attribute, func { - function checkOk() returns (bool) { return ok(); } - } - contract func_attr is func, attribute { - function checkOk() returns (bool) { return ok; } - } - )"; - compileAndRun(sourceCode, 0, "attr_func"); - BOOST_CHECK(callContractFunction("ok()") == encodeArgs(true)); - compileAndRun(sourceCode, 0, "func_attr"); - BOOST_CHECK(callContractFunction("checkOk()") == encodeArgs(false)); -} - - -BOOST_AUTO_TEST_CASE(overwriting_inheritance) -{ - // bug #1798 - char const* sourceCode = R"( - contract A { - function ok() returns (uint) { return 1; } - } - contract B { - function ok() returns (uint) { return 2; } - } - contract C { - uint ok = 6; - } - contract AB is A, B { - function ok() returns (uint) { return 4; } - } - contract reversedE is C, AB { - function checkOk() returns (uint) { return ok(); } - } - contract E is AB, C { - function checkOk() returns (uint) { return ok; } - } - )"; - compileAndRun(sourceCode, 0, "reversedE"); - BOOST_CHECK(callContractFunction("checkOk()") == encodeArgs(4)); - compileAndRun(sourceCode, 0, "E"); - BOOST_CHECK(callContractFunction("checkOk()") == encodeArgs(6)); -} - BOOST_AUTO_TEST_CASE(struct_assign_reference_to_struct) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 576421fdf..11ce99974 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1056,7 +1056,7 @@ BOOST_AUTO_TEST_CASE(modifier_overrides_function) contract A { modifier mod(uint a) { _; } } contract B is A { function mod(uint a) { } } )"; - CHECK_ERROR(text, TypeError, ""); + CHECK_ERROR(text, DeclarationError, ""); } BOOST_AUTO_TEST_CASE(function_overrides_modifier) @@ -1065,7 +1065,7 @@ BOOST_AUTO_TEST_CASE(function_overrides_modifier) contract A { function mod(uint a) { } } contract B is A { modifier mod(uint a) { _; } } )"; - CHECK_ERROR(text, TypeError, ""); + CHECK_ERROR(text, DeclarationError, ""); } BOOST_AUTO_TEST_CASE(modifier_returns_value) @@ -4304,6 +4304,25 @@ BOOST_AUTO_TEST_CASE(illegal_override_payable_nonpayable) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(function_variable_mixin) +{ + // bug #1798 (cpp-ethereum), related to #1286 (solidity) + char const* text = R"( + contract attribute { + bool ok = false; + } + contract func { + function ok() returns (bool) { return true; } + } + + contract attr_func is attribute, func { + function checkOk() returns (bool) { return ok(); } + } + )"; + CHECK_ERROR(text, DeclarationError, ""); +} + + BOOST_AUTO_TEST_CASE(payable_constant_conflict) { char const* text = R"( From 91d4e8e0bab6f5285103af6ef84a7813cfbb61a2 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 2 Dec 2016 16:24:53 +0100 Subject: [PATCH 026/414] analysis: changes necessary to compile std/StandardToken.sol --- libsolidity/analysis/DeclarationContainer.cpp | 13 ++++++- libsolidity/analysis/NameAndTypeResolver.cpp | 38 +++++++++++++++---- libsolidity/analysis/TypeChecker.cpp | 19 +++++++++- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index 1599b83ac..f8c12c5bd 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -44,10 +44,19 @@ Declaration const* DeclarationContainer::conflictingDeclaration( if (dynamic_cast(&_declaration)) { - // check that all other declarations with the same name are functions + // check that all other declarations with the same name are functions or a public state variable for (Declaration const* declaration: declarations) - if (!dynamic_cast(declaration)) + { + if (dynamic_cast(declaration)) + continue; + if (auto variableDeclaration = dynamic_cast(declaration)) + { + if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic()) + continue; return declaration; + } + return declaration; + } } else if (declarations.size() == 1 && declarations.front() == &_declaration) return nullptr; diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 002ff8116..ee05910c8 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -260,20 +260,42 @@ vector NameAndTypeResolver::cleanedDeclarations( for (auto it = _declarations.begin(); it != _declarations.end(); ++it) { solAssert(*it, ""); - // the declaration is functionDefinition while declarations > 1 - FunctionDefinition const& functionDefinition = dynamic_cast(**it); - FunctionType functionType(functionDefinition); - for (auto parameter: functionType.parameterTypes() + functionType.returnParameterTypes()) - if (!parameter) - reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); + // the declaration is functionDefinition or a VariableDeclaration while declarations > 1 + solAssert(dynamic_cast(*it) || dynamic_cast(*it), + "Found overloading involving something not a function or a variable"); + + shared_ptr functionType {}; + + if (FunctionDefinition const* functionDefinition = dynamic_cast(*it)) + { + functionType = make_shared(*functionDefinition); + for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) + if (!parameter) + reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); + } + else + { + VariableDeclaration const* variableDeclaration = dynamic_cast(*it); + functionType = make_shared(*variableDeclaration); + } + solAssert(functionType, "failed to determine the function type of the overloaded"); if (uniqueFunctions.end() == find_if( uniqueFunctions.begin(), uniqueFunctions.end(), [&](Declaration const* d) { - FunctionType newFunctionType(dynamic_cast(*d)); - return functionType.hasEqualArgumentTypes(newFunctionType); + if (FunctionDefinition const* functionDefinition = dynamic_cast(d)) + { + FunctionType const newFunctionType(*functionDefinition); + return functionType->hasEqualArgumentTypes(newFunctionType); + } + else if (VariableDeclaration const* variableDeclaration = dynamic_cast(d)) + { + FunctionType const newFunctionType(*variableDeclaration); + return functionType->hasEqualArgumentTypes(newFunctionType); + } + return false; // to make compiler happy } )) uniqueFunctions.push_back(*it); diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index e414e27c4..67c8ac175 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1500,8 +1500,23 @@ bool TypeChecker::visit(Identifier const& _identifier) if (!annotation.referencedDeclaration) { if (!annotation.argumentTypes) - fatalTypeError(_identifier.location(), "Unable to determine overloaded type."); - if (annotation.overloadedDeclarations.empty()) + { + // The identifier should be a public state variable shadowing other functions + vector candidates; + + for (Declaration const* declaration: annotation.overloadedDeclarations) + { + if (VariableDeclaration const* variableDeclaration = dynamic_cast(declaration)) + candidates.push_back(declaration); + } + if (candidates.empty()) + fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup."); + else if (candidates.size() == 1) + annotation.referencedDeclaration = candidates.front(); + else + fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup."); + } + else if (annotation.overloadedDeclarations.empty()) fatalTypeError(_identifier.location(), "No candidates for overload resolution found."); else if (annotation.overloadedDeclarations.size() == 1) annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin(); From 0216f34010343af6285305b59d47db62261c03c6 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 5 Dec 2016 12:03:07 +0100 Subject: [PATCH 027/414] analysis: avoid emscripten build failure --- libsolidity/analysis/NameAndTypeResolver.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index ee05910c8..260965ae3 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -264,11 +264,11 @@ vector NameAndTypeResolver::cleanedDeclarations( solAssert(dynamic_cast(*it) || dynamic_cast(*it), "Found overloading involving something not a function or a variable"); - shared_ptr functionType {}; + unique_ptr functionType {}; if (FunctionDefinition const* functionDefinition = dynamic_cast(*it)) { - functionType = make_shared(*functionDefinition); + functionType = unique_ptr(new FunctionType(*functionDefinition)); for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) if (!parameter) reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); @@ -276,7 +276,7 @@ vector NameAndTypeResolver::cleanedDeclarations( else { VariableDeclaration const* variableDeclaration = dynamic_cast(*it); - functionType = make_shared(*variableDeclaration); + functionType = unique_ptr(new FunctionType(*variableDeclaration)); } solAssert(functionType, "failed to determine the function type of the overloaded"); From 9602318eab7affca78b8b5db018e9e646f40e84e Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 14 Dec 2016 14:18:11 +0100 Subject: [PATCH 028/414] test: allow multiple errors in modifier-function overriding --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 11ce99974..9f6ea2b3b 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1056,7 +1056,9 @@ BOOST_AUTO_TEST_CASE(modifier_overrides_function) contract A { modifier mod(uint a) { _; } } contract B is A { function mod(uint a) { } } )"; - CHECK_ERROR(text, DeclarationError, ""); + // Error: Identifier already declared. + // Error: Override changes modifier to function. + CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, ""); } BOOST_AUTO_TEST_CASE(function_overrides_modifier) @@ -1065,7 +1067,9 @@ BOOST_AUTO_TEST_CASE(function_overrides_modifier) contract A { function mod(uint a) { } } contract B is A { modifier mod(uint a) { _; } } )"; - CHECK_ERROR(text, DeclarationError, ""); + // Error: Identifier already declared. + // Error: Override changes function to modifier. + CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, ""); } BOOST_AUTO_TEST_CASE(modifier_returns_value) From bd6e65a89c1e90a01740c61af84b17be7ade26a0 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 3 Jan 2017 19:40:50 +0100 Subject: [PATCH 029/414] docs: document the resriction about a function and an event of the same name, and so on --- docs/contracts.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/contracts.rst b/docs/contracts.rst index e82b7495f..edc42c3d7 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -877,6 +877,13 @@ cannot be resolved. A simple rule to remember is to specify the base classes in the order from "most base-like" to "most derived". +Inheriting Different Kinds of Members of the Same Name +====================================================== + +When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. +This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. +As an exception, a state variable accessor can override a public function. + .. index:: ! contract;abstract, ! abstract contract ****************** From e254a59bd29b7bf226197eaa836fc163be16ef09 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 9 Jan 2017 18:32:02 +0100 Subject: [PATCH 030/414] Changelog: add a line about #1479 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 8a005eed2..81f08c9fb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Features: BugFixes: * Type checker, code generator: enable access to events of base contracts' names. * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. + * Type checker, disallow inheritances of different kinds (e.g. a function and a modifier) of members of the same name ### 0.4.7 (2016-12-15) From 4542f459f165502ed9537bb570de44640cdc4228 Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Wed, 11 Jan 2017 11:45:14 -0600 Subject: [PATCH 031/414] added fix and a test for order independence of nested prefixing Signed-off-by: VoR0220 --- libsolidity/interface/CompilerStack.cpp | 4 ++-- test/libsolidity/Imports.cpp | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1e571caec..e4c351ffd 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -523,7 +523,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context if (!isPrefixOf(context, _context)) continue; // Skip if we already have a closer prefix match. - if (prefix.length() < longestPrefix) + if (prefix.length() < longestPrefix && context.length() == longestContext) continue; // Skip if the prefix does not match. if (!isPrefixOf(prefix, _path)) @@ -534,7 +534,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context bestMatchTarget = redir.target; } - string path = longestPrefixTarget; + string path = bestMatchTarget; path.append(_path.begin() + longestPrefix, _path.end()); return path; } diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 7945f7299..712f133a3 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -183,6 +183,24 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_pres BOOST_CHECK(c.compile()); } +BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent) +{ + CompilerStack c; + c.setRemappings(vector{"a:x/y/z=d", "a/b:x=e"}); + c.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); + c.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); + c.addSource("x/y/z/z.sol", "contract D {} pragma solidity >=0.0;"); + c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); + BOOST_CHECK(c.compile()); + CompilerStack d; + d.setRemappings(vector{"a/b:x=e", "a:x/y/z=d"}); + d.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); + d.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); + d.addSource("x/y/z/z.sol", "contract D {} pragma solidity >=0.0;"); + d.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); + BOOST_CHECK(d.compile()); +} + BOOST_AUTO_TEST_SUITE_END() } From b6508ca992531154a572320bf8bb117e3b9294b9 Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Wed, 11 Jan 2017 12:03:54 -0600 Subject: [PATCH 032/414] fixed Signed-off-by: VoR0220 --- libsolidity/interface/CompilerStack.cpp | 4 ++-- test/libsolidity/Imports.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index e4c351ffd..a31df5846 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -516,6 +516,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context { string context = sanitizePath(redir.context); string prefix = sanitizePath(redir.prefix); + // Skip if current context is closer if (context.length() < longestContext) continue; @@ -531,9 +532,8 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context longestContext = context.length(); longestPrefix = prefix.length(); - bestMatchTarget = redir.target; + bestMatchTarget = sanitizePath(redir.target); } - string path = bestMatchTarget; path.append(_path.begin() + longestPrefix, _path.end()); return path; diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 712f133a3..56895fdc7 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -189,14 +189,14 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent) c.setRemappings(vector{"a:x/y/z=d", "a/b:x=e"}); c.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); c.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); - c.addSource("x/y/z/z.sol", "contract D {} pragma solidity >=0.0;"); + c.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); CompilerStack d; d.setRemappings(vector{"a/b:x=e", "a:x/y/z=d"}); d.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); d.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); - d.addSource("x/y/z/z.sol", "contract D {} pragma solidity >=0.0;"); + d.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); d.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); BOOST_CHECK(d.compile()); } From 5e29b4cde228b7aab0ae6c22ef4190d26cb58ab8 Mon Sep 17 00:00:00 2001 From: Anders Brownworth Date: Wed, 11 Jan 2017 13:13:11 -0500 Subject: [PATCH 033/414] Re-word somewhat bumpy sentence to be more clear. --- docs/introduction-to-smart-contracts.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index aee1e03b3..4c134abcf 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -283,8 +283,8 @@ determined at the time the contract is created (it is derived from the creator address and the number of transactions sent from that address, the so-called "nonce"). -Apart from the fact whether an account stores code or not, -the EVM treats the two types equally, though. +Regardless of whether or not the account stores code, the two types are +treated equally by the EVM. Every account has a persistent key-value store mapping 256-bit words to 256-bit words called **storage**. From eda147f47bb5a0dfbed50aa2c2224daebafa76a8 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 10 Jan 2017 16:26:13 +0100 Subject: [PATCH 034/414] ast: add Declaration::functionType() --- libsolidity/analysis/NameAndTypeResolver.cpp | 21 +++---- libsolidity/ast/AST.cpp | 61 ++++++++++++++++++++ libsolidity/ast/AST.h | 12 ++++ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 260965ae3..47a9df1bd 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -264,22 +264,15 @@ vector NameAndTypeResolver::cleanedDeclarations( solAssert(dynamic_cast(*it) || dynamic_cast(*it), "Found overloading involving something not a function or a variable"); - unique_ptr functionType {}; - - if (FunctionDefinition const* functionDefinition = dynamic_cast(*it)) - { - functionType = unique_ptr(new FunctionType(*functionDefinition)); - for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) - if (!parameter) - reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); - } - else - { - VariableDeclaration const* variableDeclaration = dynamic_cast(*it); - functionType = unique_ptr(new FunctionType(*variableDeclaration)); - } + shared_ptr functionType { (*it)->functionType(false) }; + if (!functionType) + functionType = (*it)->functionType(true); solAssert(functionType, "failed to determine the function type of the overloaded"); + for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) + if (!parameter) + reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); + if (uniqueFunctions.end() == find_if( uniqueFunctions.begin(), uniqueFunctions.end(), diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 78d8949ca..ac92eaa32 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -274,6 +274,45 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const return static_cast(*m_annotation); } +shared_ptr FunctionDefinition::functionType(bool _internal) const +{ + if (_internal) + { + switch (visibility()) + { + case Declaration::Visibility::Default: + solAssert(false, "visibility() should not return Default"); + case Declaration::Visibility::Private: + case Declaration::Visibility::Internal: + case Declaration::Visibility::Public: + return make_shared(*this, _internal); + case Declaration::Visibility::External: + return {}; + default: + solAssert(false, "visibility() should not return a Visibility"); + } + } + else + { + switch (visibility()) + { + case Declaration::Visibility::Default: + solAssert(false, "visibility() should not return Default"); + case Declaration::Visibility::Private: + case Declaration::Visibility::Internal: + return {}; + case Declaration::Visibility::Public: + case Declaration::Visibility::External: + return make_shared(*this, _internal); + default: + solAssert(false, "visibility() should not return a Visibility"); + } + } + + // To make the compiler happy + return {}; +} + TypePointer FunctionDefinition::type() const { return make_shared(*this); @@ -365,6 +404,28 @@ TypePointer VariableDeclaration::type() const return annotation().type; } +shared_ptr VariableDeclaration::functionType(bool _internal) const +{ + if (_internal) + return {}; + switch (visibility()) + { + case Declaration::Visibility::Default: + solAssert(false, "visibility() should not return Default"); + case Declaration::Visibility::Private: + case Declaration::Visibility::Internal: + return {}; + case Declaration::Visibility::Public: + case Declaration::Visibility::External: + return make_shared(*this); + default: + solAssert(false, "visibility() should not return a Visibility"); + } + + // To make the compiler happy + return {}; +} + VariableDeclarationAnnotation& VariableDeclaration::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index ab4be1eaf..088d68dbe 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -171,6 +171,10 @@ public: /// This can only be called once types of variable declarations have already been resolved. virtual TypePointer type() const = 0; + /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. + /// @returns null when it is not accessible as a function. + virtual std::shared_ptr functionType(bool /*_internal*/) const { return {}; } + protected: virtual Visibility defaultVisibility() const { return Visibility::Public; } @@ -581,6 +585,10 @@ public: virtual TypePointer type() const override; + /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. + /// @returns null when it is not accessible as a function. + virtual std::shared_ptr functionType(bool /*_internal*/) const override; + virtual FunctionDefinitionAnnotation& annotation() const override; private: @@ -643,6 +651,10 @@ public: virtual TypePointer type() const override; + /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. + /// @returns null when it is not accessible as a function. + virtual std::shared_ptr functionType(bool /*_internal*/) const override; + virtual VariableDeclarationAnnotation& annotation() const override; protected: From 7fea4b7360d1db01a33fd2c5a90e966880232e47 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 10 Jan 2017 16:34:55 +0100 Subject: [PATCH 035/414] analysis: use Declaration::functionType() in another location --- libsolidity/analysis/NameAndTypeResolver.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 47a9df1bd..08323243a 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -278,17 +278,10 @@ vector NameAndTypeResolver::cleanedDeclarations( uniqueFunctions.end(), [&](Declaration const* d) { - if (FunctionDefinition const* functionDefinition = dynamic_cast(d)) - { - FunctionType const newFunctionType(*functionDefinition); - return functionType->hasEqualArgumentTypes(newFunctionType); - } - else if (VariableDeclaration const* variableDeclaration = dynamic_cast(d)) - { - FunctionType const newFunctionType(*variableDeclaration); - return functionType->hasEqualArgumentTypes(newFunctionType); - } - return false; // to make compiler happy + shared_ptr newFunctionType { d->functionType(false) }; + if (!newFunctionType) + newFunctionType = d->functionType(true); + return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType); } )) uniqueFunctions.push_back(*it); From abc24420a7837557e5160db8af83ea3be5a371c3 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 10 Jan 2017 18:55:36 +0100 Subject: [PATCH 036/414] ast: events have FunctionType too --- libsolidity/ast/AST.cpp | 18 +++++++++++++----- libsolidity/ast/AST.h | 7 ++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index ac92eaa32..6f7a64dcb 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -274,7 +274,7 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const return static_cast(*m_annotation); } -shared_ptr FunctionDefinition::functionType(bool _internal) const +shared_ptr FunctionDefinition::functionType(bool _internal) const { if (_internal) { @@ -285,7 +285,7 @@ shared_ptr FunctionDefinition::functionType(bool _internal) case Declaration::Visibility::Private: case Declaration::Visibility::Internal: case Declaration::Visibility::Public: - return make_shared(*this, _internal); + return make_shared(*this, _internal); case Declaration::Visibility::External: return {}; default: @@ -303,7 +303,7 @@ shared_ptr FunctionDefinition::functionType(bool _internal) return {}; case Declaration::Visibility::Public: case Declaration::Visibility::External: - return make_shared(*this, _internal); + return make_shared(*this, _internal); default: solAssert(false, "visibility() should not return a Visibility"); } @@ -347,6 +347,14 @@ TypePointer EventDefinition::type() const return make_shared(*this); } +std::shared_ptr EventDefinition::functionType(bool _internal) const +{ + if (_internal) + return make_shared(*this); + else + return {}; +} + EventDefinitionAnnotation& EventDefinition::annotation() const { if (!m_annotation) @@ -404,7 +412,7 @@ TypePointer VariableDeclaration::type() const return annotation().type; } -shared_ptr VariableDeclaration::functionType(bool _internal) const +shared_ptr VariableDeclaration::functionType(bool _internal) const { if (_internal) return {}; @@ -417,7 +425,7 @@ shared_ptr VariableDeclaration::functionType(bool _internal) return {}; case Declaration::Visibility::Public: case Declaration::Visibility::External: - return make_shared(*this); + return make_shared(*this); default: solAssert(false, "visibility() should not return a Visibility"); } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 088d68dbe..2d0924089 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -173,7 +173,7 @@ public: /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @returns null when it is not accessible as a function. - virtual std::shared_ptr functionType(bool /*_internal*/) const { return {}; } + virtual std::shared_ptr functionType(bool /*_internal*/) const { return {}; } protected: virtual Visibility defaultVisibility() const { return Visibility::Public; } @@ -587,7 +587,7 @@ public: /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @returns null when it is not accessible as a function. - virtual std::shared_ptr functionType(bool /*_internal*/) const override; + virtual std::shared_ptr functionType(bool /*_internal*/) const override; virtual FunctionDefinitionAnnotation& annotation() const override; @@ -653,7 +653,7 @@ public: /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @returns null when it is not accessible as a function. - virtual std::shared_ptr functionType(bool /*_internal*/) const override; + virtual std::shared_ptr functionType(bool /*_internal*/) const override; virtual VariableDeclarationAnnotation& annotation() const override; @@ -752,6 +752,7 @@ public: bool isAnonymous() const { return m_anonymous; } virtual TypePointer type() const override; + virtual std::shared_ptr functionType(bool /*_internal*/) const override; virtual EventDefinitionAnnotation& annotation() const override; From ee0bf07487068a46af0d286208a0224a58721527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 12 Jan 2017 14:14:19 +0100 Subject: [PATCH 037/414] Travis CI: Install latest CMake --- .travis.yml | 2 ++ scripts/install_cmake.sh | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100755 scripts/install_cmake.sh diff --git a/.travis.yml b/.travis.yml index a50674c1b..017f1a611 100644 --- a/.travis.yml +++ b/.travis.yml @@ -140,9 +140,11 @@ cache: directories: - boost_1_57_0 - build + - $HOME/.local install: - test $TRAVIS_INSTALL_DEPS != On || ./scripts/install_deps.sh + - test "$TRAVIS_OS_NAME" != "linux" || ./scripts/install_cmake.sh - echo -n "$TRAVIS_COMMIT" > commit_hash.txt before_script: - test $TRAVIS_EMSCRIPTEN != On || ./scripts/build_emscripten.sh diff --git a/scripts/install_cmake.sh b/scripts/install_cmake.sh new file mode 100755 index 000000000..00d013b96 --- /dev/null +++ b/scripts/install_cmake.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env sh + +# This script downloads the CMake binary and installs it in ~/.local directory +# (the cmake executable will be in ~/.local/bin). +# This is mostly suitable for CIs, not end users. + +set -e + +VERSION=3.7.1 +PREFIX=~/.local + +OS=$(uname -s) +case $OS in +Linux) SHA256=7b4b7a1d9f314f45722899c0521c261e4bfab4a6b532609e37fef391da6bade2;; +Darwin) SHA256=1851d1448964893fdc5a8c05863326119f397a3790e0c84c40b83499c7960267;; +esac + + +BIN=$PREFIX/bin + +if test -f $BIN/cmake && ($BIN/cmake --version | grep -q "$VERSION"); then + echo "CMake $VERSION already installed in $BIN" +else + FILE=cmake-$VERSION-$OS-x86_64.tar.gz + URL=https://cmake.org/files/v3.7/$FILE + ERROR=0 + TMPFILE=$(mktemp --tmpdir cmake-$VERSION-$OS-x86_64.XXXXXXXX.tar.gz) + echo "Downloading CMake ($URL)..." + wget "$URL" -O "$TMPFILE" -nv + if ! (shasum -a256 "$TMPFILE" | grep -q "$SHA256"); then + echo "Checksum mismatch ($TMPFILE)" + exit 1 + fi + mkdir -p "$PREFIX" + tar xzf "$TMPFILE" -C "$PREFIX" --strip 1 + rm $TMPFILE +fi From 4713dd625df08fe6a5fe9cd29febcc225951c36d Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 23 Jan 2017 15:46:03 +0100 Subject: [PATCH 038/414] Fix error tag usage in lll. --- libevmasm/Assembly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 845abfd4e..0247593bf 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -40,7 +40,7 @@ void Assembly::append(Assembly const& _a) auto newDeposit = m_deposit + _a.deposit(); for (AssemblyItem i: _a.m_items) { - if (i.type() == Tag || i.type() == PushTag) + if (i.type() == Tag || (i.type() == PushTag && i != errorTag())) i.setData(i.data() + m_usedTags); else if (i.type() == PushSub || i.type() == PushSubSize) i.setData(i.data() + m_subs.size()); From 0cb95ac35e4ae2ce1b9f929c00a2aed282bd4a70 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 12 Jan 2017 14:52:01 +0000 Subject: [PATCH 039/414] LLL: add test for (panic) in a sequence --- test/liblll/EndToEndTest.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp index 77c1f7409..c7c1fd3b4 100644 --- a/test/liblll/EndToEndTest.cpp +++ b/test/liblll/EndToEndTest.cpp @@ -50,6 +50,13 @@ BOOST_AUTO_TEST_CASE(bare_panic) BOOST_REQUIRE(m_output.empty()); } +BOOST_AUTO_TEST_CASE(panic) +{ + char const* sourceCode = "{ (panic) }"; + compileAndRunWithoutCheck(sourceCode); + BOOST_REQUIRE(m_output.empty()); +} + BOOST_AUTO_TEST_CASE(exp_operator_const) { char const* sourceCode = R"( From afad40ac5a259cf60cd2f5c8b31495f3f64f3e8e Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 6 Jan 2017 11:33:08 +0100 Subject: [PATCH 040/414] Optimise AssemblyItem::m_data. --- cmake/EthCompilerSettings.cmake | 2 +- libevmasm/Assembly.cpp | 2 +- libevmasm/AssemblyItem.cpp | 8 +++--- libevmasm/AssemblyItem.h | 46 ++++++++++++++++++++++++--------- libevmasm/ExpressionClasses.cpp | 23 ++++++++++++++--- 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index c734423b7..65742dfb1 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -71,7 +71,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-fPIC) # Configuration-specific compiler settings. - set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DETH_DEBUG") + set(CMAKE_CXX_FLAGS_DEBUG " -g -DETH_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index f50a38a67..845abfd4e 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -416,7 +416,7 @@ LinkerObject const& Assembly::assemble() const switch (i.type()) { case Operation: - ret.bytecode.push_back((byte)i.data()); + ret.bytecode.push_back((byte)i.instruction()); break; case PushString: { diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index b790e094c..6c7d5425e 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -29,19 +29,19 @@ using namespace dev::eth; AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const { - assertThrow(m_data < (u256(1) << 64), Exception, "Tag already has subassembly set."); + assertThrow(data() < (u256(1) << 64), Exception, "Tag already has subassembly set."); assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); AssemblyItem r = *this; r.m_type = PushTag; - r.setPushTagSubIdAndTag(_subId, size_t(m_data)); + r.setPushTagSubIdAndTag(_subId, size_t(data())); return r; } pair AssemblyItem::splitForeignPushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); - return make_pair(size_t(m_data / (u256(1) << 64)) - 1, size_t(m_data)); + return make_pair(size_t((data()) / (u256(1) << 64)) - 1, size_t(data())); } void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) @@ -60,7 +60,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const case PushString: return 33; case Push: - return 1 + max(1, dev::bytesRequired(m_data)); + return 1 + max(1, dev::bytesRequired(data())); case PushSubSize: case PushProgramSize: return 4; // worst case: a 16MB program diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index cddfb17cf..7fcaa1451 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -59,16 +59,22 @@ public: AssemblyItem(u256 _push, SourceLocation const& _location = SourceLocation()): AssemblyItem(Push, _push, _location) { } AssemblyItem(solidity::Instruction _i, SourceLocation const& _location = SourceLocation()): - AssemblyItem(Operation, byte(_i), _location) { } + m_type(Operation), + m_instruction(_i), + m_location(_location) + {} AssemblyItem(AssemblyItemType _type, u256 _data = 0, SourceLocation const& _location = SourceLocation()): m_type(_type), - m_data(_data), m_location(_location) { + if (m_type == Operation) + m_instruction = Instruction(byte(_data)); + else + m_data = std::make_shared(_data); } - AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); } - AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); } + AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, data()); } + AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, data()); } /// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies. /// @param _subId the identifier of the subassembly the tag is taken from. AssemblyItem toSubAssemblyTag(size_t _subId) const; @@ -79,18 +85,34 @@ public: void setPushTagSubIdAndTag(size_t _subId, size_t _tag); AssemblyItemType type() const { return m_type; } - u256 const& data() const { return m_data; } - void setType(AssemblyItemType const _type) { m_type = _type; } - void setData(u256 const& _data) { m_data = _data; } + u256 const& data() const { assertThrow(m_type != Operation, Exception, ""); return *m_data; } + //void setType(AssemblyItemType const _type) { m_type = _type; } + void setData(u256 const& _data) { assertThrow(m_type != Operation, Exception, ""); m_data = std::make_shared(_data); } /// @returns the instruction of this item (only valid if type() == Operation) - Instruction instruction() const { return Instruction(byte(m_data)); } + Instruction instruction() const { assertThrow(m_type == Operation, Exception, ""); return m_instruction; } /// @returns true if the type and data of the items are equal. - bool operator==(AssemblyItem const& _other) const { return m_type == _other.m_type && m_data == _other.m_data; } + bool operator==(AssemblyItem const& _other) const + { + if (type() != _other.type()) + return false; + if (type() == Operation) + return instruction() == _other.instruction(); + else + return data() == _other.data(); + } bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); } /// Less-than operator compatible with operator==. - bool operator<(AssemblyItem const& _other) const { return std::tie(m_type, m_data) < std::tie(_other.m_type, _other.m_data); } + bool operator<(AssemblyItem const& _other) const + { + if (type() != _other.type()) + return type() < _other.type(); + else if (type() == Operation) + return instruction() < _other.instruction(); + else + return data() < _other.data(); + } /// @returns an upper bound for the number of bytes required by this item, assuming that /// the value of a jump tag takes @a _addressLength bytes. @@ -100,7 +122,6 @@ public: /// @returns true if the assembly item can be used in a functional context. bool canBeFunctional() const; - bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); } void setLocation(SourceLocation const& _location) { m_location = _location; } SourceLocation const& location() const { return m_location; } @@ -115,7 +136,8 @@ public: private: AssemblyItemType m_type; - u256 m_data; + Instruction m_instruction; ///< Only valid if m_type == Operation + std::shared_ptr m_data; ///< Only valid if m_type != Operation SourceLocation m_location; JumpType m_jumpType = JumpType::Ordinary; /// Pushed value for operations with data to be determined during assembly stage, diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index d5ccd7e3c..b903ae044 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -40,8 +40,18 @@ bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression cons assertThrow(!!item && !!_other.item, OptimizerException, ""); auto type = item->type(); auto otherType = _other.item->type(); - return std::tie(type, item->data(), arguments, sequenceNumber) < - std::tie(otherType, _other.item->data(), _other.arguments, _other.sequenceNumber); + if (type != otherType) + return type < otherType; + else if (type == Operation) + { + auto instr = item->instruction(); + auto otherInstr = _other.item->instruction(); + return std::tie(instr, arguments, sequenceNumber) < + std::tie(otherInstr, _other.arguments, _other.sequenceNumber); + } + else + return std::tie(item->data(), arguments, sequenceNumber) < + std::tie(_other.item->data(), _other.arguments, _other.sequenceNumber); } ExpressionClasses::Id ExpressionClasses::find( @@ -479,8 +489,13 @@ bool Pattern::matchesBaseItem(AssemblyItem const* _item) const return false; if (m_type != _item->type()) return false; - if (m_requireDataMatch && m_data != _item->data()) - return false; + if (m_requireDataMatch) + { + if (m_type == Operation) + return m_data == u256(byte(_item->instruction())); + else + return m_data == _item->data(); + } return true; } From 652d8dab191fcd50ce59b049177ae3281255f309 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 6 Jan 2017 11:25:02 +0100 Subject: [PATCH 041/414] Optimize pattern matcher. --- libevmasm/ExpressionClasses.cpp | 32 ++++++++++++++++++-------------- libevmasm/ExpressionClasses.h | 6 ++++-- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index b903ae044..fccac1b0a 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -185,7 +185,7 @@ class Rules: public boost::noncopyable public: Rules(); void resetMatchGroups() { m_matchGroups.clear(); } - vector>> rules() const { return m_rules; } + vector>> const& rules() const { return m_rules; } private: using Expression = ExpressionClasses::Expression; @@ -417,8 +417,7 @@ ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate co Pattern::Pattern(Instruction _instruction, std::vector const& _arguments): m_type(Operation), - m_requireDataMatch(true), - m_data(_instruction), + m_instruction(_instruction), m_arguments(_arguments) { } @@ -449,7 +448,10 @@ bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const { - return AssemblyItem(m_type, m_data, _location); + if (m_type == Operation) + return AssemblyItem(m_instruction, _location); + else + return AssemblyItem(m_type, data(), _location); } string Pattern::toString() const @@ -458,16 +460,16 @@ string Pattern::toString() const switch (m_type) { case Operation: - s << instructionInfo(Instruction(unsigned(m_data))).name; + s << instructionInfo(m_instruction).name; break; case Push: - s << "PUSH " << hex << m_data; + s << "PUSH " << hex << data(); break; case UndefinedItem: s << "ANY"; break; default: - s << "t=" << dec << m_type << " d=" << hex << m_data; + s << "t=" << dec << m_type << " d=" << hex << data(); break; } if (!m_requireDataMatch) @@ -489,13 +491,10 @@ bool Pattern::matchesBaseItem(AssemblyItem const* _item) const return false; if (m_type != _item->type()) return false; - if (m_requireDataMatch) - { - if (m_type == Operation) - return m_data == u256(byte(_item->instruction())); - else - return m_data == _item->data(); - } + else if (m_type == Operation) + return m_instruction == _item->instruction(); + else if (m_requireDataMatch) + return data() == _item->data(); return true; } @@ -507,6 +506,11 @@ Pattern::Expression const& Pattern::matchGroupValue() const return *(*m_matchGroups)[m_matchGroup]; } +u256 const& Pattern::data() const +{ + assertThrow(m_data, OptimizerException, ""); + return *m_data; +} ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location) { diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h index 11a698dd8..c91b227f4 100644 --- a/libevmasm/ExpressionClasses.h +++ b/libevmasm/ExpressionClasses.h @@ -135,7 +135,7 @@ public: // Matches a specific constant value. Pattern(unsigned _value): Pattern(u256(_value)) {} // Matches a specific constant value. - Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(_value) {} + Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(std::make_shared(_value)) {} // Matches a specific assembly item type or anything if not given. Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} // Matches a given instruction with given arguments @@ -160,10 +160,12 @@ public: private: bool matchesBaseItem(AssemblyItem const* _item) const; Expression const& matchGroupValue() const; + u256 const& data() const; AssemblyItemType m_type; bool m_requireDataMatch = false; - u256 m_data = 0; + Instruction m_instruction; ///< Only valid if m_type is Operation + std::shared_ptr m_data; ///< Only valid if m_type is not Operation std::vector m_arguments; unsigned m_matchGroup = 0; std::map* m_matchGroups = nullptr; From a3b01eca27b19311a05b9c7b7e2ed8eb51b0e51c Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 6 Jan 2017 15:17:16 +0100 Subject: [PATCH 042/414] Do not evaluate strings if assertion succeeds. --- libdevcore/Assertions.h | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h index 05e0b0e5c..0fb5837c7 100644 --- a/libdevcore/Assertions.h +++ b/libdevcore/Assertions.h @@ -73,27 +73,19 @@ inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char con /// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong."); /// Do NOT supply an exception object as the second parameter. #define assertThrow(_condition, _ExceptionType, _description) \ - ::dev::assertThrowAux<_ExceptionType>(!!(_condition), _description, __LINE__, __FILE__, ETH_FUNC) + do \ + { \ + if (!(_condition)) \ + ::boost::throw_exception( \ + _ExceptionType() << \ + ::dev::errinfo_comment(_description) << \ + ::boost::throw_function(ETH_FUNC) << \ + ::boost::throw_file(__FILE__) << \ + ::boost::throw_line(__LINE__) \ + ); \ + } \ + while (false) using errinfo_comment = boost::error_info; -template -inline void assertThrowAux( - bool _condition, - ::std::string const& _errorDescription, - unsigned _line, - char const* _file, - char const* _function -) -{ - if (!_condition) - ::boost::throw_exception( - _ExceptionType() << - ::dev::errinfo_comment(_errorDescription) << - ::boost::throw_function(_function) << - ::boost::throw_file(_file) << - ::boost::throw_line(_line) - ); -} - } From f1a4976ce65bd4730f72b0805635e818ad6334f7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 6 Jan 2017 16:16:11 +0100 Subject: [PATCH 043/414] Optimise simplification rules (sort into bins). --- libevmasm/ExpressionClasses.cpp | 338 +--------------------------- libevmasm/ExpressionClasses.h | 67 ------ libevmasm/SimplificationRules.cpp | 363 ++++++++++++++++++++++++++++++ libevmasm/SimplificationRules.h | 140 ++++++++++++ 4 files changed, 514 insertions(+), 394 deletions(-) create mode 100644 libevmasm/SimplificationRules.cpp create mode 100644 libevmasm/SimplificationRules.h diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index fccac1b0a..fc283b0bc 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -29,6 +29,7 @@ #include #include #include +#include using namespace std; using namespace dev; @@ -180,191 +181,6 @@ string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const return str.str(); } -class Rules: public boost::noncopyable -{ -public: - Rules(); - void resetMatchGroups() { m_matchGroups.clear(); } - vector>> const& rules() const { return m_rules; } - -private: - using Expression = ExpressionClasses::Expression; - map m_matchGroups; - vector>> m_rules; -}; - -template S divWorkaround(S const& _a, S const& _b) -{ - return (S)(bigint(_a) / bigint(_b)); -} - -template S modWorkaround(S const& _a, S const& _b) -{ - return (S)(bigint(_a) % bigint(_b)); -} - -Rules::Rules() -{ - // Multiple occurences of one of these inside one rule must match the same equivalence class. - // Constants. - Pattern A(Push); - Pattern B(Push); - Pattern C(Push); - // Anything. - Pattern X; - Pattern Y; - Pattern Z; - A.setMatchGroup(1, m_matchGroups); - B.setMatchGroup(2, m_matchGroups); - C.setMatchGroup(3, m_matchGroups); - X.setMatchGroup(4, m_matchGroups); - Y.setMatchGroup(5, m_matchGroups); - Z.setMatchGroup(6, m_matchGroups); - - m_rules = vector>>{ - // arithmetics on constants - {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, - {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, - {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, - {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }}, - {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }}, - {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }}, - {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }}, - {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, - {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, - {{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }}, - {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, - {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, - {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, - {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, - {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, - {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, - {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, - {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, - {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, - {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, - {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { - if (A.d() >= 31) - return B.d(); - unsigned testBit = unsigned(A.d()) * 8 + 7; - u256 mask = (u256(1) << testBit) - 1; - return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); - }}, - - // invariants involving known constants - {{Instruction::ADD, {X, 0}}, [=]{ return X; }}, - {{Instruction::SUB, {X, 0}}, [=]{ return X; }}, - {{Instruction::MUL, {X, 1}}, [=]{ return X; }}, - {{Instruction::DIV, {X, 1}}, [=]{ return X; }}, - {{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, - {{Instruction::OR, {X, 0}}, [=]{ return X; }}, - {{Instruction::XOR, {X, 0}}, [=]{ return X; }}, - {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, - {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, - {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } }, - // operations involving an expression and itself - {{Instruction::AND, {X, X}}, [=]{ return X; }}, - {{Instruction::OR, {X, X}}, [=]{ return X; }}, - {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, - {{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, - - {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, - {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }}, - {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }}, - {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }}, - {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }}, - {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }}, - }; - // Double negation of opcodes with binary result - for (auto const& op: vector{ - Instruction::EQ, - Instruction::LT, - Instruction::SLT, - Instruction::GT, - Instruction::SGT - }) - m_rules.push_back({ - {Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}}, - [=]() -> Pattern { return {op, {X, Y}}; } - }); - m_rules.push_back({ - {Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}}, - [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } - }); - m_rules.push_back({ - {Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}}, - [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; } - }); - // Associative operations - for (auto const& opFun: vector>>{ - {Instruction::ADD, plus()}, - {Instruction::MUL, multiplies()}, - {Instruction::AND, bit_and()}, - {Instruction::OR, bit_or()}, - {Instruction::XOR, bit_xor()} - }) - { - auto op = opFun.first; - auto fun = opFun.second; - // Moving constants to the outside, order matters here! - // we need actions that return expressions (or patterns?) here, and we need also reversed rules - // (X+A)+B -> X+(A+B) - m_rules += vector>>{{ - {op, {{op, {X, A}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } - }, { - // X+(Y+A) -> (X+Y)+A - {op, {{op, {X, A}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } - }, { - // For now, we still need explicit commutativity for the inner pattern - {op, {{op, {A, X}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } - }, { - {op, {{op, {A, X}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } - }}; - } - // move constants across subtractions - m_rules += vector>>{ - { - // X - A -> X + (-A) - {Instruction::SUB, {X, A}}, - [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } - }, { - // (X + A) - Y -> (X - Y) + A - {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } - }, { - // (A + X) - Y -> (X - Y) + A - {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } - }, { - // X - (Y + A) -> (X - Y) + (-A) - {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } - }, { - // X - (A + Y) -> (X - Y) + (-A) - {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } - } - }; -} - ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun) { static Rules rules; @@ -376,21 +192,17 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, ) return -1; - for (auto const& rule: rules.rules()) + if (auto match = rules.findFirstMatch(_expr, *this)) { - rules.resetMatchGroups(); - if (rule.first.matches(_expr, *this)) - { - // Debug info - //cout << "Simplifying " << *_expr.item << "("; - //for (Id arg: _expr.arguments) - // cout << fullDAGToString(arg) << ", "; - //cout << ")" << endl; - //cout << "with rule " << rule.first.toString() << endl; - //ExpressionTemplate t(rule.second()); - //cout << "to " << rule.second().toString() << endl; - return rebuildExpression(ExpressionTemplate(rule.second(), _expr.item->location())); - } + // Debug info + //cout << "Simplifying " << *_expr.item << "("; + //for (Id arg: _expr.arguments) + // cout << fullDAGToString(arg) << ", "; + //cout << ")" << endl; + //cout << "with rule " << match->first.toString() << endl; + //ExpressionTemplate t(match->second()); + //cout << "to " << match->second().toString() << endl; + return rebuildExpression(ExpressionTemplate(match->second(), _expr.item->location())); } if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item)) @@ -413,131 +225,3 @@ ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate co arguments.push_back(rebuildExpression(t)); return find(_template.item, arguments); } - - -Pattern::Pattern(Instruction _instruction, std::vector const& _arguments): - m_type(Operation), - m_instruction(_instruction), - m_arguments(_arguments) -{ -} - -void Pattern::setMatchGroup(unsigned _group, map& _matchGroups) -{ - m_matchGroup = _group; - m_matchGroups = &_matchGroups; -} - -bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const -{ - if (!matchesBaseItem(_expr.item)) - return false; - if (m_matchGroup) - { - if (!m_matchGroups->count(m_matchGroup)) - (*m_matchGroups)[m_matchGroup] = &_expr; - else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id) - return false; - } - assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, ""); - for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes)) - return false; - return true; -} - -AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const -{ - if (m_type == Operation) - return AssemblyItem(m_instruction, _location); - else - return AssemblyItem(m_type, data(), _location); -} - -string Pattern::toString() const -{ - stringstream s; - switch (m_type) - { - case Operation: - s << instructionInfo(m_instruction).name; - break; - case Push: - s << "PUSH " << hex << data(); - break; - case UndefinedItem: - s << "ANY"; - break; - default: - s << "t=" << dec << m_type << " d=" << hex << data(); - break; - } - if (!m_requireDataMatch) - s << " ~"; - if (m_matchGroup) - s << "[" << dec << m_matchGroup << "]"; - s << "("; - for (Pattern const& p: m_arguments) - s << p.toString() << ", "; - s << ")"; - return s.str(); -} - -bool Pattern::matchesBaseItem(AssemblyItem const* _item) const -{ - if (m_type == UndefinedItem) - return true; - if (!_item) - return false; - if (m_type != _item->type()) - return false; - else if (m_type == Operation) - return m_instruction == _item->instruction(); - else if (m_requireDataMatch) - return data() == _item->data(); - return true; -} - -Pattern::Expression const& Pattern::matchGroupValue() const -{ - assertThrow(m_matchGroup > 0, OptimizerException, ""); - assertThrow(!!m_matchGroups, OptimizerException, ""); - assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, ""); - return *(*m_matchGroups)[m_matchGroup]; -} - -u256 const& Pattern::data() const -{ - assertThrow(m_data, OptimizerException, ""); - return *m_data; -} - -ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location) -{ - if (_pattern.matchGroup()) - { - hasId = true; - id = _pattern.id(); - } - else - { - hasId = false; - item = _pattern.toAssemblyItem(_location); - } - for (auto const& arg: _pattern.arguments()) - arguments.push_back(ExpressionTemplate(arg, _location)); -} - -string ExpressionTemplate::toString() const -{ - stringstream s; - if (hasId) - s << id; - else - s << item; - s << "("; - for (auto const& arg: arguments) - s << arg.toString(); - s << ")"; - return s.str(); -} diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h index c91b227f4..5d53b2921 100644 --- a/libevmasm/ExpressionClasses.h +++ b/libevmasm/ExpressionClasses.h @@ -121,72 +121,5 @@ private: std::vector> m_spareAssemblyItems; }; -/** - * Pattern to match against an expression. - * Also stores matched expressions to retrieve them later, for constructing new expressions using - * ExpressionTemplate. - */ -class Pattern -{ -public: - using Expression = ExpressionClasses::Expression; - using Id = ExpressionClasses::Id; - - // Matches a specific constant value. - Pattern(unsigned _value): Pattern(u256(_value)) {} - // Matches a specific constant value. - Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(std::make_shared(_value)) {} - // Matches a specific assembly item type or anything if not given. - Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} - // Matches a given instruction with given arguments - Pattern(Instruction _instruction, std::vector const& _arguments = {}); - /// Sets this pattern to be part of the match group with the identifier @a _group. - /// Inside one rule, all patterns in the same match group have to match expressions from the - /// same expression equivalence class. - void setMatchGroup(unsigned _group, std::map& _matchGroups); - unsigned matchGroup() const { return m_matchGroup; } - bool matches(Expression const& _expr, ExpressionClasses const& _classes) const; - - AssemblyItem toAssemblyItem(SourceLocation const& _location) const; - std::vector arguments() const { return m_arguments; } - - /// @returns the id of the matched expression if this pattern is part of a match group. - Id id() const { return matchGroupValue().id; } - /// @returns the data of the matched expression if this pattern is part of a match group. - u256 const& d() const { return matchGroupValue().item->data(); } - - std::string toString() const; - -private: - bool matchesBaseItem(AssemblyItem const* _item) const; - Expression const& matchGroupValue() const; - u256 const& data() const; - - AssemblyItemType m_type; - bool m_requireDataMatch = false; - Instruction m_instruction; ///< Only valid if m_type is Operation - std::shared_ptr m_data; ///< Only valid if m_type is not Operation - std::vector m_arguments; - unsigned m_matchGroup = 0; - std::map* m_matchGroups = nullptr; -}; - -/** - * Template for a new expression that can be built from matched patterns. - */ -struct ExpressionTemplate -{ - using Expression = ExpressionClasses::Expression; - using Id = ExpressionClasses::Id; - explicit ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location); - std::string toString() const; - bool hasId = false; - /// Id of the matched expression, if available. - Id id = Id(-1); - // Otherwise, assembly item. - AssemblyItem item = UndefinedItem; - std::vector arguments; -}; - } } diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp new file mode 100644 index 000000000..bb9504000 --- /dev/null +++ b/libevmasm/SimplificationRules.cpp @@ -0,0 +1,363 @@ +/* + 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 . +*/ +/** + * @file ExpressionClasses.cpp + * @author Christian + * @date 2015 + * Container for equivalence classes of expressions for use in common subexpression elimination. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + + +pair > const* Rules::findFirstMatch( + Expression const& _expr, + ExpressionClasses const& _classes +) +{ + resetMatchGroups(); + + assertThrow(_expr.item, OptimizerException, ""); + for (auto const& rule: m_rules[byte(_expr.item->instruction())]) + { + if (rule.first.matches(_expr, _classes)) + return &rule; + } + return nullptr; +} + +void Rules::addRules(std::vector > > const& _rules) +{ + for (auto const& r: _rules) + addRule(r); +} + +void Rules::addRule(std::pair > const& _rule) +{ + m_rules[byte(_rule.first.instruction())].push_back(_rule); +} + +template S divWorkaround(S const& _a, S const& _b) +{ + return (S)(bigint(_a) / bigint(_b)); +} + +template S modWorkaround(S const& _a, S const& _b) +{ + return (S)(bigint(_a) % bigint(_b)); +} + +Rules::Rules() +{ + // Multiple occurences of one of these inside one rule must match the same equivalence class. + // Constants. + Pattern A(Push); + Pattern B(Push); + Pattern C(Push); + // Anything. + Pattern X; + Pattern Y; + Pattern Z; + A.setMatchGroup(1, m_matchGroups); + B.setMatchGroup(2, m_matchGroups); + C.setMatchGroup(3, m_matchGroups); + X.setMatchGroup(4, m_matchGroups); + Y.setMatchGroup(5, m_matchGroups); + Z.setMatchGroup(6, m_matchGroups); + + addRules(vector>>{ + // arithmetics on constants + {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, + {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, + {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, + {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }}, + {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }}, + {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }}, + {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }}, + {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, + {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, + {{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }}, + {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, + {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, + {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, + {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, + {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, + {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, + {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, + {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, + {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, + {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, + {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { + if (A.d() >= 31) + return B.d(); + unsigned testBit = unsigned(A.d()) * 8 + 7; + u256 mask = (u256(1) << testBit) - 1; + return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); + }}, + + // invariants involving known constants + {{Instruction::ADD, {X, 0}}, [=]{ return X; }}, + {{Instruction::SUB, {X, 0}}, [=]{ return X; }}, + {{Instruction::MUL, {X, 1}}, [=]{ return X; }}, + {{Instruction::DIV, {X, 1}}, [=]{ return X; }}, + {{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, + {{Instruction::OR, {X, 0}}, [=]{ return X; }}, + {{Instruction::XOR, {X, 0}}, [=]{ return X; }}, + {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, + {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, + {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, + {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } }, + // operations involving an expression and itself + {{Instruction::AND, {X, X}}, [=]{ return X; }}, + {{Instruction::OR, {X, X}}, [=]{ return X; }}, + {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, + {{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, + + {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, + {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }}, + {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }}, + {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }}, + {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }}, + {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }}, + }); + // Double negation of opcodes with binary result + for (auto const& op: vector{ + Instruction::EQ, + Instruction::LT, + Instruction::SLT, + Instruction::GT, + Instruction::SGT + }) + addRule({ + {Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}}, + [=]() -> Pattern { return {op, {X, Y}}; } + }); + addRule({ + {Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}}, + [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } + }); + addRule({ + {Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}}, + [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; } + }); + // Associative operations + for (auto const& opFun: vector>>{ + {Instruction::ADD, plus()}, + {Instruction::MUL, multiplies()}, + {Instruction::AND, bit_and()}, + {Instruction::OR, bit_or()}, + {Instruction::XOR, bit_xor()} + }) + { + auto op = opFun.first; + auto fun = opFun.second; + // Moving constants to the outside, order matters here! + // we need actions that return expressions (or patterns?) here, and we need also reversed rules + // (X+A)+B -> X+(A+B) + addRules(vector>>{{ + {op, {{op, {X, A}}, B}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + }, { + // X+(Y+A) -> (X+Y)+A + {op, {{op, {X, A}}, Y}}, + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + }, { + // For now, we still need explicit commutativity for the inner pattern + {op, {{op, {A, X}}, B}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + }, { + {op, {{op, {A, X}}, Y}}, + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + }}); + } + // move constants across subtractions + addRules(vector>>{ + { + // X - A -> X + (-A) + {Instruction::SUB, {X, A}}, + [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } + }, { + // (X + A) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // (A + X) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // X - (Y + A) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + }, { + // X - (A + Y) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + } + }); +} + +Pattern::Pattern(Instruction _instruction, std::vector const& _arguments): + m_type(Operation), + m_instruction(_instruction), + m_arguments(_arguments) +{ +} + +void Pattern::setMatchGroup(unsigned _group, map& _matchGroups) +{ + m_matchGroup = _group; + m_matchGroups = &_matchGroups; +} + +bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const +{ + if (!matchesBaseItem(_expr.item)) + return false; + if (m_matchGroup) + { + if (!m_matchGroups->count(m_matchGroup)) + (*m_matchGroups)[m_matchGroup] = &_expr; + else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id) + return false; + } + assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, ""); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes)) + return false; + return true; +} + +AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const +{ + if (m_type == Operation) + return AssemblyItem(m_instruction, _location); + else + return AssemblyItem(m_type, data(), _location); +} + +string Pattern::toString() const +{ + stringstream s; + switch (m_type) + { + case Operation: + s << instructionInfo(m_instruction).name; + break; + case Push: + s << "PUSH " << hex << data(); + break; + case UndefinedItem: + s << "ANY"; + break; + default: + s << "t=" << dec << m_type << " d=" << hex << data(); + break; + } + if (!m_requireDataMatch) + s << " ~"; + if (m_matchGroup) + s << "[" << dec << m_matchGroup << "]"; + s << "("; + for (Pattern const& p: m_arguments) + s << p.toString() << ", "; + s << ")"; + return s.str(); +} + +bool Pattern::matchesBaseItem(AssemblyItem const* _item) const +{ + if (m_type == UndefinedItem) + return true; + if (!_item) + return false; + if (m_type != _item->type()) + return false; + else if (m_type == Operation) + return m_instruction == _item->instruction(); + else if (m_requireDataMatch) + return data() == _item->data(); + return true; +} + +Pattern::Expression const& Pattern::matchGroupValue() const +{ + assertThrow(m_matchGroup > 0, OptimizerException, ""); + assertThrow(!!m_matchGroups, OptimizerException, ""); + assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, ""); + return *(*m_matchGroups)[m_matchGroup]; +} + +u256 const& Pattern::data() const +{ + assertThrow(m_data, OptimizerException, ""); + return *m_data; +} + +ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location) +{ + if (_pattern.matchGroup()) + { + hasId = true; + id = _pattern.id(); + } + else + { + hasId = false; + item = _pattern.toAssemblyItem(_location); + } + for (auto const& arg: _pattern.arguments()) + arguments.push_back(ExpressionTemplate(arg, _location)); +} + +string ExpressionTemplate::toString() const +{ + stringstream s; + if (hasId) + s << id; + else + s << item; + s << "("; + for (auto const& arg: arguments) + s << arg.toString(); + s << ")"; + return s.str(); +} diff --git a/libevmasm/SimplificationRules.h b/libevmasm/SimplificationRules.h new file mode 100644 index 000000000..a4da5849c --- /dev/null +++ b/libevmasm/SimplificationRules.h @@ -0,0 +1,140 @@ +/* + 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 . +*/ +/** + * @file SimplificationRules + * @author Christian + * @date 2017 + * Module for applying replacement rules against Expressions. + */ + +#pragma once + +#include + +#include +#include + +namespace dev +{ +namespace eth +{ + +class Pattern; + +/** + * Container for all simplification rules. + */ +class Rules: public boost::noncopyable +{ +public: + using Expression = ExpressionClasses::Expression; + + Rules(); + + /// @returns a pointer to the first matching pattern and sets the match + /// groups accordingly. + std::pair> const* findFirstMatch( + Expression const& _expr, + ExpressionClasses const& _classes + ); + +private: + void addRules(std::vector>> const& _rules); + void addRule(std::pair> const& _rule); + + void resetMatchGroups() { m_matchGroups.clear(); } + + std::map m_matchGroups; + std::vector>> m_rules[256]; +}; + +/** + * Pattern to match against an expression. + * Also stores matched expressions to retrieve them later, for constructing new expressions using + * ExpressionTemplate. + */ +class Pattern +{ +public: + using Expression = ExpressionClasses::Expression; + using Id = ExpressionClasses::Id; + + // Matches a specific constant value. + Pattern(unsigned _value): Pattern(u256(_value)) {} + // Matches a specific constant value. + Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(std::make_shared(_value)) {} + // Matches a specific assembly item type or anything if not given. + Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} + // Matches a given instruction with given arguments + Pattern(Instruction _instruction, std::vector const& _arguments = {}); + /// Sets this pattern to be part of the match group with the identifier @a _group. + /// Inside one rule, all patterns in the same match group have to match expressions from the + /// same expression equivalence class. + void setMatchGroup(unsigned _group, std::map& _matchGroups); + unsigned matchGroup() const { return m_matchGroup; } + bool matches(Expression const& _expr, ExpressionClasses const& _classes) const; + + AssemblyItem toAssemblyItem(SourceLocation const& _location) const; + std::vector arguments() const { return m_arguments; } + + /// @returns the id of the matched expression if this pattern is part of a match group. + Id id() const { return matchGroupValue().id; } + /// @returns the data of the matched expression if this pattern is part of a match group. + u256 const& d() const { return matchGroupValue().item->data(); } + + std::string toString() const; + + AssemblyItemType type() const { return m_type; } + Instruction instruction() const + { + assertThrow(type() == Operation, OptimizerException, ""); + return m_instruction; + } + +private: + bool matchesBaseItem(AssemblyItem const* _item) const; + Expression const& matchGroupValue() const; + u256 const& data() const; + + AssemblyItemType m_type; + bool m_requireDataMatch = false; + Instruction m_instruction; ///< Only valid if m_type is Operation + std::shared_ptr m_data; ///< Only valid if m_type is not Operation + std::vector m_arguments; + unsigned m_matchGroup = 0; + std::map* m_matchGroups = nullptr; +}; + +/** + * Template for a new expression that can be built from matched patterns. + */ +struct ExpressionTemplate +{ + using Expression = ExpressionClasses::Expression; + using Id = ExpressionClasses::Id; + explicit ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location); + std::string toString() const; + bool hasId = false; + /// Id of the matched expression, if available. + Id id = Id(-1); + // Otherwise, assembly item. + AssemblyItem item = UndefinedItem; + std::vector arguments; +}; + +} +} From da5e171f3b7a8d16fb01f4366f61a301567f1ae2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 6 Jan 2017 17:05:27 +0100 Subject: [PATCH 044/414] Optimize constant optimization. --- libevmasm/ConstantOptimiser.cpp | 80 ++++++++++++++++++++------------- libevmasm/ConstantOptimiser.h | 19 ++++---- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index f4a50c2d6..601ce812c 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -38,6 +38,7 @@ unsigned ConstantOptimisationMethod::optimiseConstants( for (AssemblyItem const& item: _items) if (item.type() == Push) pushes[item]++; + map pendingReplacements; for (auto it: pushes) { AssemblyItem const& item = it.first; @@ -53,17 +54,22 @@ unsigned ConstantOptimisationMethod::optimiseConstants( bigint copyGas = copy.gasNeeded(); ComputeMethod compute(params, item.data()); bigint computeGas = compute.gasNeeded(); + AssemblyItems replacement; if (copyGas < literalGas && copyGas < computeGas) { - copy.execute(_assembly, _items); + replacement = copy.execute(_assembly); optimisations++; } else if (computeGas < literalGas && computeGas < copyGas) { - compute.execute(_assembly, _items); + replacement = compute.execute(_assembly); optimisations++; } + if (!replacement.empty()) + pendingReplacements[item.data()] = replacement; } + if (!pendingReplacements.empty()) + replaceConstants(_items, pendingReplacements); return optimisations; } @@ -101,18 +107,24 @@ size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items) void ConstantOptimisationMethod::replaceConstants( AssemblyItems& _items, - AssemblyItems const& _replacement -) const + map const& _replacements +) { - assertThrow(_items.size() > 0, OptimizerException, ""); - for (size_t i = 0; i < _items.size(); ++i) + AssemblyItems replaced; + for (AssemblyItem const& item: _items) { - if (_items.at(i) != AssemblyItem(m_value)) - continue; - _items[i] = _replacement[0]; - _items.insert(_items.begin() + i + 1, _replacement.begin() + 1, _replacement.end()); - i += _replacement.size() - 1; + if (item.type() == Push) + { + auto it = _replacements.find(item.data()); + if (it != _replacements.end()) + { + replaced += it->second; + continue; + } + } + replaced.push_back(item); } + _items = std::move(replaced); } bigint LiteralMethod::gasNeeded() @@ -128,7 +140,31 @@ bigint LiteralMethod::gasNeeded() CodeCopyMethod::CodeCopyMethod(Params const& _params, u256 const& _value): ConstantOptimisationMethod(_params, _value) { - m_copyRoutine = AssemblyItems{ +} + +bigint CodeCopyMethod::gasNeeded() +{ + return combineGas( + // Run gas: we ignore memory increase costs + simpleRunGas(copyRoutine()) + GasCosts::copyGas, + // Data gas for copy routines: Some bytes are zero, but we ignore them. + bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas), + // Data gas for data itself + dataGas(toBigEndian(m_value)) + ); +} + +AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) +{ + bytes data = toBigEndian(m_value); + AssemblyItems actualCopyRoutine = copyRoutine(); + actualCopyRoutine[4] = _assembly.newData(data); + return actualCopyRoutine; +} + +AssemblyItems const& CodeCopyMethod::copyRoutine() const +{ + AssemblyItems static copyRoutine{ u256(0), Instruction::DUP1, Instruction::MLOAD, // back up memory @@ -141,25 +177,7 @@ CodeCopyMethod::CodeCopyMethod(Params const& _params, u256 const& _value): Instruction::SWAP2, Instruction::MSTORE }; -} - -bigint CodeCopyMethod::gasNeeded() -{ - return combineGas( - // Run gas: we ignore memory increase costs - simpleRunGas(m_copyRoutine) + GasCosts::copyGas, - // Data gas for copy routines: Some bytes are zero, but we ignore them. - bytesRequired(m_copyRoutine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas), - // Data gas for data itself - dataGas(toBigEndian(m_value)) - ); -} - -void CodeCopyMethod::execute(Assembly& _assembly, AssemblyItems& _items) -{ - bytes data = toBigEndian(m_value); - m_copyRoutine[4] = _assembly.newData(data); - replaceConstants(_items, m_copyRoutine); + return copyRoutine; } AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index b35b2a69d..dfa2fbf8a 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -60,7 +60,10 @@ public: explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value): m_params(_params), m_value(_value) {} virtual bigint gasNeeded() = 0; - virtual void execute(Assembly& _assembly, AssemblyItems& _items) = 0; + /// Executes the method, potentially appending to the assembly and returns a vector of + /// assembly items the constant should be relpaced with in one sweep. + /// If the vector is empty, the constants will not be deleted. + virtual AssemblyItems execute(Assembly& _assembly) = 0; protected: size_t dataSize() const { return std::max(1, dev::bytesRequired(m_value)); } @@ -83,8 +86,8 @@ protected: return m_params.runs * _runGas + m_params.multiplicity * _repeatedDataGas + _uniqueDataGas; } - /// Replaces the constant by the code given in @a _replacement. - void replaceConstants(AssemblyItems& _items, AssemblyItems const& _replacement) const; + /// Replaces all constants i by the code given in @a _replacement[i]. + static void replaceConstants(AssemblyItems& _items, std::map const& _replacement); Params m_params; u256 const& m_value; @@ -100,7 +103,7 @@ public: explicit LiteralMethod(Params const& _params, u256 const& _value): ConstantOptimisationMethod(_params, _value) {} virtual bigint gasNeeded() override; - virtual void execute(Assembly&, AssemblyItems&) override {} + virtual AssemblyItems execute(Assembly&) override { return AssemblyItems{}; } }; /** @@ -111,10 +114,10 @@ class CodeCopyMethod: public ConstantOptimisationMethod public: explicit CodeCopyMethod(Params const& _params, u256 const& _value); virtual bigint gasNeeded() override; - virtual void execute(Assembly& _assembly, AssemblyItems& _items) override; + virtual AssemblyItems execute(Assembly& _assembly) override; protected: - AssemblyItems m_copyRoutine; + AssemblyItems const& copyRoutine() const; }; /** @@ -130,9 +133,9 @@ public: } virtual bigint gasNeeded() override { return gasNeeded(m_routine); } - virtual void execute(Assembly&, AssemblyItems& _items) override + virtual AssemblyItems execute(Assembly&) override { - replaceConstants(_items, m_routine); + return m_routine; } protected: From 7127f2fdfdc1bfb8efd9a1212725e36c3c42ad9a Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 10 Jan 2017 16:35:10 +0100 Subject: [PATCH 045/414] Fix debug output. --- libevmasm/SimplificationRules.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index bb9504000..0e95b4bb8 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -283,13 +283,19 @@ string Pattern::toString() const s << instructionInfo(m_instruction).name; break; case Push: - s << "PUSH " << hex << data(); + if (m_data) + s << "PUSH " << hex << data(); + else + s << "PUSH "; break; case UndefinedItem: s << "ANY"; break; default: - s << "t=" << dec << m_type << " d=" << hex << data(); + if (m_data) + s << "t=" << dec << m_type << " d=" << hex << data(); + else + s << "t=" << dec << m_type << " d: nullptr"; break; } if (!m_requireDataMatch) From 58509ca3aa1f1c649303bca4e705c1c96e832c08 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 10 Jan 2017 16:35:18 +0100 Subject: [PATCH 046/414] Fix match groups. --- libevmasm/SimplificationRules.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index 0e95b4bb8..2976d95f1 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -48,6 +48,7 @@ pair > const* Rules::findFirstMatch( { if (rule.first.matches(_expr, _classes)) return &rule; + resetMatchGroups(); } return nullptr; } From d34b561f682336bef0258537f7fdf87b16f4a741 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 10 Jan 2017 17:38:42 +0100 Subject: [PATCH 047/414] Windows build fix. --- libevmasm/CommonSubexpressionEliminator.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libevmasm/CommonSubexpressionEliminator.cpp b/libevmasm/CommonSubexpressionEliminator.cpp index 6294e579d..fd4fffa6e 100644 --- a/libevmasm/CommonSubexpressionEliminator.cpp +++ b/libevmasm/CommonSubexpressionEliminator.cpp @@ -303,7 +303,9 @@ void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced) for (auto it: m_classPositions) for (auto p: it.second) if (p > m_stackHeight) + { assertThrow(false, OptimizerException, ""); + } // do some cleanup removeStackTopIfPossible(); From a32def5c42af6785ca83842fb583f9bf9d427fd4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 11 Jan 2017 10:25:48 +0100 Subject: [PATCH 048/414] Changelog entry. --- Changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 7676b5beb..af4845170 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,9 +1,10 @@ ### 0.4.8 (unreleased) Features: + * Optimiser: Performance improments. * Output: Print assembly in new standardized Solidity assembly format. -BugFixes: +Bugfixes: * Remappings: Prefer longer context over longer prefix. * Type checker, code generator: enable access to events of base contracts' names. * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. From 3cfa851f6f4175ef17c4fc1079cda67b516a2d72 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 12 Jan 2017 17:52:23 +0100 Subject: [PATCH 049/414] Minor changes from review. --- Changelog.md | 2 +- cmake/EthCompilerSettings.cmake | 2 +- libevmasm/AssemblyItem.h | 1 - libevmasm/ConstantOptimiser.cpp | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index af4845170..3e999d13b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,7 +1,7 @@ ### 0.4.8 (unreleased) Features: - * Optimiser: Performance improments. + * Optimiser: Performance improvements. * Output: Print assembly in new standardized Solidity assembly format. Bugfixes: diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 65742dfb1..c734423b7 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -71,7 +71,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-fPIC) # Configuration-specific compiler settings. - set(CMAKE_CXX_FLAGS_DEBUG " -g -DETH_DEBUG") + set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DETH_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 7fcaa1451..002b3c874 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -86,7 +86,6 @@ public: AssemblyItemType type() const { return m_type; } u256 const& data() const { assertThrow(m_type != Operation, Exception, ""); return *m_data; } - //void setType(AssemblyItemType const _type) { m_type = _type; } void setData(u256 const& _data) { assertThrow(m_type != Operation, Exception, ""); m_data = std::make_shared(_data); } /// @returns the instruction of this item (only valid if type() == Operation) diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index 601ce812c..86244e172 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -60,7 +60,7 @@ unsigned ConstantOptimisationMethod::optimiseConstants( replacement = copy.execute(_assembly); optimisations++; } - else if (computeGas < literalGas && computeGas < copyGas) + else if (computeGas < literalGas && computeGas <= copyGas) { replacement = compute.execute(_assembly); optimisations++; From e22672b7c739dde9f37a919e63245abda4b1fc89 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 13 Jan 2017 12:18:22 +0100 Subject: [PATCH 050/414] Update Changelog.md --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index bef98f15d..b8effc19f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,4 @@ -### 0.4.8 (unreleased) +### 0.4.8 (2017-01-13) Features: * Optimiser: Performance improvements. From 20c62a132da4e32efbde7df3bb1dbdc4f448243f Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 13 Jan 2017 13:19:14 +0100 Subject: [PATCH 051/414] Make enum Tier into an enum class --- libevmasm/GasMeter.cpp | 16 +-- libevmasm/Instruction.cpp | 262 +++++++++++++++++++------------------- libevmasm/Instruction.h | 4 +- 3 files changed, 141 insertions(+), 141 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 21db35655..bc7d4e55e 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -223,14 +223,14 @@ unsigned GasMeter::runGas(Instruction _instruction) switch (instructionInfo(_instruction).gasPriceTier) { - case 0: return GasCosts::tier0Gas; - case 1: return GasCosts::tier1Gas; - case 2: return GasCosts::tier2Gas; - case 3: return GasCosts::tier3Gas; - case 4: return GasCosts::tier4Gas; - case 5: return GasCosts::tier5Gas; - case 6: return GasCosts::tier6Gas; - case 7: return GasCosts::tier7Gas; + case Tier::ZeroTier: return GasCosts::tier0Gas; + case Tier::BaseTier: return GasCosts::tier1Gas; + case Tier::VeryLowTier: return GasCosts::tier2Gas; + case Tier::LowTier: return GasCosts::tier3Gas; + case Tier::MidTier: return GasCosts::tier4Gas; + case Tier::HighTier: return GasCosts::tier5Gas; + case Tier::ExtTier: return GasCosts::tier6Gas; + case Tier::SpecialTier: return GasCosts::tier7Gas; default: break; } assertThrow(false, OptimizerException, "Invalid gas tier."); diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 5244a91f7..43cbbd380 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -164,136 +164,136 @@ const std::map dev::solidity::c_instructions = static const std::map c_instructionInfo = { // Add, Args, Ret, SideEffects, GasPriceTier - { Instruction::STOP, { "STOP", 0, 0, 0, true, ZeroTier } }, - { Instruction::ADD, { "ADD", 0, 2, 1, false, VeryLowTier } }, - { Instruction::SUB, { "SUB", 0, 2, 1, false, VeryLowTier } }, - { Instruction::MUL, { "MUL", 0, 2, 1, false, LowTier } }, - { Instruction::DIV, { "DIV", 0, 2, 1, false, LowTier } }, - { Instruction::SDIV, { "SDIV", 0, 2, 1, false, LowTier } }, - { Instruction::MOD, { "MOD", 0, 2, 1, false, LowTier } }, - { Instruction::SMOD, { "SMOD", 0, 2, 1, false, LowTier } }, - { Instruction::EXP, { "EXP", 0, 2, 1, false, SpecialTier } }, - { Instruction::NOT, { "NOT", 0, 1, 1, false, VeryLowTier } }, - { Instruction::LT, { "LT", 0, 2, 1, false, VeryLowTier } }, - { Instruction::GT, { "GT", 0, 2, 1, false, VeryLowTier } }, - { Instruction::SLT, { "SLT", 0, 2, 1, false, VeryLowTier } }, - { Instruction::SGT, { "SGT", 0, 2, 1, false, VeryLowTier } }, - { Instruction::EQ, { "EQ", 0, 2, 1, false, VeryLowTier } }, - { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, VeryLowTier } }, - { Instruction::AND, { "AND", 0, 2, 1, false, VeryLowTier } }, - { Instruction::OR, { "OR", 0, 2, 1, false, VeryLowTier } }, - { Instruction::XOR, { "XOR", 0, 2, 1, false, VeryLowTier } }, - { Instruction::BYTE, { "BYTE", 0, 2, 1, false, VeryLowTier } }, - { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, MidTier } }, - { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, MidTier } }, - { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, LowTier } }, - { Instruction::SHA3, { "SHA3", 0, 2, 1, false, SpecialTier } }, - { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, BaseTier } }, - { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, ExtTier } }, - { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, BaseTier } }, - { Instruction::CALLER, { "CALLER", 0, 0, 1, false, BaseTier } }, - { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, BaseTier } }, - { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, VeryLowTier } }, - { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, BaseTier } }, - { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, VeryLowTier } }, - { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, BaseTier } }, - { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, VeryLowTier } }, - { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, BaseTier } }, - { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, ExtTier } }, - { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, ExtTier } }, - { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, ExtTier } }, - { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, BaseTier } }, - { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, BaseTier } }, - { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, BaseTier } }, - { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, BaseTier } }, - { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, BaseTier } }, - { Instruction::POP, { "POP", 0, 1, 0, false, BaseTier } }, - { Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, VeryLowTier } }, - { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, VeryLowTier } }, - { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, VeryLowTier } }, - { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, SpecialTier } }, - { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, SpecialTier } }, - { Instruction::JUMP, { "JUMP", 0, 1, 0, true, MidTier } }, - { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, HighTier } }, - { Instruction::PC, { "PC", 0, 0, 1, false, BaseTier } }, - { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, BaseTier } }, - { Instruction::GAS, { "GAS", 0, 0, 1, false, BaseTier } }, - { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, SpecialTier } }, - { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, VeryLowTier } }, - { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, VeryLowTier } }, - { Instruction::DUP1, { "DUP1", 0, 1, 2, false, VeryLowTier } }, - { Instruction::DUP2, { "DUP2", 0, 2, 3, false, VeryLowTier } }, - { Instruction::DUP3, { "DUP3", 0, 3, 4, false, VeryLowTier } }, - { Instruction::DUP4, { "DUP4", 0, 4, 5, false, VeryLowTier } }, - { Instruction::DUP5, { "DUP5", 0, 5, 6, false, VeryLowTier } }, - { Instruction::DUP6, { "DUP6", 0, 6, 7, false, VeryLowTier } }, - { Instruction::DUP7, { "DUP7", 0, 7, 8, false, VeryLowTier } }, - { Instruction::DUP8, { "DUP8", 0, 8, 9, false, VeryLowTier } }, - { Instruction::DUP9, { "DUP9", 0, 9, 10, false, VeryLowTier } }, - { Instruction::DUP10, { "DUP10", 0, 10, 11, false, VeryLowTier } }, - { Instruction::DUP11, { "DUP11", 0, 11, 12, false, VeryLowTier } }, - { Instruction::DUP12, { "DUP12", 0, 12, 13, false, VeryLowTier } }, - { Instruction::DUP13, { "DUP13", 0, 13, 14, false, VeryLowTier } }, - { Instruction::DUP14, { "DUP14", 0, 14, 15, false, VeryLowTier } }, - { Instruction::DUP15, { "DUP15", 0, 15, 16, false, VeryLowTier } }, - { Instruction::DUP16, { "DUP16", 0, 16, 17, false, VeryLowTier } }, - { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, VeryLowTier } }, - { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, VeryLowTier } }, - { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, VeryLowTier } }, - { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, VeryLowTier } }, - { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, VeryLowTier } }, - { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, VeryLowTier } }, - { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, VeryLowTier } }, - { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, VeryLowTier } }, - { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, VeryLowTier } }, - { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, VeryLowTier } }, - { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, VeryLowTier } }, - { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, VeryLowTier } }, - { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, VeryLowTier } }, - { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, VeryLowTier } }, - { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, VeryLowTier } }, - { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, VeryLowTier } }, - { Instruction::LOG0, { "LOG0", 0, 2, 0, true, SpecialTier } }, - { Instruction::LOG1, { "LOG1", 0, 3, 0, true, SpecialTier } }, - { Instruction::LOG2, { "LOG2", 0, 4, 0, true, SpecialTier } }, - { Instruction::LOG3, { "LOG3", 0, 5, 0, true, SpecialTier } }, - { Instruction::LOG4, { "LOG4", 0, 6, 0, true, SpecialTier } }, - { Instruction::CREATE, { "CREATE", 0, 3, 1, true, SpecialTier } }, - { Instruction::CALL, { "CALL", 0, 7, 1, true, SpecialTier } }, - { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, SpecialTier } }, - { Instruction::RETURN, { "RETURN", 0, 2, 0, true, ZeroTier } }, - { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, SpecialTier } }, - { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, ZeroTier } } + { Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::ZeroTier } }, + { Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::SUB, { "SUB", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::MUL, { "MUL", 0, 2, 1, false, Tier::LowTier } }, + { Instruction::DIV, { "DIV", 0, 2, 1, false, Tier::LowTier } }, + { Instruction::SDIV, { "SDIV", 0, 2, 1, false, Tier::LowTier } }, + { Instruction::MOD, { "MOD", 0, 2, 1, false, Tier::LowTier } }, + { Instruction::SMOD, { "SMOD", 0, 2, 1, false, Tier::LowTier } }, + { Instruction::EXP, { "EXP", 0, 2, 1, false, Tier::SpecialTier } }, + { Instruction::NOT, { "NOT", 0, 1, 1, false, Tier::VeryLowTier } }, + { Instruction::LT, { "LT", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::GT, { "GT", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::SLT, { "SLT", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::SGT, { "SGT", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::EQ, { "EQ", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, Tier::VeryLowTier } }, + { Instruction::AND, { "AND", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::OR, { "OR", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::XOR, { "XOR", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::BYTE, { "BYTE", 0, 2, 1, false, Tier::VeryLowTier } }, + { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::MidTier } }, + { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::MidTier } }, + { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::LowTier } }, + { Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::SpecialTier } }, + { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::ExtTier } }, + { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, Tier::VeryLowTier } }, + { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, Tier::VeryLowTier } }, + { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLowTier } }, + { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::ExtTier } }, + { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtTier } }, + { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::ExtTier } }, + { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::POP, { "POP", 0, 1, 0, false, Tier::BaseTier } }, + { Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, Tier::VeryLowTier } }, + { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLowTier } }, + { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, Tier::VeryLowTier } }, + { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, Tier::SpecialTier } }, + { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, Tier::SpecialTier } }, + { Instruction::JUMP, { "JUMP", 0, 1, 0, true, Tier::MidTier } }, + { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, Tier::HighTier } }, + { Instruction::PC, { "PC", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::GAS, { "GAS", 0, 0, 1, false, Tier::BaseTier } }, + { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, Tier::SpecialTier } }, + { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, Tier::VeryLowTier } }, + { Instruction::DUP1, { "DUP1", 0, 1, 2, false, Tier::VeryLowTier } }, + { Instruction::DUP2, { "DUP2", 0, 2, 3, false, Tier::VeryLowTier } }, + { Instruction::DUP3, { "DUP3", 0, 3, 4, false, Tier::VeryLowTier } }, + { Instruction::DUP4, { "DUP4", 0, 4, 5, false, Tier::VeryLowTier } }, + { Instruction::DUP5, { "DUP5", 0, 5, 6, false, Tier::VeryLowTier } }, + { Instruction::DUP6, { "DUP6", 0, 6, 7, false, Tier::VeryLowTier } }, + { Instruction::DUP7, { "DUP7", 0, 7, 8, false, Tier::VeryLowTier } }, + { Instruction::DUP8, { "DUP8", 0, 8, 9, false, Tier::VeryLowTier } }, + { Instruction::DUP9, { "DUP9", 0, 9, 10, false, Tier::VeryLowTier } }, + { Instruction::DUP10, { "DUP10", 0, 10, 11, false, Tier::VeryLowTier } }, + { Instruction::DUP11, { "DUP11", 0, 11, 12, false, Tier::VeryLowTier } }, + { Instruction::DUP12, { "DUP12", 0, 12, 13, false, Tier::VeryLowTier } }, + { Instruction::DUP13, { "DUP13", 0, 13, 14, false, Tier::VeryLowTier } }, + { Instruction::DUP14, { "DUP14", 0, 14, 15, false, Tier::VeryLowTier } }, + { Instruction::DUP15, { "DUP15", 0, 15, 16, false, Tier::VeryLowTier } }, + { Instruction::DUP16, { "DUP16", 0, 16, 17, false, Tier::VeryLowTier } }, + { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, Tier::VeryLowTier } }, + { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, Tier::VeryLowTier } }, + { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, Tier::VeryLowTier } }, + { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, Tier::VeryLowTier } }, + { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, Tier::VeryLowTier } }, + { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, Tier::VeryLowTier } }, + { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, Tier::VeryLowTier } }, + { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, Tier::VeryLowTier } }, + { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, Tier::VeryLowTier } }, + { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, Tier::VeryLowTier } }, + { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, Tier::VeryLowTier } }, + { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, Tier::VeryLowTier } }, + { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, Tier::VeryLowTier } }, + { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, Tier::VeryLowTier } }, + { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, Tier::VeryLowTier } }, + { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, Tier::VeryLowTier } }, + { Instruction::LOG0, { "LOG0", 0, 2, 0, true, Tier::SpecialTier } }, + { Instruction::LOG1, { "LOG1", 0, 3, 0, true, Tier::SpecialTier } }, + { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::SpecialTier } }, + { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::SpecialTier } }, + { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::SpecialTier } }, + { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::SpecialTier } }, + { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::SpecialTier } }, + { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::SpecialTier } }, + { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::ZeroTier } }, + { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, Tier::SpecialTier } }, + { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, Tier::ZeroTier } } }; void dev::solidity::eachInstruction( @@ -343,7 +343,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst) } catch (...) { - return InstructionInfo({"", 0, 0, 0, false, InvalidTier}); + return InstructionInfo({"", 0, 0, 0, false, Tier::InvalidTier}); } } diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index c7fad1c5a..4ebaeb987 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -225,7 +225,7 @@ inline Instruction logInstruction(unsigned _number) return Instruction(unsigned(Instruction::LOG0) + _number); } -enum Tier +enum class Tier : int { ZeroTier = 0, // 0, Zero BaseTier, // 2, Quick @@ -246,7 +246,7 @@ struct InstructionInfo int args; ///< Number of items required on the stack for this instruction (and, for the purposes of ret, the number taken from the stack). int ret; ///< Number of items placed (back) on the stack by this instruction, assuming args items were removed. bool sideEffects; ///< false if the only effect on the execution environment (apart from gas usage) is a change to a topmost segment of the stack - int gasPriceTier; ///< Tier for gas pricing. + Tier gasPriceTier; ///< Tier for gas pricing. }; /// Information on all the instructions. From 10c2df8b33115eb1806cd3796c20c295e8e6f38d Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 13 Jan 2017 13:56:55 +0100 Subject: [PATCH 052/414] Update version to 0.4.9. --- CMakeLists.txt | 2 +- Changelog.md | 2 ++ docs/conf.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ecd857adf..68bb71f4a 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.8") +set(PROJECT_VERSION "0.4.9") project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies diff --git a/Changelog.md b/Changelog.md index b8effc19f..a5c4b922f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,5 @@ +### 0.4.9 (unreleased) + ### 0.4.8 (2017-01-13) Features: diff --git a/docs/conf.py b/docs/conf.py index ecabbb861..85fc36776 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,9 +56,9 @@ copyright = '2016, Ethereum' # built documents. # # The short X.Y version. -version = '0.4.8' +version = '0.4.9' # The full version, including alpha/beta/rc tags. -release = '0.4.8-develop' +release = '0.4.9-develop' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From fb5583857a85b91614c761c6d91eafc2627177ba Mon Sep 17 00:00:00 2001 From: Nicola Date: Fri, 13 Jan 2017 15:40:46 +0100 Subject: [PATCH 053/414] Update SolidityLexer.py 'payable' added as a keyword --- docs/utils/SolidityLexer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py index 779147f48..b536d1a49 100644 --- a/docs/utils/SolidityLexer.py +++ b/docs/utils/SolidityLexer.py @@ -57,7 +57,7 @@ class SolidityLexer(RegexLexer): (r'(for|in|while|do|break|return|continue|switch|case|default|if|else|' r'throw|try|catch|finally|new|delete|typeof|instanceof|void|' r'this|import|mapping|returns|private|public|external|internal|' - r'constant|memory|storage)\b', Keyword, 'slashstartsregex'), + r'constant|memory|storage|payable)\b', Keyword, 'slashstartsregex'), (r'(var|let|with|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'), (r'(bytes|string|address|uint|int|bool|byte|' + '|'.join( From 79e5772b8a18bc4a7a1fd1019604cec6f741b743 Mon Sep 17 00:00:00 2001 From: Nicola Date: Mon, 16 Jan 2017 10:26:09 +0100 Subject: [PATCH 054/414] Update SolidityLexer.py (#1567) Added number unit keywords --- docs/utils/SolidityLexer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py index b536d1a49..a7da59aaf 100644 --- a/docs/utils/SolidityLexer.py +++ b/docs/utils/SolidityLexer.py @@ -67,6 +67,7 @@ class SolidityLexer(RegexLexer): ['ufixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)] + ['fixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)] ) + r')\b', Keyword.Type, 'slashstartsregex'), + (r'(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b', Keyword.Type, 'slashstartsregex'), (r'(abstract|boolean|byte|char|class|const|debugger|double|enum|export|' r'extends|final|float|goto|implements|int|interface|long|native|' r'package|private|protected|public|short|static|super|synchronized|throws|' From f3a84eab91a6f05cfe58e260f206f9af51811313 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Fri, 18 Nov 2016 07:37:15 -0800 Subject: [PATCH 055/414] Error out when contracts collide on name The previous behaviour, courtesy of the [] operator in std::map, would uncritically store a new ContractDefinition in m_contracts even when a ContractDefinition already existed. This "resolved" collissions on contract names by clobbering the original one with the new one, and could lead to scenarios where the clobber would only be discovered when the original ContractDefinition could not be found or referred to, which was an unhelpful InternalCompilerError. This change checks the m_contracts map for a collision first and will not let the ContractDefinition be changed to a new one once it's set, throwing a CompilerError with information about the conflict. --- libsolidity/interface/CompilerStack.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index a31df5846..5d4ccd92b 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -180,6 +180,15 @@ bool CompilerStack::parse() if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false; if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false; if (!resolver.resolveNamesAndTypes(*contract)) return false; + if (m_contracts.find(contract->name()) != m_contracts.end()) + { + const ContractDefinition* existingContract = m_contracts.find(contract->name())->second.contract; + if (contract != existingContract) + BOOST_THROW_EXCEPTION(CompilerError() << + errinfo_sourceLocation(contract->location()) << + errinfo_comment(contract->name() + " conflicts with contract at " + + *(existingContract->location().sourceName))); + } m_contracts[contract->name()].contract = contract; } @@ -201,6 +210,16 @@ bool CompilerStack::parse() else noErrors = false; + if (m_contracts.find(contract->name()) != m_contracts.end()) + { + const ContractDefinition* existingContract = m_contracts.find(contract->name())->second.contract; + if (contract != existingContract) + BOOST_THROW_EXCEPTION(CompilerError() << + errinfo_sourceLocation(contract->location()) << + errinfo_comment(contract->name() + " conflicts with!!! contract at " + + *(existingContract->location().sourceName))); + } + m_contracts[contract->name()].contract = contract; } From b24ca4fa236ccc89600d30206172baf3eee386a7 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Fri, 18 Nov 2016 19:29:08 -0800 Subject: [PATCH 056/414] Fix tab, drop stupid '!!!', change error message. --- libsolidity/interface/CompilerStack.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 5d4ccd92b..dded81f5f 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -183,10 +183,10 @@ bool CompilerStack::parse() if (m_contracts.find(contract->name()) != m_contracts.end()) { const ContractDefinition* existingContract = m_contracts.find(contract->name())->second.contract; - if (contract != existingContract) + if (contract != existingContract) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(contract->location()) << - errinfo_comment(contract->name() + " conflicts with contract at " + errinfo_comment(contract->name() + " is already defined at " + *(existingContract->location().sourceName))); } m_contracts[contract->name()].contract = contract; @@ -216,7 +216,7 @@ bool CompilerStack::parse() if (contract != existingContract) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(contract->location()) << - errinfo_comment(contract->name() + " conflicts with!!! contract at " + errinfo_comment(contract->name() + " is already defined at " + *(existingContract->location().sourceName))); } From ce3082dec2178273d4913a6f9df909c0aca8ef5a Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sun, 27 Nov 2016 18:46:44 -0800 Subject: [PATCH 057/414] Tidy up the error message --- libsolidity/interface/CompilerStack.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index dded81f5f..dd5188607 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -184,10 +184,10 @@ bool CompilerStack::parse() { const ContractDefinition* existingContract = m_contracts.find(contract->name())->second.contract; if (contract != existingContract) - BOOST_THROW_EXCEPTION(CompilerError() << + BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(contract->location()) << - errinfo_comment(contract->name() + " is already defined at " - + *(existingContract->location().sourceName))); + errinfo_comment(contract->name() + " is already defined.") << + errinfo_secondarySourceLocation(SecondarySourceLocation().append(existingContract->location()), "Previous definition is here:")); } m_contracts[contract->name()].contract = contract; } From 9e88f1eebe27c780c80be06d702eac30e2fc5fa3 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sun, 27 Nov 2016 18:50:42 -0800 Subject: [PATCH 058/414] Tab whitespace cleanup (again) --- libsolidity/interface/CompilerStack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index dd5188607..9c4618e78 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -187,7 +187,8 @@ bool CompilerStack::parse() BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(contract->location()) << errinfo_comment(contract->name() + " is already defined.") << - errinfo_secondarySourceLocation(SecondarySourceLocation().append(existingContract->location()), "Previous definition is here:")); + errinfo_secondarySourceLocation( + SecondarySourceLocation().append(existingContract->location()), "Previous definition is here:")); } m_contracts[contract->name()].contract = contract; } From 071b936b371cd5287717d0ac27b80b837836809a Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sun, 11 Dec 2016 14:17:38 -0800 Subject: [PATCH 059/414] Only avoid collision if it's the same file @chriseth had suggested that it would be better if contracts were referenced in a file:contract notation, and that we output .bin files that prepend original path names if necessary to avoid a collision. This commit is mostly a draft; it still needs to be run through the test suite. --- libsolidity/ast/AST.cpp | 6 ++++ libsolidity/ast/AST.h | 2 ++ libsolidity/interface/CompilerStack.cpp | 48 ++++++++++++++++++------- libsolidity/interface/CompilerStack.h | 4 +++ solc/CommandLineInterface.cpp | 6 ++-- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 6f7a64dcb..480fce443 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -190,6 +190,12 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat } +std::string ContractDefinition::fullyQualifiedName() const +{ + std::string qualifiedName = *(location().sourceName) + ":" + name(); + return qualifiedName; +} + vector const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 2d0924089..060cf9737 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -358,6 +358,8 @@ public: Json::Value const& devDocumentation() const; void setDevDocumentation(Json::Value const& _devDocumentation); + std::string fullyQualifiedName() const; + virtual TypePointer type() const override; virtual ContractDefinitionAnnotation& annotation() const override; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 9c4618e78..13cf7d6ce 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -21,6 +21,7 @@ * Full-stack compiler that converts a source code string to bytecode. */ + #include #include @@ -180,17 +181,18 @@ bool CompilerStack::parse() if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false; if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false; if (!resolver.resolveNamesAndTypes(*contract)) return false; - if (m_contracts.find(contract->name()) != m_contracts.end()) + + if (m_contracts.find(contract->fullyQualifiedName()) != m_contracts.end()) { - const ContractDefinition* existingContract = m_contracts.find(contract->name())->second.contract; + const ContractDefinition* existingContract = m_contracts.find(contract->fullyQualifiedName())->second.contract; if (contract != existingContract) - BOOST_THROW_EXCEPTION(DeclarationError() << + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(contract->location()) << errinfo_comment(contract->name() + " is already defined.") << errinfo_secondarySourceLocation( - SecondarySourceLocation().append(existingContract->location()), "Previous definition is here:")); + SecondarySourceLocation().append("Previous definition is here:", existingContract->location()))); } - m_contracts[contract->name()].contract = contract; + m_contracts[contract->fullyQualifiedName()].contract = contract; } if (!checkLibraryNameClashes()) @@ -211,9 +213,9 @@ bool CompilerStack::parse() else noErrors = false; - if (m_contracts.find(contract->name()) != m_contracts.end()) + if (m_contracts.find(contract->fullyQualifiedName()) != m_contracts.end()) { - const ContractDefinition* existingContract = m_contracts.find(contract->name())->second.contract; + const ContractDefinition* existingContract = m_contracts.find(contract->fullyQualifiedName())->second.contract; if (contract != existingContract) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(contract->location()) << @@ -221,7 +223,7 @@ bool CompilerStack::parse() + *(existingContract->location().sourceName))); } - m_contracts[contract->name()].contract = contract; + m_contracts[contract->fullyQualifiedName()].contract = contract; } if (noErrors) @@ -335,6 +337,28 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c return c.runtimeSourceMapping.get(); } +std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const +{ + // Look up the contract (by its fully-qualified name) + Contract const& matchContract = m_contracts.at(_contractName); + // Check to see if it could collide on name + for (auto const& contract: m_contracts) + { + if (contract.second.contract->name() == matchContract.contract->name() && + contract.second.contract != matchContract.contract) + { + // If it does, then return its fully-qualified name, made fs-friendly + std::string friendlyName = boost::algorithm::replace_all_copy(_contractName, "/", "_"); + boost::algorithm::replace_all(friendlyName, ":", "_"); + boost::algorithm::replace_all(friendlyName, ".", "_"); + return friendlyName; + } + } + // If no collision, return the contract's name + // String is copied to ensure that the contract's name can't be messed with + return std::string(matchContract.contract->name()); +} + eth::LinkerObject const& CompilerStack::object(string const& _contractName) const { return contract(_contractName).object; @@ -598,7 +622,7 @@ bool CompilerStack::checkLibraryNameClashes() if (ContractDefinition* contract = dynamic_cast(node.get())) if (contract->isLibrary()) { - if (libraries.count(contract->name())) + if (libraries.count(contract->fullyQualifiedName())) { auto err = make_shared(Error::Type::DeclarationError); *err << @@ -615,7 +639,7 @@ bool CompilerStack::checkLibraryNameClashes() clashFound = true; } else - libraries[contract->name()] = contract->location(); + libraries[contract->fullyQualifiedName()] = contract->location(); } return !clashFound; } @@ -648,7 +672,7 @@ void CompilerStack::compileContract( compileContract(*dependency, _compiledContracts); shared_ptr compiler = make_shared(m_optimize, m_optimizeRuns); - Contract& compiledContract = m_contracts.at(_contract.name()); + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); string onChainMetadata = createOnChainMetadata(compiledContract); bytes cborEncodedMetadata = // CBOR-encoding of {"bzzr0": dev::swarmHash(onChainMetadata)} @@ -694,7 +718,7 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa for (auto const& it: m_sources) for (ASTPointer const& node: it.second.ast->nodes()) if (auto contract = dynamic_cast(node.get())) - contractName = contract->name(); + contractName = contract->fullyQualifiedName(); auto it = m_contracts.find(contractName); if (it == m_contracts.end()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index d49a8df14..9436bd834 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -148,6 +148,10 @@ public: /// @returns the string that provides a mapping between runtime bytecode and sourcecode. /// if the contract does not (yet) have bytecode. std::string const* runtimeSourceMapping(std::string const& _contractName = "") const; + + /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use + std::string const filesystemFriendlyName(std::string const& _contractName) const; + /// @returns hash of the runtime bytecode for the contract, i.e. the code that is /// returned by the constructor or the zero-h256 if the contract still needs to be linked or /// does not have runtime code. diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index e322455b8..cebc9a07e 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -185,8 +185,10 @@ void CommandLineInterface::handleBinary(string const& _contract) { if (m_args.count(g_argBinary)) { - if (m_args.count(g_argOutputDir)) - createFile(_contract + ".bin", m_compiler->object(_contract).toHex()); + if (m_args.count("output-dir")) + { + createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", m_compiler->object(_contract).toHex()); + } else { cout << "Binary: " << endl; From 8f25bd54e35dba3fb632c2d209a86acaba63d33d Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sun, 11 Dec 2016 23:06:57 -0800 Subject: [PATCH 060/414] Drop ':' if the source file name is empty A large number of tests compile contracts while passing in an empty string for the source name. This leads to it being keyed by the name ":", while the tests try to look it up under the name "". This change resolves that issue by dropping the ':' in cases where there is, effectively, no source file to prepend anyway. --- libsolidity/ast/AST.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 480fce443..562ac8285 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -192,7 +192,8 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat std::string ContractDefinition::fullyQualifiedName() const { - std::string qualifiedName = *(location().sourceName) + ":" + name(); + std::string sourceString = *(location().sourceName); + std::string qualifiedName = (sourceString.empty() ? ("") : (sourceString + ":")) + name(); return qualifiedName; } From e3b0827721f114a371df57b7079205034683ed25 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sun, 11 Dec 2016 23:58:01 -0800 Subject: [PATCH 061/414] Push the error instead of throwing it Throwing a CompilerError on multiple contract definition violates the expectations of the test suite, which thinks that compile() will return false if the code can't compile. This brings contract collision reporting in line with most of the other errors. --- libsolidity/interface/CompilerStack.cpp | 48 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 13cf7d6ce..7626406c0 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -186,13 +186,24 @@ bool CompilerStack::parse() { const ContractDefinition* existingContract = m_contracts.find(contract->fullyQualifiedName())->second.contract; if (contract != existingContract) - BOOST_THROW_EXCEPTION(CompilerError() << + { + auto err = make_shared(Error::Type::DeclarationError); + *err << errinfo_sourceLocation(contract->location()) << - errinfo_comment(contract->name() + " is already defined.") << - errinfo_secondarySourceLocation( - SecondarySourceLocation().append("Previous definition is here:", existingContract->location()))); + errinfo_comment( + "Contract/Library \"" + contract->name() + "\" declared twice " + ) << + errinfo_secondarySourceLocation(SecondarySourceLocation().append( + "The other declaration is here:", existingContract->location())); + + m_errors.push_back(err); + noErrors = false; + } + } + else + { + m_contracts[contract->fullyQualifiedName()].contract = contract; } - m_contracts[contract->fullyQualifiedName()].contract = contract; } if (!checkLibraryNameClashes()) @@ -216,14 +227,27 @@ bool CompilerStack::parse() if (m_contracts.find(contract->fullyQualifiedName()) != m_contracts.end()) { const ContractDefinition* existingContract = m_contracts.find(contract->fullyQualifiedName())->second.contract; - if (contract != existingContract) - BOOST_THROW_EXCEPTION(CompilerError() << - errinfo_sourceLocation(contract->location()) << - errinfo_comment(contract->name() + " is already defined at " - + *(existingContract->location().sourceName))); - } - m_contracts[contract->fullyQualifiedName()].contract = contract; + if (contract != existingContract) + { + auto err = make_shared(Error::Type::DeclarationError); + *err << + errinfo_sourceLocation(contract->location()) << + errinfo_comment( + "Contract/Library \"" + contract->name() + "\" declared twice " + ) << + errinfo_secondarySourceLocation(SecondarySourceLocation().append( + "The other declaration is here:", existingContract->location())); + + m_errors.push_back(err); + noErrors = false; + } + + } + else + { + m_contracts[contract->fullyQualifiedName()].contract = contract; + } } if (noErrors) From 220ccfb492511eab5a45936e477e6882f52be96a Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Mon, 12 Dec 2016 00:00:50 -0800 Subject: [PATCH 062/414] Move clashing libraries to common source in test Since contracts and libraries only collide if they share a common source file now, this test only works if both libraries are in the same source. --- test/libsolidity/Imports.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 56895fdc7..e01d2e905 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -104,8 +104,7 @@ BOOST_AUTO_TEST_CASE(simple_alias) BOOST_AUTO_TEST_CASE(library_name_clash) { CompilerStack c; - c.addSource("a", "library A {} pragma solidity >=0.0;"); - c.addSource("b", "library A {} pragma solidity >=0.0;"); + c.addSource("a", "library A {} library A {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); } From 0c98e4b2da6b5de885114a744339a1f0f96c79be Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Fri, 16 Dec 2016 04:22:42 -0800 Subject: [PATCH 063/414] Stylistic corrections --- libsolidity/ast/AST.cpp | 2 +- libsolidity/interface/CompilerStack.cpp | 12 ++++-------- solc/CommandLineInterface.cpp | 2 -- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 562ac8285..45ff69c58 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -190,7 +190,7 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat } -std::string ContractDefinition::fullyQualifiedName() const +string ContractDefinition::fullyQualifiedName() const { std::string sourceString = *(location().sourceName); std::string qualifiedName = (sourceString.empty() ? ("") : (sourceString + ":")) + name(); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 7626406c0..fbaf1bcc6 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -184,7 +184,7 @@ bool CompilerStack::parse() if (m_contracts.find(contract->fullyQualifiedName()) != m_contracts.end()) { - const ContractDefinition* existingContract = m_contracts.find(contract->fullyQualifiedName())->second.contract; + ContractDefinition const* existingContract = m_contracts[contract->fullyQualifiedName()].contract; if (contract != existingContract) { auto err = make_shared(Error::Type::DeclarationError); @@ -201,9 +201,7 @@ bool CompilerStack::parse() } } else - { m_contracts[contract->fullyQualifiedName()].contract = contract; - } } if (!checkLibraryNameClashes()) @@ -224,9 +222,10 @@ bool CompilerStack::parse() else noErrors = false; + // Note that find() must be used here to prevent an automatic insert into the map if (m_contracts.find(contract->fullyQualifiedName()) != m_contracts.end()) { - const ContractDefinition* existingContract = m_contracts.find(contract->fullyQualifiedName())->second.contract; + ContractDefinition const* existingContract = m_contracts[contract->fullyQualifiedName()].contract; if (contract != existingContract) { @@ -245,9 +244,7 @@ bool CompilerStack::parse() } else - { m_contracts[contract->fullyQualifiedName()].contract = contract; - } } if (noErrors) @@ -379,8 +376,7 @@ std::string const CompilerStack::filesystemFriendlyName(string const& _contractN } } // If no collision, return the contract's name - // String is copied to ensure that the contract's name can't be messed with - return std::string(matchContract.contract->name()); + return matchContract.contract->name(); } eth::LinkerObject const& CompilerStack::object(string const& _contractName) const diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index cebc9a07e..4a4d3571b 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -186,9 +186,7 @@ void CommandLineInterface::handleBinary(string const& _contract) if (m_args.count(g_argBinary)) { if (m_args.count("output-dir")) - { createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", m_compiler->object(_contract).toHex()); - } else { cout << "Binary: " << endl; From f10bf36ae3681169be7c75dac32567f1746c529d Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Fri, 16 Dec 2016 04:52:19 -0800 Subject: [PATCH 064/414] Move fullyQualified() name to Declaration --- libsolidity/ast/AST.cpp | 8 -------- libsolidity/ast/AST.h | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 45ff69c58..dd582e544 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -189,14 +189,6 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat m_userDocumentation = _userDocumentation; } - -string ContractDefinition::fullyQualifiedName() const -{ - std::string sourceString = *(location().sourceName); - std::string qualifiedName = (sourceString.empty() ? ("") : (sourceString + ":")) + name(); - return qualifiedName; -} - vector const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 060cf9737..f72b272c2 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -161,6 +161,7 @@ public: /// @returns the source name this declaration is present in. /// Can be combined with annotation().canonicalName to form a globally unique name. std::string sourceUnitName() const; + std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); } virtual bool isLValue() const { return false; } virtual bool isPartOfExternalInterface() const { return false; } @@ -358,8 +359,6 @@ public: Json::Value const& devDocumentation() const; void setDevDocumentation(Json::Value const& _devDocumentation); - std::string fullyQualifiedName() const; - virtual TypePointer type() const override; virtual ContractDefinitionAnnotation& annotation() const override; From f8914c6b281f398d6084f10708233bf79d633ef0 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Fri, 16 Dec 2016 07:54:18 -0800 Subject: [PATCH 065/414] Fix contract lookup in tests The fully-qualified name of a contract with no source unit is : instead of just , so the test system needed to be adjusted accordingly. --- test/contracts/AuctionRegistrar.cpp | 2 +- test/contracts/FixedFeeRegistrar.cpp | 2 +- test/contracts/Wallet.cpp | 2 +- test/libsolidity/SolidityABIJSON.cpp | 4 ++-- test/libsolidity/SolidityExecutionFramework.h | 6 +++++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index fb8c1c68c..0169648e3 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -224,7 +224,7 @@ protected: m_compiler.reset(false); m_compiler.addSource("", registrarCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); - s_compiledRegistrar.reset(new bytes(m_compiler.object("GlobalRegistrar").bytecode)); + s_compiledRegistrar.reset(new bytes(m_compiler.object(":GlobalRegistrar").bytecode)); } sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(!m_output.empty()); diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index 39c32eb72..6ef1a8fe1 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -136,7 +136,7 @@ protected: m_compiler.reset(false); m_compiler.addSource("", registrarCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); - s_compiledRegistrar.reset(new bytes(m_compiler.object("FixedFeeRegistrar").bytecode)); + s_compiledRegistrar.reset(new bytes(m_compiler.object(":FixedFeeRegistrar").bytecode)); } sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(!m_output.empty()); diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index 80f06613e..24ecebe78 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -451,7 +451,7 @@ protected: m_compiler.reset(false); m_compiler.addSource("", walletCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); - s_compiledWallet.reset(new bytes(m_compiler.object("Wallet").bytecode)); + s_compiledWallet.reset(new bytes(m_compiler.object(":Wallet").bytecode)); } bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); sendMessage(*s_compiledWallet + args, true, _value); diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 043d74ed4..a54e9b7ea 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -762,8 +762,8 @@ BOOST_AUTO_TEST_CASE(metadata_stamp) } )"; BOOST_REQUIRE(m_compilerStack.compile(std::string(sourceCode))); - bytes const& bytecode = m_compilerStack.runtimeObject("test").bytecode; - bytes hash = dev::swarmHash(m_compilerStack.onChainMetadata("test")).asBytes(); + bytes const& bytecode = m_compilerStack.runtimeObject(":test").bytecode; + bytes hash = dev::swarmHash(m_compilerStack.onChainMetadata(":test")).asBytes(); BOOST_REQUIRE(hash.size() == 32); BOOST_REQUIRE(bytecode.size() >= 2); size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]); diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 03e3a881c..00e8330ce 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -67,7 +67,11 @@ public: ); BOOST_ERROR("Compiling contract failed"); } - eth::LinkerObject obj = m_compiler.object(_contractName); + eth::LinkerObject obj; + if (_contractName.empty()) + obj = m_compiler.object(_contractName); + else + obj = m_compiler.object(":" + _contractName); BOOST_REQUIRE(obj.linkReferences.empty()); sendMessage(obj.bytecode + _arguments, true, _value); return m_output; From 85c55c796adf4d93c3d11ccb8d048a51dc46cf8d Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Tue, 20 Dec 2016 04:57:46 -0800 Subject: [PATCH 066/414] Remove unique error for contract collision Because contracts are uniquely identified by their source unit, there is no need for a unique error for this; it's actually covered by the checker for double-declaration of identifiers. --- libsolidity/interface/CompilerStack.cpp | 49 +++++-------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index fbaf1bcc6..d4675a235 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -182,25 +182,12 @@ bool CompilerStack::parse() if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false; if (!resolver.resolveNamesAndTypes(*contract)) return false; - if (m_contracts.find(contract->fullyQualifiedName()) != m_contracts.end()) - { - ContractDefinition const* existingContract = m_contracts[contract->fullyQualifiedName()].contract; - if (contract != existingContract) - { - auto err = make_shared(Error::Type::DeclarationError); - *err << - errinfo_sourceLocation(contract->location()) << - errinfo_comment( - "Contract/Library \"" + contract->name() + "\" declared twice " - ) << - errinfo_secondarySourceLocation(SecondarySourceLocation().append( - "The other declaration is here:", existingContract->location())); + // Note that we now reference contracts by their fully qualified names, and + // thus contracts can only conflict if declared in the same source file. This + // already causes a double-declaration error elsewhere, so we do not report + // an error here and instead silently drop any additional contracts we find. - m_errors.push_back(err); - noErrors = false; - } - } - else + if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) m_contracts[contract->fullyQualifiedName()].contract = contract; } @@ -222,28 +209,12 @@ bool CompilerStack::parse() else noErrors = false; - // Note that find() must be used here to prevent an automatic insert into the map - if (m_contracts.find(contract->fullyQualifiedName()) != m_contracts.end()) - { - ContractDefinition const* existingContract = m_contracts[contract->fullyQualifiedName()].contract; + // Note that we now reference contracts by their fully qualified names, and + // thus contracts can only conflict if declared in the same source file. This + // already causes a double-declaration error elsewhere, so we do not report + // an error here and instead silently drop any additional contracts we find. - if (contract != existingContract) - { - auto err = make_shared(Error::Type::DeclarationError); - *err << - errinfo_sourceLocation(contract->location()) << - errinfo_comment( - "Contract/Library \"" + contract->name() + "\" declared twice " - ) << - errinfo_secondarySourceLocation(SecondarySourceLocation().append( - "The other declaration is here:", existingContract->location())); - - m_errors.push_back(err); - noErrors = false; - } - - } - else + if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) m_contracts[contract->fullyQualifiedName()].contract = contract; } From 1f30982ab532fdf719f3924e24e80057fe85e031 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Wed, 21 Dec 2016 11:26:22 -0800 Subject: [PATCH 067/414] Use fully-qualified names for linking, too Using libraries leaves behind a library link reference in the binary which the linker must later resolve. These link references were still being generated by name and not by fully-qualified name. This would lead to a link-time collision between two libraries having the same name but in different source units. This change changes linker symbols over to fully-qualified names, which resolves that issue. This does potentially introduce a new problem, which is that linker symbols appear to be limited to 36 characters and are truncated. Storing paths extends the average symbol size, and it would be great if truncation was from the tail rather than the head. --- libsolidity/codegen/ContractCompiler.cpp | 2 +- libsolidity/codegen/ExpressionCompiler.cpp | 4 ++-- test/libsolidity/SolidityEndToEndTest.cpp | 24 +++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a0f196bc3..fa77c1aad 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -575,7 +575,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) else if (auto contract = dynamic_cast(decl)) { solAssert(contract->isLibrary(), ""); - _assembly.appendLibraryAddress(contract->name()); + _assembly.appendLibraryAddress(contract->fullyQualifiedName()); } else solAssert(false, "Invalid declaration type."); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 3922da881..37bd14583 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -892,7 +892,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(funType->location() == FunctionType::Location::DelegateCall, ""); auto contract = dynamic_cast(funType->declaration().scope()); solAssert(contract && contract->isLibrary(), ""); - m_context.appendLibraryAddress(contract->name()); + m_context.appendLibraryAddress(contract->fullyQualifiedName()); m_context << funType->externalIdentifier(); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 2); } @@ -1270,7 +1270,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) else if (auto contract = dynamic_cast(declaration)) { if (contract->isLibrary()) - m_context.appendLibraryAddress(contract->name()); + m_context.appendLibraryAddress(contract->fullyQualifiedName()); } else if (dynamic_cast(declaration)) { diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 191618317..03aa26999 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3192,7 +3192,7 @@ BOOST_AUTO_TEST_CASE(library_call_in_homestead) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs()); BOOST_CHECK(callContractFunction("sender()") == encodeArgs(u160(m_sender))); } @@ -6191,7 +6191,7 @@ BOOST_AUTO_TEST_CASE(library_call) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(33)) == encodeArgs(u256(33) * 9)); } @@ -6208,7 +6208,7 @@ BOOST_AUTO_TEST_CASE(library_stray_values) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(33)) == encodeArgs(u256(42))); } @@ -6341,7 +6341,7 @@ BOOST_AUTO_TEST_CASE(internal_types_in_library) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(4), u256(17))); } @@ -6368,7 +6368,7 @@ BOOST_AUTO_TEST_CASE(using_library_structs) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7), u256(8))); } @@ -6902,7 +6902,7 @@ BOOST_AUTO_TEST_CASE(using_for_function_on_int) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(9)) == encodeArgs(u256(2 * 9))); } @@ -6920,7 +6920,7 @@ BOOST_AUTO_TEST_CASE(using_for_function_on_struct) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(3 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(3 * 7))); } @@ -6943,7 +6943,7 @@ BOOST_AUTO_TEST_CASE(using_for_overload) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(6 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(6 * 7))); } @@ -6962,7 +6962,7 @@ BOOST_AUTO_TEST_CASE(using_for_by_name) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(6 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(6 * 7))); } @@ -6982,7 +6982,7 @@ BOOST_AUTO_TEST_CASE(bound_function_in_var) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(6 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(6 * 7))); } @@ -7005,7 +7005,7 @@ BOOST_AUTO_TEST_CASE(bound_function_to_string) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(3))); BOOST_CHECK(callContractFunction("g()") == encodeArgs(u256(3))); } @@ -7751,7 +7751,7 @@ BOOST_AUTO_TEST_CASE(payable_function_calls_library) } )"; compileAndRun(sourceCode, 0, "L"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{"L", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{":L", m_contractAddress}}); BOOST_CHECK(callContractFunctionWithValue("f()", 27) == encodeArgs(u256(7))); } From 5a2331a9f6615e58f4d10d266870daaaf05deb6b Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Wed, 21 Dec 2016 11:40:13 -0800 Subject: [PATCH 068/414] Remove checkLibraryNameClashes() The library name clash checker throws errors when two libraries of the same name are spotted. In a previous commit, this function was rewritten to use fully-qualified names instead, which makes it redundant to the checker for multiply-declared identifiers. Since it no longer serves a clear purpose, the function is being dropped. --- libsolidity/interface/CompilerStack.cpp | 34 ------------------------- libsolidity/interface/CompilerStack.h | 3 --- 2 files changed, 37 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index d4675a235..d8bb20d7c 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -191,9 +191,6 @@ bool CompilerStack::parse() m_contracts[contract->fullyQualifiedName()].contract = contract; } - if (!checkLibraryNameClashes()) - noErrors = false; - for (Source const* source: m_sourceOrder) for (ASTPointer const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) @@ -604,37 +601,6 @@ void CompilerStack::resolveImports() swap(m_sourceOrder, sourceOrder); } -bool CompilerStack::checkLibraryNameClashes() -{ - bool clashFound = false; - map libraries; - for (Source const* source: m_sourceOrder) - for (ASTPointer const& node: source->ast->nodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) - if (contract->isLibrary()) - { - if (libraries.count(contract->fullyQualifiedName())) - { - auto err = make_shared(Error::Type::DeclarationError); - *err << - errinfo_sourceLocation(contract->location()) << - errinfo_comment( - "Library \"" + contract->name() + "\" declared twice " - "(will create ambiguities during linking)." - ) << - errinfo_secondarySourceLocation(SecondarySourceLocation().append( - "The other declaration is here:", libraries[contract->name()] - )); - - m_errors.push_back(err); - clashFound = true; - } - else - libraries[contract->fullyQualifiedName()] = contract->location(); - } - return !clashFound; -} - string CompilerStack::absolutePath(string const& _path, string const& _reference) const { using path = boost::filesystem::path; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 9436bd834..61edc284a 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -234,9 +234,6 @@ private: StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path); std::string applyRemapping(std::string const& _path, std::string const& _context); void resolveImports(); - /// Checks whether there are libraries with the same name, reports that as an error and - /// @returns false in this case. - bool checkLibraryNameClashes(); /// @returns the absolute path corresponding to @a _path relative to @a _reference. std::string absolutePath(std::string const& _path, std::string const& _reference) const; /// Helper function to return path converted strings. From a7f8a1986a9b3997c75cfc84235e567d2fa9aab4 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Wed, 21 Dec 2016 11:45:34 -0800 Subject: [PATCH 069/414] Revert "Move clashing libraries to common source in test" This reverts commit c4a9ca5cfe7a8b4ba9d2d84392c57d5eefacd1f7. --- test/libsolidity/Imports.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index e01d2e905..56895fdc7 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -104,7 +104,8 @@ BOOST_AUTO_TEST_CASE(simple_alias) BOOST_AUTO_TEST_CASE(library_name_clash) { CompilerStack c; - c.addSource("a", "library A {} library A {} pragma solidity >=0.0;"); + c.addSource("a", "library A {} pragma solidity >=0.0;"); + c.addSource("b", "library A {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); } From fda39afdba9591131aac6d647cb46247e9495606 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Thu, 22 Dec 2016 08:30:53 -0800 Subject: [PATCH 070/414] Modify library collision test Since libaraies no longer collide on name but on fully-qualified name, you can only induce collision by colliding them as idenfitiers. --- test/libsolidity/Imports.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 56895fdc7..6aa96fb87 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -106,6 +106,7 @@ BOOST_AUTO_TEST_CASE(library_name_clash) CompilerStack c; c.addSource("a", "library A {} pragma solidity >=0.0;"); c.addSource("b", "library A {} pragma solidity >=0.0;"); + c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";"); BOOST_CHECK(!c.compile()); } From 610156fb92cbf9feadf14b6d0827f0f840b4c765 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Thu, 5 Jan 2017 20:08:34 -0500 Subject: [PATCH 071/414] Fix bad merge artifact(?) Looks like merging up munged line 188 in CommandLineInterface.cpp, so that a string literal was being used where a global variable should be. --- solc/CommandLineInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 4a4d3571b..e49e8517d 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -185,7 +185,7 @@ void CommandLineInterface::handleBinary(string const& _contract) { if (m_args.count(g_argBinary)) { - if (m_args.count("output-dir")) + if (m_args.count(g_argOutputDir)) createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", m_compiler->object(_contract).toHex()); else { From 43dbf01e73b915dbeddfd7eb9d62112fdf2f99f1 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sun, 8 Jan 2017 21:49:39 -0500 Subject: [PATCH 072/414] Update changelog --- Changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog.md b/Changelog.md index a5c4b922f..2c5c37fd7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,9 @@ Bugfixes: * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. * Type checker, disallow inheritances of different kinds (e.g. a function and a modifier) of members of the same name +Features: + * Contracts and libraries are now unique to their source files, rather than globally. + ### 0.4.7 (2016-12-15) Features: From 88a2ac25e5e10aa7cea8627b60aa663f7ed6066d Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Mon, 16 Jan 2017 18:07:48 -0500 Subject: [PATCH 073/414] Revert "Fix contract lookup in tests" This reverts commit f8914c6b281f398d6084f10708233bf79d633ef0. --- test/contracts/AuctionRegistrar.cpp | 2 +- test/contracts/FixedFeeRegistrar.cpp | 2 +- test/contracts/Wallet.cpp | 2 +- test/libsolidity/SolidityABIJSON.cpp | 4 ++-- test/libsolidity/SolidityExecutionFramework.h | 6 +----- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index 0169648e3..fb8c1c68c 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -224,7 +224,7 @@ protected: m_compiler.reset(false); m_compiler.addSource("", registrarCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); - s_compiledRegistrar.reset(new bytes(m_compiler.object(":GlobalRegistrar").bytecode)); + s_compiledRegistrar.reset(new bytes(m_compiler.object("GlobalRegistrar").bytecode)); } sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(!m_output.empty()); diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index 6ef1a8fe1..39c32eb72 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -136,7 +136,7 @@ protected: m_compiler.reset(false); m_compiler.addSource("", registrarCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); - s_compiledRegistrar.reset(new bytes(m_compiler.object(":FixedFeeRegistrar").bytecode)); + s_compiledRegistrar.reset(new bytes(m_compiler.object("FixedFeeRegistrar").bytecode)); } sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(!m_output.empty()); diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index 24ecebe78..80f06613e 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -451,7 +451,7 @@ protected: m_compiler.reset(false); m_compiler.addSource("", walletCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); - s_compiledWallet.reset(new bytes(m_compiler.object(":Wallet").bytecode)); + s_compiledWallet.reset(new bytes(m_compiler.object("Wallet").bytecode)); } bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); sendMessage(*s_compiledWallet + args, true, _value); diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index a54e9b7ea..043d74ed4 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -762,8 +762,8 @@ BOOST_AUTO_TEST_CASE(metadata_stamp) } )"; BOOST_REQUIRE(m_compilerStack.compile(std::string(sourceCode))); - bytes const& bytecode = m_compilerStack.runtimeObject(":test").bytecode; - bytes hash = dev::swarmHash(m_compilerStack.onChainMetadata(":test")).asBytes(); + bytes const& bytecode = m_compilerStack.runtimeObject("test").bytecode; + bytes hash = dev::swarmHash(m_compilerStack.onChainMetadata("test")).asBytes(); BOOST_REQUIRE(hash.size() == 32); BOOST_REQUIRE(bytecode.size() >= 2); size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]); diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 00e8330ce..03e3a881c 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -67,11 +67,7 @@ public: ); BOOST_ERROR("Compiling contract failed"); } - eth::LinkerObject obj; - if (_contractName.empty()) - obj = m_compiler.object(_contractName); - else - obj = m_compiler.object(":" + _contractName); + eth::LinkerObject obj = m_compiler.object(_contractName); BOOST_REQUIRE(obj.linkReferences.empty()); sendMessage(obj.bytecode + _arguments, true, _value); return m_output; From 94b092d87c051e8846f5d61eaa1a4581b6588c71 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Mon, 16 Jan 2017 23:47:04 -0500 Subject: [PATCH 074/414] Provide fall-back method for contract lookup Properly, contracts are now looked up via : identifiers called "fully qualified names." As a modicum of backward-compatibility, failure on a lookup is now backed up by seeing if the ":" exists at all, and if it doesn't, then the known contracts are scanned for any matching contract name. --- libsolidity/interface/CompilerStack.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index d8bb20d7c..262b91ff1 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -677,8 +677,25 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa if (auto contract = dynamic_cast(node.get())) contractName = contract->fullyQualifiedName(); auto it = m_contracts.find(contractName); - if (it == m_contracts.end()) + // To provide a measure of backward-compatibility, if a contract is not located by its + // fully-qualified name, a lookup will be attempted purely on the contract's name to see + // if anything will satisfy. + if (it == m_contracts.end() && contractName.find(":") == string::npos) + { + for (auto const& contractEntry: m_contracts) + { + stringstream ss; + ss.str(contractEntry.first); + // All entries are : + string source; + string foundName; + getline(ss, source, ':'); + getline(ss, foundName, ':'); + if (foundName == contractName) return contractEntry.second; + } + // If we get here, both lookup methods failed. BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); + } return it->second; } From 6ecb4aa36f8c30ca91f650d17f1214c2f2bc4319 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Tue, 17 Jan 2017 02:14:53 -0500 Subject: [PATCH 075/414] Add git submodule init to build instructions (#1569) --- docs/installing-solidity.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index ef38705cb..d73747179 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -119,6 +119,11 @@ you should fork Solidity and add your personal fork as a second remote: cd solidity git remote add personal git@github.com:[username]/solidity.git +Solidity has git submodules. Ensure they are properly loaded: + +.. code:: bash + + git submodule update --init --recursive Prerequisites - macOS --------------------- From 99eaadd2cd4ece9c3ceba16fa5559c45c2e66b08 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 17 Jan 2017 10:31:09 +0100 Subject: [PATCH 076/414] Deterministic AST node identifiers. --- Changelog.md | 3 +++ libsolidity/ast/AST.cpp | 2 ++ libsolidity/ast/AST.h | 4 ++++ libsolidity/ast/ASTJsonConverter.cpp | 4 ++-- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index a5c4b922f..1a80bac04 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ ### 0.4.9 (unreleased) +Features: + * AST: Use deterministic node identifiers. + ### 0.4.8 (2017-01-13) Features: diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 6f7a64dcb..3db4627a2 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -37,6 +37,8 @@ using namespace dev::solidity; ASTNode::ASTNode(SourceLocation const& _location): m_location(_location) { + static size_t id = 0; + m_id = ++id; } ASTNode::~ASTNode() diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 2d0924089..e9df2e7d5 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -57,6 +57,9 @@ public: explicit ASTNode(SourceLocation const& _location); virtual ~ASTNode(); + /// @returns an identifier of this AST node that is unique for a single compilation run. + size_t id() const { return m_id; } + virtual void accept(ASTVisitor& _visitor) = 0; virtual void accept(ASTConstVisitor& _visitor) const = 0; template @@ -94,6 +97,7 @@ public: ///@} protected: + size_t m_id = 0; /// Annotation - is specialised in derived classes, is created upon request (because of polymorphism). mutable ASTAnnotation* m_annotation = nullptr; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index abaad0fdf..de8fde92f 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -42,7 +42,7 @@ void ASTJsonConverter::addJsonNode( { Json::Value node; - node["id"] = reinterpret_cast(&_node); + node["id"] = _node.id(); node["src"] = sourceLocationToString(_node.location()); node["name"] = _nodeName; if (_attributes.size() != 0) @@ -124,7 +124,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) { Json::Value linearizedBaseContracts(Json::arrayValue); for (auto const& baseContract: _node.annotation().linearizedBaseContracts) - linearizedBaseContracts.append(reinterpret_cast(baseContract)); + linearizedBaseContracts.append(baseContract->id()); addJsonNode(_node, "ContractDefinition", { make_pair("name", _node.name()), make_pair("isLibrary", _node.isLibrary()), From 9f9b7fb6f0953558bb671c5385dec9c875eb15f0 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 17 Jan 2017 12:32:07 +0000 Subject: [PATCH 077/414] Explain how version numbers are built --- docs/installing-solidity.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index d73747179..2a1f50124 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -216,6 +216,21 @@ Alternatively, you can build for Windows on the command-line, like so: cmake --build . --config RelWithDebInfo +The version string in detail +============================ + +The Solidity version string contains four parts: +- the version number +- pre-release tag, usually set to ``develop.YYYY.MM.DD`` or ``nightly.YYYY.MM.DD`` +- commit in the format of ``commit.GITHASH`` +- platform has arbitrary number of items, containing details about the platform and compiler + +If there are local modifications, the commit will be postfixed with ``.mod``. + +A relase example: ``0.4.8+commit.60cc1668.Emscripten.clang``. + +A pre-release example: ``0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang`` + Important information about versioning ====================================== From 350c7e7e2c1bafbe8d543fc052aedad3e69ba5e1 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 17 Jan 2017 12:09:13 +0000 Subject: [PATCH 078/414] Store strict version number in metadata (exclude the platform) --- Changelog.md | 1 + libsolidity/interface/CompilerStack.cpp | 2 +- libsolidity/interface/Version.cpp | 3 +++ libsolidity/interface/Version.h | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 1a80bac04..a4063640c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Features: * AST: Use deterministic node identifiers. + * Metadata: Do not include platform in the version number. ### 0.4.8 (2017-01-13) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index a31df5846..08b217155 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -695,7 +695,7 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const Json::Value meta; meta["version"] = 1; meta["language"] = "Solidity"; - meta["compiler"]["version"] = VersionString; + meta["compiler"]["version"] = VersionStringStrict; meta["sources"] = Json::objectValue; for (auto const& s: m_sources) diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index ff66f0398..30923fc25 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -38,6 +38,9 @@ string const dev::solidity::VersionString = (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); +string const dev::solidity::VersionStringStrict = + string(dev::solidity::VersionNumber) + + (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)); bytes dev::solidity::binaryVersion() { diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h index 5b07b3f42..24c3555d5 100644 --- a/libsolidity/interface/Version.h +++ b/libsolidity/interface/Version.h @@ -32,6 +32,7 @@ namespace solidity extern char const* VersionNumber; extern std::string const VersionString; +extern std::string const VersionStringStrict; /// @returns a binary form of the version string, where A.B.C-HASH is encoded such that /// the first byte is zero, the following three bytes encode A B and C (interpreted as decimals) From 467559917008525d6b606de264e4a86a1c5e0620 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 17 Jan 2017 12:35:13 +0000 Subject: [PATCH 079/414] Include SOL_VERSION_COMMIT/SOL_VERSION_PLATFORM in buildinfo.h --- cmake/scripts/buildinfo.cmake | 2 ++ cmake/templates/BuildInfo.h.in | 2 ++ libsolidity/interface/Version.cpp | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmake/scripts/buildinfo.cmake b/cmake/scripts/buildinfo.cmake index 8e1615f6d..efbfb8fb0 100644 --- a/cmake/scripts/buildinfo.cmake +++ b/cmake/scripts/buildinfo.cmake @@ -60,6 +60,8 @@ if (SOL_COMMIT_HASH AND SOL_LOCAL_CHANGES) set(SOL_COMMIT_HASH "${SOL_COMMIT_HASH}.mod") endif() +set(SOL_VERSION_COMMIT "commit.${SOL_COMMIT_HASH}") +set(SOl_VERSION_PLATFORM ETH_BUILD_PLATFORM) set(SOL_VERSION_BUILDINFO "commit.${SOL_COMMIT_HASH}.${ETH_BUILD_PLATFORM}") set(TMPFILE "${ETH_DST_DIR}/BuildInfo.h.tmp") diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in index 6c16e4ac4..4b35df981 100644 --- a/cmake/templates/BuildInfo.h.in +++ b/cmake/templates/BuildInfo.h.in @@ -8,3 +8,5 @@ #define ETH_BUILD_PLATFORM "@ETH_BUILD_PLATFORM@" #define SOL_VERSION_PRERELEASE "@SOL_VERSION_PRERELEASE@" #define SOL_VERSION_BUILDINFO "@SOL_VERSION_BUILDINFO@" +#define SOL_VERSION_COMMIT "@SOL_VERSION_COMMIT@" +#define SOL_VERSION_PLATFORM "@SOL_VERSION_PLATFORM@" diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index 30923fc25..0d23f9c3d 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -40,7 +40,8 @@ string const dev::solidity::VersionString = string const dev::solidity::VersionStringStrict = string(dev::solidity::VersionNumber) + - (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)); + (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + + (string(SOL_VERSION_COMMIT).empty() ? "" : "+" + string(SOL_VERSION_COMMIT)); bytes dev::solidity::binaryVersion() { From 592c692bceb4cbf14446ad7eff949a475ab63251 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 17 Jan 2017 13:01:11 +0000 Subject: [PATCH 080/414] Mention how our version maps to Semver --- docs/installing-solidity.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 2a1f50124..345780d71 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -227,6 +227,9 @@ The Solidity version string contains four parts: If there are local modifications, the commit will be postfixed with ``.mod``. +These parts are combined as required by Semver, where the Solidity pre-release tag equals to the Semver pre-release +and the Solidity commit and platform combined make up the Semver build metadata. + A relase example: ``0.4.8+commit.60cc1668.Emscripten.clang``. A pre-release example: ``0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang`` From 2bf89776ae999297d373b7e3fe5e16c0b5d85965 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 17 Jan 2017 13:08:22 +0000 Subject: [PATCH 081/414] Update copyright date in docs --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 85fc36776..e97eff3a6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ master_doc = 'index' # General information about the project. project = 'Solidity' -copyright = '2016, Ethereum' +copyright = '2016-2017, Ethereum' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From c16e141ffbca70174c0efa119dbe7c7ef14b5286 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 18 Jan 2017 14:09:40 +0100 Subject: [PATCH 082/414] Fix JSON output on macos. --- libsolidity/ast/ASTJsonConverter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index de8fde92f..69c10c8d3 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -42,7 +42,7 @@ void ASTJsonConverter::addJsonNode( { Json::Value node; - node["id"] = _node.id(); + node["id"] = Json::UInt64(_node.id()); node["src"] = sourceLocationToString(_node.location()); node["name"] = _nodeName; if (_attributes.size() != 0) @@ -124,7 +124,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) { Json::Value linearizedBaseContracts(Json::arrayValue); for (auto const& baseContract: _node.annotation().linearizedBaseContracts) - linearizedBaseContracts.append(baseContract->id()); + linearizedBaseContracts.append(Json::UInt64(baseContract->id())); addJsonNode(_node, "ContractDefinition", { make_pair("name", _node.name()), make_pair("isLibrary", _node.isLibrary()), From d40ae663ecbdbccc2b71eff175bb6765291ba078 Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Wed, 18 Jan 2017 12:43:23 -0300 Subject: [PATCH 083/414] Fix typo in comment --- libsolidity/ast/AST.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index d11a246c2..4af649634 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -606,7 +606,7 @@ private: /** * Declaration of a variable. This can be used in various places, e.g. in function parameter - * lists, struct definitions and even function bodys. + * lists, struct definitions and even function bodies. */ class VariableDeclaration: public Declaration { From 821997a1d38718d7808416a69a3ba276729d2c08 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 18 Jan 2017 17:24:39 +0100 Subject: [PATCH 084/414] libevmasm: remove duplicate `Tier` from `Tier::xTier`s Synchronize with cpp-ethereum --- libevmasm/GasMeter.cpp | 16 +-- libevmasm/Instruction.cpp | 262 +++++++++++++++++++------------------- libevmasm/Instruction.h | 20 +-- 3 files changed, 149 insertions(+), 149 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index bc7d4e55e..462c09dd0 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -223,14 +223,14 @@ unsigned GasMeter::runGas(Instruction _instruction) switch (instructionInfo(_instruction).gasPriceTier) { - case Tier::ZeroTier: return GasCosts::tier0Gas; - case Tier::BaseTier: return GasCosts::tier1Gas; - case Tier::VeryLowTier: return GasCosts::tier2Gas; - case Tier::LowTier: return GasCosts::tier3Gas; - case Tier::MidTier: return GasCosts::tier4Gas; - case Tier::HighTier: return GasCosts::tier5Gas; - case Tier::ExtTier: return GasCosts::tier6Gas; - case Tier::SpecialTier: return GasCosts::tier7Gas; + case Tier::Zero: return GasCosts::tier0Gas; + case Tier::Base: return GasCosts::tier1Gas; + case Tier::VeryLow: return GasCosts::tier2Gas; + case Tier::Low: return GasCosts::tier3Gas; + case Tier::Mid: return GasCosts::tier4Gas; + case Tier::High: return GasCosts::tier5Gas; + case Tier::Ext: return GasCosts::tier6Gas; + case Tier::Special: return GasCosts::tier7Gas; default: break; } assertThrow(false, OptimizerException, "Invalid gas tier."); diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 43cbbd380..17445c599 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -164,136 +164,136 @@ const std::map dev::solidity::c_instructions = static const std::map c_instructionInfo = { // Add, Args, Ret, SideEffects, GasPriceTier - { Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::ZeroTier } }, - { Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::SUB, { "SUB", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::MUL, { "MUL", 0, 2, 1, false, Tier::LowTier } }, - { Instruction::DIV, { "DIV", 0, 2, 1, false, Tier::LowTier } }, - { Instruction::SDIV, { "SDIV", 0, 2, 1, false, Tier::LowTier } }, - { Instruction::MOD, { "MOD", 0, 2, 1, false, Tier::LowTier } }, - { Instruction::SMOD, { "SMOD", 0, 2, 1, false, Tier::LowTier } }, - { Instruction::EXP, { "EXP", 0, 2, 1, false, Tier::SpecialTier } }, - { Instruction::NOT, { "NOT", 0, 1, 1, false, Tier::VeryLowTier } }, - { Instruction::LT, { "LT", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::GT, { "GT", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::SLT, { "SLT", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::SGT, { "SGT", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::EQ, { "EQ", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, Tier::VeryLowTier } }, - { Instruction::AND, { "AND", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::OR, { "OR", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::XOR, { "XOR", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::BYTE, { "BYTE", 0, 2, 1, false, Tier::VeryLowTier } }, - { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::MidTier } }, - { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::MidTier } }, - { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::LowTier } }, - { Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::SpecialTier } }, - { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::ExtTier } }, - { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, Tier::VeryLowTier } }, - { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, Tier::VeryLowTier } }, - { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLowTier } }, - { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::ExtTier } }, - { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtTier } }, - { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::ExtTier } }, - { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::POP, { "POP", 0, 1, 0, false, Tier::BaseTier } }, - { Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, Tier::VeryLowTier } }, - { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLowTier } }, - { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, Tier::VeryLowTier } }, - { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, Tier::SpecialTier } }, - { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, Tier::SpecialTier } }, - { Instruction::JUMP, { "JUMP", 0, 1, 0, true, Tier::MidTier } }, - { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, Tier::HighTier } }, - { Instruction::PC, { "PC", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::GAS, { "GAS", 0, 0, 1, false, Tier::BaseTier } }, - { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, Tier::SpecialTier } }, - { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, Tier::VeryLowTier } }, - { Instruction::DUP1, { "DUP1", 0, 1, 2, false, Tier::VeryLowTier } }, - { Instruction::DUP2, { "DUP2", 0, 2, 3, false, Tier::VeryLowTier } }, - { Instruction::DUP3, { "DUP3", 0, 3, 4, false, Tier::VeryLowTier } }, - { Instruction::DUP4, { "DUP4", 0, 4, 5, false, Tier::VeryLowTier } }, - { Instruction::DUP5, { "DUP5", 0, 5, 6, false, Tier::VeryLowTier } }, - { Instruction::DUP6, { "DUP6", 0, 6, 7, false, Tier::VeryLowTier } }, - { Instruction::DUP7, { "DUP7", 0, 7, 8, false, Tier::VeryLowTier } }, - { Instruction::DUP8, { "DUP8", 0, 8, 9, false, Tier::VeryLowTier } }, - { Instruction::DUP9, { "DUP9", 0, 9, 10, false, Tier::VeryLowTier } }, - { Instruction::DUP10, { "DUP10", 0, 10, 11, false, Tier::VeryLowTier } }, - { Instruction::DUP11, { "DUP11", 0, 11, 12, false, Tier::VeryLowTier } }, - { Instruction::DUP12, { "DUP12", 0, 12, 13, false, Tier::VeryLowTier } }, - { Instruction::DUP13, { "DUP13", 0, 13, 14, false, Tier::VeryLowTier } }, - { Instruction::DUP14, { "DUP14", 0, 14, 15, false, Tier::VeryLowTier } }, - { Instruction::DUP15, { "DUP15", 0, 15, 16, false, Tier::VeryLowTier } }, - { Instruction::DUP16, { "DUP16", 0, 16, 17, false, Tier::VeryLowTier } }, - { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, Tier::VeryLowTier } }, - { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, Tier::VeryLowTier } }, - { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, Tier::VeryLowTier } }, - { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, Tier::VeryLowTier } }, - { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, Tier::VeryLowTier } }, - { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, Tier::VeryLowTier } }, - { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, Tier::VeryLowTier } }, - { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, Tier::VeryLowTier } }, - { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, Tier::VeryLowTier } }, - { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, Tier::VeryLowTier } }, - { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, Tier::VeryLowTier } }, - { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, Tier::VeryLowTier } }, - { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, Tier::VeryLowTier } }, - { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, Tier::VeryLowTier } }, - { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, Tier::VeryLowTier } }, - { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, Tier::VeryLowTier } }, - { Instruction::LOG0, { "LOG0", 0, 2, 0, true, Tier::SpecialTier } }, - { Instruction::LOG1, { "LOG1", 0, 3, 0, true, Tier::SpecialTier } }, - { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::SpecialTier } }, - { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::SpecialTier } }, - { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::SpecialTier } }, - { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::SpecialTier } }, - { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::SpecialTier } }, - { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::SpecialTier } }, - { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::ZeroTier } }, - { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, Tier::SpecialTier } }, - { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, Tier::ZeroTier } } + { Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::Zero } }, + { Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::SUB, { "SUB", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::MUL, { "MUL", 0, 2, 1, false, Tier::Low } }, + { Instruction::DIV, { "DIV", 0, 2, 1, false, Tier::Low } }, + { Instruction::SDIV, { "SDIV", 0, 2, 1, false, Tier::Low } }, + { Instruction::MOD, { "MOD", 0, 2, 1, false, Tier::Low } }, + { Instruction::SMOD, { "SMOD", 0, 2, 1, false, Tier::Low } }, + { Instruction::EXP, { "EXP", 0, 2, 1, false, Tier::Special } }, + { Instruction::NOT, { "NOT", 0, 1, 1, false, Tier::VeryLow } }, + { Instruction::LT, { "LT", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::GT, { "GT", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::SLT, { "SLT", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::SGT, { "SGT", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::EQ, { "EQ", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, Tier::VeryLow } }, + { Instruction::AND, { "AND", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::OR, { "OR", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::XOR, { "XOR", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::BYTE, { "BYTE", 0, 2, 1, false, Tier::VeryLow } }, + { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::Mid } }, + { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } }, + { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } }, + { Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::Special } }, + { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } }, + { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Ext } }, + { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } }, + { Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::Base } }, + { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::Base } }, + { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, Tier::VeryLow } }, + { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, Tier::Base } }, + { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, Tier::VeryLow } }, + { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::Base } }, + { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } }, + { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } }, + { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::Ext } }, + { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::Ext } }, + { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } }, + { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } }, + { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } }, + { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } }, + { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } }, + { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } }, + { Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } }, + { Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, Tier::VeryLow } }, + { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } }, + { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, Tier::VeryLow } }, + { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, Tier::Special } }, + { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, Tier::Special } }, + { Instruction::JUMP, { "JUMP", 0, 1, 0, true, Tier::Mid } }, + { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, Tier::High } }, + { Instruction::PC, { "PC", 0, 0, 1, false, Tier::Base } }, + { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, Tier::Base } }, + { Instruction::GAS, { "GAS", 0, 0, 1, false, Tier::Base } }, + { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, Tier::Special } }, + { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, Tier::VeryLow } }, + { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, Tier::VeryLow } }, + { Instruction::DUP1, { "DUP1", 0, 1, 2, false, Tier::VeryLow } }, + { Instruction::DUP2, { "DUP2", 0, 2, 3, false, Tier::VeryLow } }, + { Instruction::DUP3, { "DUP3", 0, 3, 4, false, Tier::VeryLow } }, + { Instruction::DUP4, { "DUP4", 0, 4, 5, false, Tier::VeryLow } }, + { Instruction::DUP5, { "DUP5", 0, 5, 6, false, Tier::VeryLow } }, + { Instruction::DUP6, { "DUP6", 0, 6, 7, false, Tier::VeryLow } }, + { Instruction::DUP7, { "DUP7", 0, 7, 8, false, Tier::VeryLow } }, + { Instruction::DUP8, { "DUP8", 0, 8, 9, false, Tier::VeryLow } }, + { Instruction::DUP9, { "DUP9", 0, 9, 10, false, Tier::VeryLow } }, + { Instruction::DUP10, { "DUP10", 0, 10, 11, false, Tier::VeryLow } }, + { Instruction::DUP11, { "DUP11", 0, 11, 12, false, Tier::VeryLow } }, + { Instruction::DUP12, { "DUP12", 0, 12, 13, false, Tier::VeryLow } }, + { Instruction::DUP13, { "DUP13", 0, 13, 14, false, Tier::VeryLow } }, + { Instruction::DUP14, { "DUP14", 0, 14, 15, false, Tier::VeryLow } }, + { Instruction::DUP15, { "DUP15", 0, 15, 16, false, Tier::VeryLow } }, + { Instruction::DUP16, { "DUP16", 0, 16, 17, false, Tier::VeryLow } }, + { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, Tier::VeryLow } }, + { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, Tier::VeryLow } }, + { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, Tier::VeryLow } }, + { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, Tier::VeryLow } }, + { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, Tier::VeryLow } }, + { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, Tier::VeryLow } }, + { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, Tier::VeryLow } }, + { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, Tier::VeryLow } }, + { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, Tier::VeryLow } }, + { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, Tier::VeryLow } }, + { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, Tier::VeryLow } }, + { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, Tier::VeryLow } }, + { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, Tier::VeryLow } }, + { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, Tier::VeryLow } }, + { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, Tier::VeryLow } }, + { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, Tier::VeryLow } }, + { Instruction::LOG0, { "LOG0", 0, 2, 0, true, Tier::Special } }, + { Instruction::LOG1, { "LOG1", 0, 3, 0, true, Tier::Special } }, + { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } }, + { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } }, + { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } }, + { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } }, + { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } }, + { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, + { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, + { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, + { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, Tier::Zero } } }; void dev::solidity::eachInstruction( @@ -343,7 +343,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst) } catch (...) { - return InstructionInfo({"", 0, 0, 0, false, Tier::InvalidTier}); + return InstructionInfo({"", 0, 0, 0, false, Tier::Invalid}); } } diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 4ebaeb987..2dd451cdf 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -225,17 +225,17 @@ inline Instruction logInstruction(unsigned _number) return Instruction(unsigned(Instruction::LOG0) + _number); } -enum class Tier : int +enum class Tier : unsigned { - ZeroTier = 0, // 0, Zero - BaseTier, // 2, Quick - VeryLowTier, // 3, Fastest - LowTier, // 5, Fast - MidTier, // 8, Mid - HighTier, // 10, Slow - ExtTier, // 20, Ext - SpecialTier, // multiparam or otherwise special - InvalidTier // Invalid. + Zero = 0, // 0, Zero + Base, // 2, Quick + VeryLow, // 3, Fastest + Low, // 5, Fast + Mid, // 8, Mid + High, // 10, Slow + Ext, // 20, Ext + Special, // multiparam or otherwise special + Invalid // Invalid. }; /// Information structure for a particular instruction. From d3a391c13651569a3154a84dcd62fecf69fc074c Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 18 Jan 2017 17:41:36 +0100 Subject: [PATCH 085/414] Provide fallback for linking. --- Changelog.md | 4 +--- libevmasm/LinkerObject.cpp | 29 ++++++++++++++++++----- libevmasm/LinkerObject.h | 6 +++++ test/libsolidity/SolidityEndToEndTest.cpp | 24 +++++++++---------- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/Changelog.md b/Changelog.md index 1d3c47c12..1e5dd8e1c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.9 (unreleased) Features: + * Compiler Interface: Contracts and libraries can be referenced with a `file:` prefix to make them unique. * AST: Use deterministic node identifiers. * Metadata: Do not include platform in the version number. @@ -16,9 +17,6 @@ Bugfixes: * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``. * Type checker, disallow inheritances of different kinds (e.g. a function and a modifier) of members of the same name -Features: - * Contracts and libraries are now unique to their source files, rather than globally. - ### 0.4.7 (2016-12-15) Features: diff --git a/libevmasm/LinkerObject.cpp b/libevmasm/LinkerObject.cpp index 93e4067c9..06607089e 100644 --- a/libevmasm/LinkerObject.cpp +++ b/libevmasm/LinkerObject.cpp @@ -37,13 +37,10 @@ void LinkerObject::link(map const& _libraryAddresses) { std::map remainingRefs; for (auto const& linkRef: linkReferences) - { - auto it = _libraryAddresses.find(linkRef.second); - if (it == _libraryAddresses.end()) - remainingRefs.insert(linkRef); + if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses)) + address->ref().copyTo(ref(bytecode).cropped(linkRef.first, 20)); else - it->second.ref().copyTo(ref(bytecode).cropped(linkRef.first, 20)); - } + remainingRefs.insert(linkRef); linkReferences.swap(remainingRefs); } @@ -60,3 +57,23 @@ string LinkerObject::toHex() const } return hex; } + +h160 const* +LinkerObject::matchLibrary( + string const& _linkRefName, + map const& _libraryAddresses +) +{ + auto it = _libraryAddresses.find(_linkRefName); + if (it != _libraryAddresses.end()) + return &it->second; + // If the user did not supply a fully qualified library name, + // try to match only the simple libary name + size_t colon = _linkRefName.find(':'); + if (colon == string::npos) + return nullptr; + it = _libraryAddresses.find(_linkRefName.substr(colon + 1)); + if (it != _libraryAddresses.end()) + return &it->second; + return nullptr; +} diff --git a/libevmasm/LinkerObject.h b/libevmasm/LinkerObject.h index d3ec3e972..152487b44 100644 --- a/libevmasm/LinkerObject.h +++ b/libevmasm/LinkerObject.h @@ -49,6 +49,12 @@ struct LinkerObject /// @returns a hex representation of the bytecode of the given object, replacing unlinked /// addresses by placeholders. std::string toHex() const; + +private: + static h160 const* matchLibrary( + std::string const& _linkRefName, + std::map const& _libraryAddresses + ); }; } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 03aa26999..191618317 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3192,7 +3192,7 @@ BOOST_AUTO_TEST_CASE(library_call_in_homestead) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs()); BOOST_CHECK(callContractFunction("sender()") == encodeArgs(u160(m_sender))); } @@ -6191,7 +6191,7 @@ BOOST_AUTO_TEST_CASE(library_call) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(33)) == encodeArgs(u256(33) * 9)); } @@ -6208,7 +6208,7 @@ BOOST_AUTO_TEST_CASE(library_stray_values) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(33)) == encodeArgs(u256(42))); } @@ -6341,7 +6341,7 @@ BOOST_AUTO_TEST_CASE(internal_types_in_library) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(4), u256(17))); } @@ -6368,7 +6368,7 @@ BOOST_AUTO_TEST_CASE(using_library_structs) } )"; compileAndRun(sourceCode, 0, "Lib"); - compileAndRun(sourceCode, 0, "Test", bytes(), map{{":Lib", m_contractAddress}}); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{"Lib", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7), u256(8))); } @@ -6902,7 +6902,7 @@ BOOST_AUTO_TEST_CASE(using_for_function_on_int) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(9)) == encodeArgs(u256(2 * 9))); } @@ -6920,7 +6920,7 @@ BOOST_AUTO_TEST_CASE(using_for_function_on_struct) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(3 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(3 * 7))); } @@ -6943,7 +6943,7 @@ BOOST_AUTO_TEST_CASE(using_for_overload) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(6 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(6 * 7))); } @@ -6962,7 +6962,7 @@ BOOST_AUTO_TEST_CASE(using_for_by_name) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(6 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(6 * 7))); } @@ -6982,7 +6982,7 @@ BOOST_AUTO_TEST_CASE(bound_function_in_var) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f(uint256)", u256(7)) == encodeArgs(u256(6 * 7))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(6 * 7))); } @@ -7005,7 +7005,7 @@ BOOST_AUTO_TEST_CASE(bound_function_to_string) } )"; compileAndRun(sourceCode, 0, "D"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{":D", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"D", m_contractAddress}}); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(3))); BOOST_CHECK(callContractFunction("g()") == encodeArgs(u256(3))); } @@ -7751,7 +7751,7 @@ BOOST_AUTO_TEST_CASE(payable_function_calls_library) } )"; compileAndRun(sourceCode, 0, "L"); - compileAndRun(sourceCode, 0, "C", bytes(), map{{":L", m_contractAddress}}); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"L", m_contractAddress}}); BOOST_CHECK(callContractFunctionWithValue("f()", 27) == encodeArgs(u256(7))); } From 23a654ade8bb20fc2768e88b77059a14dce21635 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 19 Jan 2017 11:10:09 +0100 Subject: [PATCH 086/414] Fix default function type name visibility. --- libsolidity/analysis/ReferencesResolver.cpp | 1 - libsolidity/ast/AST.h | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 66bf1d0e1..df579c3d9 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -87,7 +87,6 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) { switch (_typeName.visibility()) { - case VariableDeclaration::Visibility::Default: case VariableDeclaration::Visibility::Internal: case VariableDeclaration::Visibility::External: break; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 4af649634..6e81892b4 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -867,7 +867,10 @@ public: std::vector> const& parameterTypes() const { return m_parameterTypes->parameters(); } std::vector> const& returnParameterTypes() const { return m_returnTypes->parameters(); } - Declaration::Visibility visibility() const { return m_visibility; } + Declaration::Visibility visibility() const + { + return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility; + } bool isDeclaredConst() const { return m_isDeclaredConst; } bool isPayable() const { return m_isPayable; } From 3fed790a56cea7bae481d01c15949aa500823968 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 17 Jan 2017 10:47:44 +0100 Subject: [PATCH 087/414] Type identifiers. --- Changelog.md | 1 + libsolidity/ast/Types.cpp | 189 ++++++++++++++++++++++++++++- libsolidity/ast/Types.h | 24 ++++ test/libsolidity/SolidityTypes.cpp | 66 ++++++++++ 4 files changed, 277 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index 1e5dd8e1c..b9e4ecc03 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ Features: * Compiler Interface: Contracts and libraries can be referenced with a `file:` prefix to make them unique. * AST: Use deterministic node identifiers. + * Type system: Introduce type identifier strings. * Metadata: Do not include platform in the version number. ### 0.4.8 (2017-01-13) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 03ff84718..9e7224741 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -272,7 +272,15 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): solAssert( m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0, "Invalid bit number for integer type: " + dev::toString(_bits) - ); + ); +} + +string IntegerType::identifier() const +{ + if (isAddress()) + return "t_address"; + else + return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits()); } bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -412,7 +420,12 @@ FixedPointType::FixedPointType(int _integerBits, int _fractionalBits, FixedPoint m_fractionalBits % 8 == 0, "Invalid bit number(s) for fixed type: " + dev::toString(_integerBits) + "x" + dev::toString(_fractionalBits) - ); + ); +} + +string FixedPointType::identifier() const +{ + return "t_" + string(isSigned() ? "" : "u") + "fixed" + std::to_string(integerBits()) + "x" + std::to_string(fractionalBits()); } bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -770,6 +783,11 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ } } +string RationalNumberType::identifier() const +{ + return "t_rational_" + m_value.numerator().str() + "_by_" + m_value.denominator().str(); +} + bool RationalNumberType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -909,6 +927,13 @@ bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } +string StringLiteralType::identifier() const +{ + // Since we have to return a valid identifier and the string itself may contain + // anything, we hash it. + return "t_stringliteral_" + toHex(keccak256(m_value).asBytes()); +} + bool StringLiteralType::operator==(const Type& _other) const { if (_other.category() != category()) @@ -1002,6 +1027,11 @@ MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) c return MemberList::MemberMap{MemberList::Member{"length", make_shared(8)}}; } +string FixedBytesType::identifier() const +{ + return "t_bytes" + std::to_string(m_bytes); +} + bool FixedBytesType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1115,6 +1145,20 @@ string ReferenceType::stringForReferencePart() const return ""; } +string ReferenceType::identifierLocationSuffix() const +{ + string id; + if (location() == DataLocation::Storage) + id += "_storage"; + else if (location() == DataLocation::Memory) + id += "_memory"; + else + id += "_calldata"; + if (isPointer()) + id += "_ptr"; + return id; +} + bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const { if (_convertTo.category() != category()) @@ -1170,6 +1214,26 @@ bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const return true; } +string ArrayType::identifier() const +{ + string id; + if (isString()) + id += "t_string"; + else if (isByteArray()) + id += "t_bytes"; + else + { + id = baseType()->identifier(); + if (isDynamicallySized()) + id += "_arraydyn"; + else + id += string("_array") + length().str(); + } + id += identifierLocationSuffix(); + + return id; +} + bool ArrayType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1184,7 +1248,7 @@ bool ArrayType::operator==(Type const& _other) const return false; if (*other.baseType() != *baseType()) return false; - return isDynamicallySized() || length() == other.length(); + return isDynamicallySized() || length() == other.length(); } unsigned ArrayType::calldataEncodedSize(bool _padded) const @@ -1356,6 +1420,11 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) return copy; } +string ContractType::identifier() const +{ + return (m_super ? "t_super_" : "t_contract_") + m_contract.name() + "_" + std::to_string(m_contract.id()); +} + bool ContractType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1465,6 +1534,11 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const return this->m_struct == convertTo.m_struct; } +string StructType::identifier() const +{ + return "t_struct_" + m_struct.name() + "_" + std::to_string(m_struct.id()) + identifierLocationSuffix(); +} + bool StructType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1605,6 +1679,11 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const return _operator == Token::Delete ? make_shared() : TypePointer(); } +string EnumType::identifier() const +{ + return "t_enum_" + m_enum.name() + "_" + std::to_string(m_enum.id()); +} + bool EnumType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1686,6 +1765,18 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const return false; } +string TupleType::identifier() const +{ + string id = "t_tuple" + std::to_string(components().size()) + "_"; + for (auto const& c: components()) + if (c) + id += c->identifier() + "_"; + else + id += "t_empty_"; + id += "tuple_end"; + return id; +} + bool TupleType::operator==(Type const& _other) const { if (auto tupleType = dynamic_cast(&_other)) @@ -1934,6 +2025,59 @@ TypePointers FunctionType::parameterTypes() const return TypePointers(m_parameterTypes.cbegin() + 1, m_parameterTypes.cend()); } +string FunctionType::identifier() const +{ + string id = "t_function_"; + switch (location()) + { + case Location::Internal: id += "internal"; break; + case Location::External: id += "external"; break; + case Location::CallCode: id += "callcode"; break; + case Location::DelegateCall: id += "delegatecall"; break; + case Location::Bare: id += "bare"; break; + case Location::BareCallCode: id += "barecallcode"; break; + case Location::BareDelegateCall: id += "baredelegatecall"; break; + case Location::Creation: id += "creation"; break; + case Location::Send: id += "send"; break; + case Location::SHA3: id += "sha3"; break; + case Location::Selfdestruct: id += "selfdestruct"; break; + case Location::ECRecover: id += "ecrecover"; break; + case Location::SHA256: id += "sha256"; break; + case Location::RIPEMD160: id += "ripemd160"; break; + case Location::Log0: id += "log0"; break; + case Location::Log1: id += "log1"; break; + case Location::Log2: id += "log2"; break; + case Location::Log3: id += "log3"; break; + case Location::Log4: id += "log4"; break; + case Location::Event: id += "event"; break; + case Location::SetGas: id += "setgas"; break; + case Location::SetValue: id += "setvalue"; break; + case Location::BlockHash: id += "blockhash"; break; + case Location::AddMod: id += "addmod"; break; + case Location::MulMod: id += "mulmod"; break; + case Location::ArrayPush: id += "arraypush"; break; + case Location::ByteArrayPush: id += "bytearraypush"; break; + case Location::ObjectCreation: id += "objectcreation"; break; + default: solAssert(false, "Unknown function location."); break; + } + if (isConstant()) + id += "_constant"; + id += "_param" + std::to_string(m_parameterTypes.size()) + "_"; + for (auto const& p: m_parameterTypes) + id += p->identifier() + "_"; + id += "return" + std::to_string(m_returnParameterTypes.size()) + "_"; + for (auto const& r: m_returnParameterTypes) + id += r->identifier() + "_"; + if (m_gasSet) + id += "gas_set_"; + if (m_valueSet) + id += "value_set_"; + if (bound()) + id += "bound_to" + selfType()->identifier() + "_"; + id += "function_end"; + return id; +} + bool FunctionType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2354,6 +2498,11 @@ ASTPointer FunctionType::documentation() const return ASTPointer(); } +string MappingType::identifier() const +{ + return "t_mapping_" + m_keyType->identifier() + "_to_" + m_valueType->identifier() + "_mapping_end"; +} + bool MappingType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2372,6 +2521,11 @@ string MappingType::canonicalName(bool) const return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")"; } +string TypeType::identifier() const +{ + return "t_type_" + actualType()->identifier(); +} + bool TypeType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2456,6 +2610,14 @@ u256 ModifierType::storageSize() const << errinfo_comment("Storage size of non-storable type type requested.")); } +string ModifierType::identifier() const +{ + string id = "t_modifier_param" + std::to_string(m_parameterTypes.size()) + "_"; + for (auto const& p: m_parameterTypes) + id += p->identifier() + "_"; + return id + "end_modifier"; +} + bool ModifierType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2480,6 +2642,11 @@ string ModifierType::toString(bool _short) const return name + ")"; } +string ModuleType::identifier() const +{ + return "t_module_" + std::to_string(m_sourceUnit.id()); +} + bool ModuleType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2501,6 +2668,22 @@ string ModuleType::toString(bool) const return string("module \"") + m_sourceUnit.annotation().path + string("\""); } +string MagicType::identifier() const +{ + switch (m_kind) + { + case Kind::Block: + return "t_magic_block"; + case Kind::Message: + return "t_magic_message"; + case Kind::Transaction: + return "t_magic_transaction"; + default: + solAssert(false, "Unknown kind of magic"); + } + return ""; +} + bool MagicType::operator==(Type const& _other) const { if (_other.category() != category()) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 26e2b8f29..c1f6a476a 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -155,6 +155,10 @@ public: static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); virtual Category category() const = 0; + /// @returns a valid solidity identifier such that two types should compare equal if and + /// only if they have the same identifier. + /// The identifier should start with "t_". + virtual std::string identifier() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const { @@ -288,6 +292,7 @@ public: explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned); + virtual std::string identifier() const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -329,6 +334,7 @@ public: explicit FixedPointType(int _integerBits, int _fractionalBits, Modifier _modifier = Modifier::Unsigned); + virtual std::string identifier() const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -378,6 +384,7 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -416,6 +423,7 @@ public: return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -449,6 +457,7 @@ public: virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; @@ -476,6 +485,7 @@ class BoolType: public Type public: BoolType() {} virtual Category category() const override { return Category::Bool; } + virtual std::string identifier() const override { return "t_bool"; } virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; @@ -533,6 +543,8 @@ protected: TypePointer copyForLocationIfReference(TypePointer const& _type) const; /// @returns a human-readable description of the reference part of the type. std::string stringForReferencePart() const; + /// @returns the suffix computed from the reference part to be used by identifier(); + std::string identifierLocationSuffix() const; DataLocation m_location = DataLocation::Storage; bool m_isPointer = true; @@ -573,6 +585,7 @@ public: virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual std::string identifier() const override; virtual bool operator==(const Type& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool isDynamicallySized() const override { return m_hasDynamicLength; } @@ -622,6 +635,7 @@ public: /// Contracts can be converted to themselves and to integers. virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded ) const override { @@ -677,6 +691,7 @@ public: explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage): ReferenceType(_location), m_struct(_struct) {} virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; u256 memorySize() const; @@ -720,6 +735,7 @@ public: virtual Category category() const override { return Category::Enum; } explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {} virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override { @@ -760,6 +776,7 @@ public: virtual Category category() const override { return Category::Tuple; } explicit TupleType(std::vector const& _types = std::vector()): m_components(_types) {} virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString(bool) const override; @@ -897,6 +914,7 @@ public: /// @returns the "self" parameter type for a bound function TypePointer selfType() const; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual std::string canonicalName(bool /*_addDataLocation*/) const override; @@ -995,6 +1013,7 @@ public: MappingType(TypePointer const& _keyType, TypePointer const& _valueType): m_keyType(_keyType), m_valueType(_valueType) {} + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; virtual std::string canonicalName(bool _addDataLocation) const override; @@ -1029,6 +1048,7 @@ public: TypePointer const& actualType() const { return m_actualType; } virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual u256 storageSize() const override; @@ -1056,6 +1076,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override { return 0; } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; @@ -1080,6 +1101,7 @@ public: return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } @@ -1109,6 +1131,7 @@ public: return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } @@ -1132,6 +1155,7 @@ class InaccessibleDynamicType: public Type public: virtual Category category() const override { return Category::InaccessibleDynamic; } + virtual std::string identifier() const override { return "t_inaccessible"; } virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; } virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; } virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index dc3143c87..1e7bcc7bb 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -21,6 +21,8 @@ */ #include +#include +#include #include using namespace std; @@ -86,6 +88,70 @@ BOOST_AUTO_TEST_CASE(storage_layout_arrays) BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(32), 9).storageSize() == 9); } +BOOST_AUTO_TEST_CASE(type_identifiers) +{ + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint128")->identifier(), "t_uint128"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("int128")->identifier(), "t_int128"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("address")->identifier(), "t_address"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint8")->identifier(), "t_uint8"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("ufixed8x64")->identifier(), "t_ufixed8x64"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("fixed128x8")->identifier(), "t_fixed128x8"); + BOOST_CHECK_EQUAL(RationalNumberType(rational(7, 1)).identifier(), "t_rational_7_by_1"); + BOOST_CHECK_EQUAL(RationalNumberType(rational(200, 77)).identifier(), "t_rational_200_by_77"); + BOOST_CHECK_EQUAL(RationalNumberType(rational(2 * 200, 2 * 77)).identifier(), "t_rational_200_by_77"); + BOOST_CHECK_EQUAL( + StringLiteralType(Literal(SourceLocation{}, Token::StringLiteral, make_shared("abc - def"))).identifier(), + "t_stringliteral_196a9142ee0d40e274a6482393c762b16dd8315713207365e1e13d8d85b74fc4" + ); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes8")->identifier(), "t_bytes8"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes32")->identifier(), "t_bytes32"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bool")->identifier(), "t_bool"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes")->identifier(), "t_bytes_storage_ptr"); + BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string")->identifier(), "t_string_storage_ptr"); + ArrayType largeintArray(DataLocation::Memory, Type::fromElementaryTypeName("int128"), u256("2535301200456458802993406410752")); + BOOST_CHECK_EQUAL(largeintArray.identifier(), "t_int128_array2535301200456458802993406410752_memory_ptr"); + TypePointer stringArray = make_shared(DataLocation::Storage, Type::fromElementaryTypeName("string"), u256("20")); + TypePointer multiArray = make_shared(DataLocation::Storage, stringArray); + BOOST_CHECK_EQUAL(multiArray->identifier(), "t_string_storage_array20_storage_arraydyn_storage_ptr"); + + ContractDefinition c(SourceLocation{}, make_shared("MyContract"), {}, {}, {}, false); + BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type_t_contract_MyContract_2"); + BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super_MyContract_2"); + + StructDefinition s({}, make_shared("Struct"), {}); + BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type_t_struct_Struct_3_storage_ptr"); + + EnumDefinition e({}, make_shared("Enum"), {}); + BOOST_CHECK_EQUAL(e.type()->identifier(), "t_type_t_enum_Enum_4"); + + TupleType t({e.type(), s.type(), stringArray, nullptr}); + BOOST_CHECK_EQUAL(t.identifier(), "t_tuple4_t_type_t_enum_Enum_4_t_type_t_struct_Struct_3_storage_ptr_t_string_storage_array20_storage_ptr_t_empty_tuple_end"); + + TypePointer sha3fun = make_shared(strings{}, strings{}, FunctionType::Location::SHA3); + BOOST_CHECK_EQUAL(sha3fun->identifier(), "t_function_sha3_param0_return0_function_end"); + + FunctionType metaFun(TypePointers{sha3fun}, TypePointers{s.type()}); + BOOST_CHECK_EQUAL(metaFun.identifier(), "t_function_internal_param1_t_function_sha3_param0_return0_function_end_return1_t_type_t_struct_Struct_3_storage_ptr_function_end"); + + TypePointer m = make_shared(Type::fromElementaryTypeName("bytes32"), s.type()); + MappingType m2(Type::fromElementaryTypeName("uint64"), m); + BOOST_CHECK_EQUAL(m2.identifier(), "t_mapping_t_uint64_to_t_mapping_t_bytes32_to_t_type_t_struct_Struct_3_storage_ptr_mapping_end_mapping_end"); + + // TypeType is tested with contract + + auto emptyParams = make_shared(SourceLocation(), std::vector>()); + ModifierDefinition mod(SourceLocation{}, make_shared("modif"), {}, emptyParams, {}); + BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier_param0_end_modifier"); + + SourceUnit su({}, {}); + BOOST_CHECK_EQUAL(ModuleType(su).identifier(), "t_module_7"); + BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Block).identifier(), "t_magic_block"); + BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Message).identifier(), "t_magic_message"); + BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Transaction).identifier(), "t_magic_transaction"); + + BOOST_CHECK_EQUAL(InaccessibleDynamicType().identifier(), "t_inaccessible"); +} + BOOST_AUTO_TEST_SUITE_END() } From da178d967fb66ca508d16bbe3feecf1962dcf6ef Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 19 Jan 2017 13:18:56 +0100 Subject: [PATCH 088/414] Properly escape user strings and lists. --- libsolidity/ast/Types.cpp | 108 +++++++++++++++++++---------- libsolidity/ast/Types.h | 22 +++--- test/libsolidity/SolidityTypes.cpp | 24 +++---- 3 files changed, 98 insertions(+), 56 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 9e7224741..6e9e9d7e2 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -21,15 +21,21 @@ */ #include -#include -#include -#include + +#include +#include + #include #include #include #include -#include -#include + +#include +#include +#include +#include + +#include using namespace std; using namespace dev; @@ -117,6 +123,51 @@ u256 const& MemberList::storageSize() const return m_storageOffsets->storageSize(); } +/// Helper functions for type identifier +namespace +{ + +string parenthesizeIdentifier(string const& _internal) +{ + return "$_" + _internal + "_$"; +} + +template +string identifierList(Range const&& _list) +{ + return parenthesizeIdentifier(boost::algorithm::join(_list, "_$_")); +} + +string identifier(TypePointer const& _type) +{ + return _type ? _type->identifier() : ""; +} + +string identifierList(vector const& _list) +{ + return identifierList(_list | boost::adaptors::transformed(identifier)); +} + +string identifierList(TypePointer const& _type) +{ + return parenthesizeIdentifier(identifier(_type)); +} + +string identifierList(TypePointer const& _type1, TypePointer const& _type2) +{ + TypePointers list; + list.push_back(_type1); + list.push_back(_type2); + return identifierList(list); +} + +string parenthesizeUserIdentifier(string const& _internal) +{ + return parenthesizeIdentifier(boost::replace_all_copy(_internal, "$", "$$$")); +} + +} + TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) { solAssert(Token::isElementaryTypeName(_type.token()), @@ -1218,16 +1269,17 @@ string ArrayType::identifier() const { string id; if (isString()) - id += "t_string"; + id = "t_string"; else if (isByteArray()) - id += "t_bytes"; + id = "t_bytes"; else { - id = baseType()->identifier(); + id = "t_array"; + id += identifierList(baseType()); if (isDynamicallySized()) - id += "_arraydyn"; + id += "dyn"; else - id += string("_array") + length().str(); + id += length().str(); } id += identifierLocationSuffix(); @@ -1422,7 +1474,7 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) string ContractType::identifier() const { - return (m_super ? "t_super_" : "t_contract_") + m_contract.name() + "_" + std::to_string(m_contract.id()); + return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id()); } bool ContractType::operator==(Type const& _other) const @@ -1536,7 +1588,7 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const string StructType::identifier() const { - return "t_struct_" + m_struct.name() + "_" + std::to_string(m_struct.id()) + identifierLocationSuffix(); + return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()) + identifierLocationSuffix(); } bool StructType::operator==(Type const& _other) const @@ -1681,7 +1733,7 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const string EnumType::identifier() const { - return "t_enum_" + m_enum.name() + "_" + std::to_string(m_enum.id()); + return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id()); } bool EnumType::operator==(Type const& _other) const @@ -1767,14 +1819,7 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const string TupleType::identifier() const { - string id = "t_tuple" + std::to_string(components().size()) + "_"; - for (auto const& c: components()) - if (c) - id += c->identifier() + "_"; - else - id += "t_empty_"; - id += "tuple_end"; - return id; + return "t_tuple" + identifierList(components()); } bool TupleType::operator==(Type const& _other) const @@ -2062,19 +2107,13 @@ string FunctionType::identifier() const } if (isConstant()) id += "_constant"; - id += "_param" + std::to_string(m_parameterTypes.size()) + "_"; - for (auto const& p: m_parameterTypes) - id += p->identifier() + "_"; - id += "return" + std::to_string(m_returnParameterTypes.size()) + "_"; - for (auto const& r: m_returnParameterTypes) - id += r->identifier() + "_"; + id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); if (m_gasSet) id += "gas_set_"; if (m_valueSet) id += "value_set_"; if (bound()) - id += "bound_to" + selfType()->identifier() + "_"; - id += "function_end"; + id += "bound_to" + identifierList(selfType()); return id; } @@ -2482,7 +2521,7 @@ vector const FunctionType::returnParameterTypeNames(bool _addDataLocatio return names; } -TypePointer FunctionType::selfType() const +TypePointer const& FunctionType::selfType() const { solAssert(bound(), "Function is not bound."); solAssert(m_parameterTypes.size() > 0, "Function has no self type."); @@ -2500,7 +2539,7 @@ ASTPointer FunctionType::documentation() const string MappingType::identifier() const { - return "t_mapping_" + m_keyType->identifier() + "_to_" + m_valueType->identifier() + "_mapping_end"; + return "t_mapping" + identifierList(m_keyType, m_valueType); } bool MappingType::operator==(Type const& _other) const @@ -2523,7 +2562,7 @@ string MappingType::canonicalName(bool) const string TypeType::identifier() const { - return "t_type_" + actualType()->identifier(); + return "t_type" + identifierList(actualType()); } bool TypeType::operator==(Type const& _other) const @@ -2612,10 +2651,7 @@ u256 ModifierType::storageSize() const string ModifierType::identifier() const { - string id = "t_modifier_param" + std::to_string(m_parameterTypes.size()) + "_"; - for (auto const& p: m_parameterTypes) - id += p->identifier() + "_"; - return id + "end_modifier"; + return "t_modifier" + identifierList(m_parameterTypes); } bool ModifierType::operator==(Type const& _other) const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c1f6a476a..1e94631e0 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -22,18 +22,21 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include #include #include #include + +#include +#include #include +#include +#include + +#include +#include +#include + namespace dev { namespace solidity @@ -158,6 +161,9 @@ public: /// @returns a valid solidity identifier such that two types should compare equal if and /// only if they have the same identifier. /// The identifier should start with "t_". + /// More complex identifier strings use "parentheses", where $_ is interpreted as as + /// "opening parenthesis", _$ as "closing parenthesis", _$_ as "comma" and any $ that + /// appears as part of a user-supplied identifier is escaped as _$$$_. virtual std::string identifier() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -912,7 +918,7 @@ public: std::vector const& returnParameterNames() const { return m_returnParameterNames; } std::vector const returnParameterTypeNames(bool _addDataLocation) const; /// @returns the "self" parameter type for a bound function - TypePointer selfType() const; + TypePointer const& selfType() const; virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 1e7bcc7bb..b4c068731 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -109,39 +109,39 @@ BOOST_AUTO_TEST_CASE(type_identifiers) BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes")->identifier(), "t_bytes_storage_ptr"); BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string")->identifier(), "t_string_storage_ptr"); ArrayType largeintArray(DataLocation::Memory, Type::fromElementaryTypeName("int128"), u256("2535301200456458802993406410752")); - BOOST_CHECK_EQUAL(largeintArray.identifier(), "t_int128_array2535301200456458802993406410752_memory_ptr"); + BOOST_CHECK_EQUAL(largeintArray.identifier(), "t_array$_t_int128_$2535301200456458802993406410752_memory_ptr"); TypePointer stringArray = make_shared(DataLocation::Storage, Type::fromElementaryTypeName("string"), u256("20")); TypePointer multiArray = make_shared(DataLocation::Storage, stringArray); - BOOST_CHECK_EQUAL(multiArray->identifier(), "t_string_storage_array20_storage_arraydyn_storage_ptr"); + BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr"); - ContractDefinition c(SourceLocation{}, make_shared("MyContract"), {}, {}, {}, false); - BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type_t_contract_MyContract_2"); - BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super_MyContract_2"); + ContractDefinition c(SourceLocation{}, make_shared("MyContract$"), {}, {}, {}, false); + BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$"); + BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2"); StructDefinition s({}, make_shared("Struct"), {}); - BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type_t_struct_Struct_3_storage_ptr"); + BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type$_t_struct$_Struct_$3_storage_ptr_$"); EnumDefinition e({}, make_shared("Enum"), {}); - BOOST_CHECK_EQUAL(e.type()->identifier(), "t_type_t_enum_Enum_4"); + BOOST_CHECK_EQUAL(e.type()->identifier(), "t_type$_t_enum$_Enum_$4_$"); TupleType t({e.type(), s.type(), stringArray, nullptr}); - BOOST_CHECK_EQUAL(t.identifier(), "t_tuple4_t_type_t_enum_Enum_4_t_type_t_struct_Struct_3_storage_ptr_t_string_storage_array20_storage_ptr_t_empty_tuple_end"); + BOOST_CHECK_EQUAL(t.identifier(), "t_tuple$_t_type$_t_enum$_Enum_$4_$_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_t_array$_t_string_storage_$20_storage_ptr_$__$"); TypePointer sha3fun = make_shared(strings{}, strings{}, FunctionType::Location::SHA3); - BOOST_CHECK_EQUAL(sha3fun->identifier(), "t_function_sha3_param0_return0_function_end"); + BOOST_CHECK_EQUAL(sha3fun->identifier(), "t_function_sha3$__$returns$__$"); FunctionType metaFun(TypePointers{sha3fun}, TypePointers{s.type()}); - BOOST_CHECK_EQUAL(metaFun.identifier(), "t_function_internal_param1_t_function_sha3_param0_return0_function_end_return1_t_type_t_struct_Struct_3_storage_ptr_function_end"); + BOOST_CHECK_EQUAL(metaFun.identifier(), "t_function_internal$_t_function_sha3$__$returns$__$_$returns$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$"); TypePointer m = make_shared(Type::fromElementaryTypeName("bytes32"), s.type()); MappingType m2(Type::fromElementaryTypeName("uint64"), m); - BOOST_CHECK_EQUAL(m2.identifier(), "t_mapping_t_uint64_to_t_mapping_t_bytes32_to_t_type_t_struct_Struct_3_storage_ptr_mapping_end_mapping_end"); + BOOST_CHECK_EQUAL(m2.identifier(), "t_mapping$_t_uint64_$_t_mapping$_t_bytes32_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_$"); // TypeType is tested with contract auto emptyParams = make_shared(SourceLocation(), std::vector>()); ModifierDefinition mod(SourceLocation{}, make_shared("modif"), {}, emptyParams, {}); - BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier_param0_end_modifier"); + BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier$__$"); SourceUnit su({}, {}); BOOST_CHECK_EQUAL(ModuleType(su).identifier(), "t_module_7"); From 7159944f0fa5d9eb3205cd8a3e1d6ec4f133a4ad Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 11:47:20 +0100 Subject: [PATCH 089/414] Reset AST node IDs between compilation runs. --- libsolidity/ast/AST.cpp | 22 ++++++++++++++++++++-- libsolidity/ast/AST.h | 2 ++ libsolidity/ast/Types.cpp | 7 ++++--- libsolidity/interface/CompilerStack.cpp | 1 + test/libsolidity/SolidityTypes.cpp | 1 + 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index fcd6e38c0..8a43c3f75 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -34,11 +34,24 @@ using namespace std; using namespace dev; using namespace dev::solidity; +class IDDispenser +{ +public: + static size_t next() { return ++instance(); } + static void reset() { instance() = 0; } +private: + static size_t& instance() + { + static IDDispenser dispenser; + return dispenser.id; + } + size_t id = 0; +}; + ASTNode::ASTNode(SourceLocation const& _location): + m_id(IDDispenser::next()), m_location(_location) { - static size_t id = 0; - m_id = ++id; } ASTNode::~ASTNode() @@ -46,6 +59,11 @@ ASTNode::~ASTNode() delete m_annotation; } +void ASTNode::resetID() +{ + IDDispenser::reset(); +} + ASTAnnotation& ASTNode::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 4af649634..116275c34 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -59,6 +59,8 @@ public: /// @returns an identifier of this AST node that is unique for a single compilation run. size_t id() const { return m_id; } + /// Resets the global ID counter. This invalidates all previous IDs. + static void resetID(); virtual void accept(ASTVisitor& _visitor) = 0; virtual void accept(ASTConstVisitor& _visitor) const = 0; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6e9e9d7e2..cefd0603d 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -163,7 +164,7 @@ string identifierList(TypePointer const& _type1, TypePointer const& _type2) string parenthesizeUserIdentifier(string const& _internal) { - return parenthesizeIdentifier(boost::replace_all_copy(_internal, "$", "$$$")); + return parenthesizeIdentifier(boost::algorithm::replace_all_copy(_internal, "$", "$$$")); } } @@ -2109,9 +2110,9 @@ string FunctionType::identifier() const id += "_constant"; id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); if (m_gasSet) - id += "gas_set_"; + id += "gas"; if (m_valueSet) - id += "value_set_"; + id += "value"; if (bound()) id += "bound_to" + identifierList(selfType()); return id; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 61fc77287..85ec0fb1a 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -112,6 +112,7 @@ bool CompilerStack::parse() { //reset m_errors.clear(); + ASTNode::resetID(); m_parseSuccessful = false; if (SemVerVersion{string(VersionString)}.isPrerelease()) diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index b4c068731..2dcb92265 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -90,6 +90,7 @@ BOOST_AUTO_TEST_CASE(storage_layout_arrays) BOOST_AUTO_TEST_CASE(type_identifiers) { + ASTNode::resetID(); BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint128")->identifier(), "t_uint128"); BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("int128")->identifier(), "t_int128"); BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("address")->identifier(), "t_address"); From 07b0a0a56028c5ea9bf3dc4fc3b7fda6e4d97ec9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 15:56:56 +0100 Subject: [PATCH 090/414] Make m_id const. --- libsolidity/ast/AST.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 116275c34..c7c374459 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -99,7 +99,7 @@ public: ///@} protected: - size_t m_id = 0; + size_t const m_id = 0; /// Annotation - is specialised in derived classes, is created upon request (because of polymorphism). mutable ASTAnnotation* m_annotation = nullptr; From 2536bdd6d0dfa1685967fd3106c682e0bcf17021 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 19:01:19 +0100 Subject: [PATCH 091/414] Report source location on "stack too deep" errors. --- Changelog.md | 3 ++- libsolidity/codegen/ContractCompiler.cpp | 9 ++++++++- libsolidity/codegen/ExpressionCompiler.cpp | 7 ++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index b9e4ecc03..5e458bba7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,7 +1,8 @@ ### 0.4.9 (unreleased) Features: - * Compiler Interface: Contracts and libraries can be referenced with a `file:` prefix to make them unique. + * Compiler interface: Contracts and libraries can be referenced with a ``file:`` prefix to make them unique. + * Compiler interface: Report source location for "stack too deep" errors. * AST: Use deterministic node identifiers. * Type system: Introduce type identifier strings. * Metadata: Do not include platform in the version number. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index fa77c1aad..bdff0da43 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -486,7 +486,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) stackLayout.push_back(i); stackLayout += vector(c_localVariablesSize, -1); - solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables."); + if (stackLayout.size() > 17) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_function.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); while (stackLayout.back() != int(stackLayout.size() - 1)) if (stackLayout.back() < 0) { @@ -551,6 +556,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) @@ -591,6 +597,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) if (stackDiff > 16 || stackDiff < 1) BOOST_THROW_EXCEPTION( CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); for (unsigned i = 0; i < size; ++i) { diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 37bd14583..81d3409ea 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -250,7 +250,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) } if (lvalueSize > 0) { - solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables."); + if (itemSize + lvalueSize > 16) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_assignment.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); // value [lvalue_ref] updated_value for (unsigned i = 0; i < itemSize; ++i) m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP; From df4ef74199392e9b29823d68e6a58dabd490e037 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 19:11:22 +0100 Subject: [PATCH 092/414] Add tests for internal constructor. --- test/libsolidity/SolidityEndToEndTest.cpp | 10 +++++++ .../SolidityNameAndTypeResolution.cpp | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 191618317..63a5828e6 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2505,6 +2505,16 @@ BOOST_AUTO_TEST_CASE(constructor_argument_overriding) BOOST_CHECK(callContractFunction("getA()") == encodeArgs(3)); } +BOOST_AUTO_TEST_CASE(internal_constructor) +{ + char const* sourceCode = R"( + contract C { + function C() internal {} + } + )"; + BOOST_CHECK(compileAndRunWithoutCheck(sourceCode, 0, "C").empty()); +} + BOOST_AUTO_TEST_CASE(function_modifier) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 9f6ea2b3b..edb57b0db 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4899,6 +4899,32 @@ BOOST_AUTO_TEST_CASE(assignment_to_constant) CHECK_ERROR(text, TypeError, "Cannot assign to a constant variable."); } +BOOST_AUTO_TEST_CASE(inconstructible_internal_constructor) +{ + char const* text = R"( + contract C { + function C() internal {} + } + contract D { + function f() { var x = new C(); } + } + )"; + CHECK_ERROR(text, TypeError, "Contract with internal constructor cannot be created directly."); +} + +BOOST_AUTO_TEST_CASE(constructible_internal_constructor) +{ + char const* text = R"( + contract C { + function C() internal {} + } + contract D is C { + function D() { } + } + )"; + success(text); +} + BOOST_AUTO_TEST_SUITE_END() } From 0ef460461ac280a9f8086d62d831dc9f7d2f5319 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 19:21:43 +0100 Subject: [PATCH 093/414] Check if constructor is public or not. --- libsolidity/analysis/TypeChecker.cpp | 7 ++++++- libsolidity/ast/ASTAnnotations.h | 2 ++ libsolidity/interface/CompilerStack.cpp | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 67c8ac175..adb3d54f7 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -75,7 +75,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract) checkContractAbstractConstructors(_contract); FunctionDefinition const* function = _contract.constructor(); - if (function) { + if (function) + { + if (!function->isPublic()) + _contract.annotation().hasPublicConstructor = false; if (!function->returnParameters().empty()) typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); if (function->isDeclaredConst()) @@ -1280,6 +1283,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) fatalTypeError(_newExpression.location(), "Identifier is not a contract."); if (!contract->annotation().isFullyImplemented) typeError(_newExpression.location(), "Trying to create an instance of an abstract contract."); + if (!contract->annotation().hasPublicConstructor) + typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); solAssert(!!m_scope, ""); m_scope->annotation().contractDependencies.insert(contract); diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 9c4c3ae8b..61e97a55b 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -80,6 +80,8 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota { /// Whether all functions are implemented. bool isFullyImplemented = true; + /// Whether a public constructor (even the default one) is available. + bool hasPublicConstructor = true; /// List of all (direct and indirect) base contracts in order from derived to /// base, including the contract itself. std::vector linearizedBaseContracts; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 85ec0fb1a..b26bd5011 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -624,7 +624,11 @@ void CompilerStack::compileContract( map& _compiledContracts ) { - if (_compiledContracts.count(&_contract) || !_contract.annotation().isFullyImplemented) + if ( + _compiledContracts.count(&_contract) || + !_contract.annotation().isFullyImplemented || + !_contract.annotation().hasPublicConstructor + ) return; for (auto const* dependency: _contract.annotation().contractDependencies) compileContract(*dependency, _compiledContracts); From 0b01678bc316d4c966f3cb1b72cbddca934f2609 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 19:29:43 +0100 Subject: [PATCH 094/414] Correct identifier regex --- docs/grammar.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/grammar.txt b/docs/grammar.txt index 62b4a0212..a9f328c06 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -114,7 +114,7 @@ NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether' | 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years' HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' -Identifier = [a-zA-Z_] [a-zA-Z_0-9]* +Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ From 61a15bb92efd24b54dfa982d42a600b075ebad38 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 19:48:53 +0100 Subject: [PATCH 095/414] Test that all constructible std contracts produce bytecode. --- scripts/tests.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/tests.sh b/scripts/tests.sh index dfbda7342..21958288b 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -45,6 +45,10 @@ do test -z "$output" -a "$failed" -eq 0 done +echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode..." +output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l) +test "$output" = "3" + # This conditional is only needed because we don't have a working Homebrew # install for `eth` at the time of writing, so we unzip the ZIP file locally # instead. This will go away soon. From c6207e27613f9664c0d79a7c7d91048ec6fc8bfc Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 19:41:25 +0100 Subject: [PATCH 096/414] Make token constructible. --- std/StandardToken.sol | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/std/StandardToken.sol b/std/StandardToken.sol index 4ff1b8f92..f59e04ac7 100644 --- a/std/StandardToken.sol +++ b/std/StandardToken.sol @@ -3,20 +3,28 @@ pragma solidity ^0.4.0; import "./Token.sol"; contract StandardToken is Token { - uint256 public totalSupply; - mapping (address => uint256) public balanceOf; + uint256 supply; + mapping (address => uint256) balance; mapping (address => - mapping (address => uint256)) public allowance; + mapping (address => uint256)) m_allowance; function StandardToken(address _initialOwner, uint256 _supply) { - totalSupply = _supply; - balanceOf[_initialOwner] = _supply; + supply = _supply; + balance[_initialOwner] = _supply; + } + + function balanceOf(address _account) constant returns (uint) { + return balance[_account]; + } + + function totalSupply() constant returns (uint) { + return supply; } function transfer(address _to, uint256 _value) returns (bool success) { - if (balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]) { - balanceOf[msg.sender] -= _value; - balanceOf[_to] += _value; + if (balance[msg.sender] >= _value && balance[_to] + _value >= balance[_to]) { + balance[msg.sender] -= _value; + balance[_to] += _value; Transfer(msg.sender, _to, _value); return true; } else { @@ -25,9 +33,9 @@ contract StandardToken is Token { } function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { - if (allowance[_from][msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]) { - allowance[_from][msg.sender] -= _value; - balanceOf[_to] += _value; + if (m_allowance[_from][msg.sender] >= _value && balance[_to] + _value >= balance[_to]) { + m_allowance[_from][msg.sender] -= _value; + balance[_to] += _value; Transfer(_from, _to, _value); return true; } else { @@ -36,8 +44,12 @@ contract StandardToken is Token { } function approve(address _spender, uint256 _value) returns (bool success) { - allowance[msg.sender][_spender] = _value; + m_allowance[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } + + function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + return m_allowance[_owner][_spender]; + } } From 0897e7bceccf6e83def1f9bd0084bbfb214b7904 Mon Sep 17 00:00:00 2001 From: Sebastien Arbogast Date: Fri, 20 Jan 2017 21:52:36 +0100 Subject: [PATCH 097/414] Fixed typo on contract instance Fixed the name of the variable referencing a contract instance --- docs/control-structures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 6c0b0f279..532dab0c5 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -104,7 +104,7 @@ contract can be called internally. External Function Calls ----------------------- -The expressions ``this.g(8);`` and ``c.g(2);`` (where ``g`` is a contract +The expressions ``this.g(8);`` and ``c.g(2);`` (where ``c`` is a contract instance) are also valid function calls, but this time, the function will be called "externally", via a message call and not directly via jumps. Please note that function calls on ``this`` cannot be used in the constructor, as the From 7ecc8e412d98ceac16cd36d87c9f3def2584cac3 Mon Sep 17 00:00:00 2001 From: Sebastien Arbogast Date: Fri, 20 Jan 2017 22:53:45 +0100 Subject: [PATCH 098/414] Fix the list of reasons for throwing automatically Used aut-enumerated list syntax --- docs/control-structures.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 6c0b0f279..ad49f7a45 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -384,16 +384,16 @@ In the following example, we show how ``throw`` can be used to easily revert an Currently, Solidity automatically generates a runtime exception in the following situations: -1. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). -1. If you access a fixed-length ``bytesN`` at a too large or negative index. -1. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. -1. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). -1. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). -1. If you shift by a negative amount. -1. If you convert a value too big or negative into an enum type. -1. If you perform an external function call targeting a contract that contains no code. -1. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). -1. If your contract receives Ether via a public accessor function. +#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). +#. If you access a fixed-length ``bytesN`` at a too large or negative index. +#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. +#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). +#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). +#. If you shift by a negative amount. +#. If you convert a value too big or negative into an enum type. +#. If you perform an external function call targeting a contract that contains no code. +#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). +#. If your contract receives Ether via a public accessor function. Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. From 318fbb2604c69dc6eef438aad37b12b99586b198 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 23 Jan 2017 14:58:07 +0100 Subject: [PATCH 099/414] Fix transferFrom. --- std/StandardToken.sol | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/std/StandardToken.sol b/std/StandardToken.sol index f59e04ac7..4dad85418 100644 --- a/std/StandardToken.sol +++ b/std/StandardToken.sol @@ -22,19 +22,23 @@ contract StandardToken is Token { } function transfer(address _to, uint256 _value) returns (bool success) { - if (balance[msg.sender] >= _value && balance[_to] + _value >= balance[_to]) { - balance[msg.sender] -= _value; - balance[_to] += _value; - Transfer(msg.sender, _to, _value); + return doTransfer(msg.sender, _to, _value); + } + + function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { + if (m_allowance[_from][msg.sender] >= _value) { + if (doTransfer(_from, _to, _value)) { + m_allowance[_from][msg.sender] -= _value; + } return true; } else { return false; } } - function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { - if (m_allowance[_from][msg.sender] >= _value && balance[_to] + _value >= balance[_to]) { - m_allowance[_from][msg.sender] -= _value; + function doTransfer(address _from, address _to, uint _value) internal returns (bool success) { + if (balance[_from] >= _value && balance[_to] + _value >= balance[_to]) { + balance[_from] -= _value; balance[_to] += _value; Transfer(_from, _to, _value); return true; From 5a56496db9266b0ee97a1ca71bb5e5ca902475af Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 18 Oct 2016 14:51:49 +0200 Subject: [PATCH 100/414] test: Add a test for #1215 using the original example from @pipermerriam --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 9f6ea2b3b..6b9d50a92 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1365,6 +1365,17 @@ BOOST_AUTO_TEST_CASE(anonymous_event_too_many_indexed) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(events_with_same_name) +{ + char const* text = R"( + contract TestIt { + event A(); + event A(uint i); + } + )"; + BOOST_CHECK(success(text)); +} + BOOST_AUTO_TEST_CASE(event_call) { char const* text = R"( From 08015590f23957153e12f06a2bcba6e9246733bc Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 18 Oct 2016 14:52:27 +0200 Subject: [PATCH 101/414] analysis: Allow multiple events of the same name Fixes #1215 --- libsolidity/analysis/DeclarationContainer.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index f8c12c5bd..ac80ab184 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -58,6 +58,13 @@ Declaration const* DeclarationContainer::conflictingDeclaration( return declaration; } } + else if (dynamic_cast(&_declaration)) + { + // check that all other declarations with the same name are events + for (Declaration const* declaration: declarations) + if (!dynamic_cast(declaration)) + return declaration; + } else if (declarations.size() == 1 && declarations.front() == &_declaration) return nullptr; else if (!declarations.empty()) From 0e85e35a7f5f8129672ac02b38771749f4e5d456 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 18 Oct 2016 16:05:16 +0200 Subject: [PATCH 102/414] test: Add an end-to-end test about multiple events of the same name See #1215 --- test/libsolidity/SolidityEndToEndTest.cpp | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 191618317..2411e7ffe 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2792,6 +2792,47 @@ BOOST_AUTO_TEST_CASE(event_access_through_base_name) BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("x()"))); } +BOOST_AUTO_TEST_CASE(events_with_same_name) +{ + char const* sourceCode = R"( + contract ClientReceipt { + event Deposit; + event Deposit(address _addr); + event Deposit(address _addr, uint _amount); + function deposit() { + Deposit(); + } + function deposit(address _addr) { + Deposit(_addr); + } + function deposit(address _addr, uint _amount) { + Deposit(_addr, _amount); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("deposit()"); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data.empty()); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); + + callContractFunction("deposit(0xabcdeabcdeabcdeabcde)"); + BOOST_REQUIRE_EQUAL(m_logs.size(), 2); + BOOST_CHECK_EQUAL(m_logs[1].address, m_contractAddress); + BOOST_CHECK(m_logs[1].data == encodeArgs(0)); + BOOST_REQUIRE_EQUAL(m_logs[1].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[1].topics[0], dev::keccak256(string("Deposit(address)"))); + + callContractFunction("deposit(0xabcdeabcdeabcdeabcde, 100)"); + BOOST_REQUIRE_EQUAL(m_logs.size(), 3); + BOOST_CHECK_EQUAL(m_logs[2].address, m_contractAddress); + BOOST_CHECK(m_logs[2].data == encodeArgs(0,100)); + BOOST_REQUIRE_EQUAL(m_logs[2].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[2].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); +} + BOOST_AUTO_TEST_CASE(event_anonymous) { char const* sourceCode = R"( From 4fd3641ce5f80b54463a5f5c8aade6c5112070a3 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 18 Oct 2016 16:06:46 +0200 Subject: [PATCH 103/414] Changelog: add a point about allowing multiple events that share the same name See #1215 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index b9e4ecc03..d322fccda 100644 --- a/Changelog.md +++ b/Changelog.md @@ -26,6 +26,7 @@ Features: * Code generator: Inject the Swarm hash of a metadata file into the bytecode. * Code generator: Replace expensive memcpy precompile by simple assembly loop. * Optimizer: Some dead code elimination. + * Type checker: Allow multiple events of the same name (but with different arities or argument types) Bugfixes: * Code generator: throw if calling the identity precompile failed during memory (array) copying. From 133d1c05e1ea1ae505e84df4c0660942f614bb3a Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 19 Oct 2016 19:51:12 +0200 Subject: [PATCH 104/414] test: fixing inconsistent usage of end-to-end test framework --- test/libsolidity/SolidityEndToEndTest.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 2411e7ffe..6163790b5 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2761,6 +2761,7 @@ BOOST_AUTO_TEST_CASE(event_no_arguments) } } )"; + compileAndRun(sourceCode); callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); @@ -2810,6 +2811,8 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) } } )"; + u160 const c_loggedAddress = m_contractAddress; + compileAndRun(sourceCode); callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); @@ -2818,17 +2821,17 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); - callContractFunction("deposit(0xabcdeabcdeabcdeabcde)"); + callContractFunction("deposit(address)", c_loggedAddress); BOOST_REQUIRE_EQUAL(m_logs.size(), 2); BOOST_CHECK_EQUAL(m_logs[1].address, m_contractAddress); - BOOST_CHECK(m_logs[1].data == encodeArgs(0)); + BOOST_CHECK(m_logs[1].data == encodeArgs(c_loggedAddress)); BOOST_REQUIRE_EQUAL(m_logs[1].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[1].topics[0], dev::keccak256(string("Deposit(address)"))); - callContractFunction("deposit(0xabcdeabcdeabcdeabcde, 100)"); + callContractFunction("deposit(address, uint256)", c_loggedAddress, u256(100)); BOOST_REQUIRE_EQUAL(m_logs.size(), 3); BOOST_CHECK_EQUAL(m_logs[2].address, m_contractAddress); - BOOST_CHECK(m_logs[2].data == encodeArgs(0,100)); + BOOST_CHECK(m_logs[2].data == encodeArgs(c_loggedAddress, 100)); BOOST_REQUIRE_EQUAL(m_logs[2].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[2].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); } From 4c09e81c3e60ddc43709656f9be4f991360b4108 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 19 Oct 2016 20:16:28 +0200 Subject: [PATCH 105/414] test: check the results of function calls in the test for multiple events of the same name --- test/libsolidity/SolidityEndToEndTest.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 6163790b5..81cedfb0e 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2800,35 +2800,38 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) event Deposit; event Deposit(address _addr); event Deposit(address _addr, uint _amount); - function deposit() { + function deposit() returns (uint) { Deposit(); + return 1; } - function deposit(address _addr) { + function deposit(address _addr) returns (uint) { Deposit(_addr); + return 1; } - function deposit(address _addr, uint _amount) { + function deposit(address _addr, uint _amount) returns (uint) { Deposit(_addr, _amount); + return 1; } } )"; u160 const c_loggedAddress = m_contractAddress; compileAndRun(sourceCode); - callContractFunction("deposit()"); + BOOST_CHECK(callContractFunction("deposit()") == encodeArgs(u256(1))); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); BOOST_CHECK(m_logs[0].data.empty()); BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); - callContractFunction("deposit(address)", c_loggedAddress); + BOOST_CHECK(callContractFunction("deposit(address)", c_loggedAddress) == encodeArgs(u256(1))); BOOST_REQUIRE_EQUAL(m_logs.size(), 2); BOOST_CHECK_EQUAL(m_logs[1].address, m_contractAddress); BOOST_CHECK(m_logs[1].data == encodeArgs(c_loggedAddress)); BOOST_REQUIRE_EQUAL(m_logs[1].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[1].topics[0], dev::keccak256(string("Deposit(address)"))); - callContractFunction("deposit(address, uint256)", c_loggedAddress, u256(100)); + BOOST_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)) == encodeArgs(u256(1))); BOOST_REQUIRE_EQUAL(m_logs.size(), 3); BOOST_CHECK_EQUAL(m_logs[2].address, m_contractAddress); BOOST_CHECK(m_logs[2].data == encodeArgs(c_loggedAddress, 100)); From 846f7dc3ea6591b34deb3e6738292ffd87c06ed3 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Thu, 20 Oct 2016 10:47:15 +0200 Subject: [PATCH 106/414] analysis: Resolve event overloading --- libsolidity/analysis/DeclarationContainer.cpp | 21 ++++++++----------- libsolidity/analysis/NameAndTypeResolver.cpp | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index ac80ab184..04836603e 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -42,28 +42,25 @@ Declaration const* DeclarationContainer::conflictingDeclaration( if (m_invisibleDeclarations.count(*_name)) declarations += m_invisibleDeclarations.at(*_name); - if (dynamic_cast(&_declaration)) + if (dynamic_cast(&_declaration) || + dynamic_cast(&_declaration) + ) { - // check that all other declarations with the same name are functions or a public state variable + // check that all other declarations with the same name are functions or a public state variable or events. + // And then check that the signatures are different. for (Declaration const* declaration: declarations) { - if (dynamic_cast(declaration)) - continue; if (auto variableDeclaration = dynamic_cast(declaration)) { if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic()) continue; return declaration; } - return declaration; - } - } - else if (dynamic_cast(&_declaration)) - { - // check that all other declarations with the same name are events - for (Declaration const* declaration: declarations) - if (!dynamic_cast(declaration)) + if (!dynamic_cast(declaration) && + !dynamic_cast(declaration)) return declaration; + // Or, continue. + } } else if (declarations.size() == 1 && declarations.front() == &_declaration) return nullptr; diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 08323243a..b0a82715d 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -260,8 +260,8 @@ vector NameAndTypeResolver::cleanedDeclarations( for (auto it = _declarations.begin(); it != _declarations.end(); ++it) { solAssert(*it, ""); - // the declaration is functionDefinition or a VariableDeclaration while declarations > 1 - solAssert(dynamic_cast(*it) || dynamic_cast(*it), + // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1 + solAssert(dynamic_cast(*it) || dynamic_cast(*it) || dynamic_cast(*it), "Found overloading involving something not a function or a variable"); shared_ptr functionType { (*it)->functionType(false) }; From 567139486f45f69b22beb846fde7c9b27e1ebce0 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 2 Dec 2016 18:58:50 +0100 Subject: [PATCH 107/414] test: somehow log counting system has changed --- test/libsolidity/SolidityEndToEndTest.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 81cedfb0e..c29ae6fcf 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2825,18 +2825,18 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); BOOST_CHECK(callContractFunction("deposit(address)", c_loggedAddress) == encodeArgs(u256(1))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 2); - BOOST_CHECK_EQUAL(m_logs[1].address, m_contractAddress); - BOOST_CHECK(m_logs[1].data == encodeArgs(c_loggedAddress)); - BOOST_REQUIRE_EQUAL(m_logs[1].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[1].topics[0], dev::keccak256(string("Deposit(address)"))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)"))); BOOST_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)) == encodeArgs(u256(1))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 3); - BOOST_CHECK_EQUAL(m_logs[2].address, m_contractAddress); - BOOST_CHECK(m_logs[2].data == encodeArgs(c_loggedAddress, 100)); - BOOST_REQUIRE_EQUAL(m_logs[2].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[2].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); } BOOST_AUTO_TEST_CASE(event_anonymous) From 1f2bf7e004f6b87aa4a6f4acb36670d780e134a8 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 2 Jan 2017 18:00:32 +0100 Subject: [PATCH 108/414] Changelog: move an item upwards --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index d322fccda..77c0b0b08 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Features: * AST: Use deterministic node identifiers. * Type system: Introduce type identifier strings. * Metadata: Do not include platform in the version number. + * Type checker: Allow multiple events of the same name (but with different arities or argument types) ### 0.4.8 (2017-01-13) @@ -26,7 +27,6 @@ Features: * Code generator: Inject the Swarm hash of a metadata file into the bytecode. * Code generator: Replace expensive memcpy precompile by simple assembly loop. * Optimizer: Some dead code elimination. - * Type checker: Allow multiple events of the same name (but with different arities or argument types) Bugfixes: * Code generator: throw if calling the identity precompile failed during memory (array) copying. From 8dc306d62b1d7ef2816187175958ce4edab0170c Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 20 Jan 2017 14:11:06 +0100 Subject: [PATCH 109/414] test: add a test case about inheriting multiple events of the same name --- test/libsolidity/SolidityEndToEndTest.cpp | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c29ae6fcf..b2b5a25e4 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2839,6 +2839,58 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); } +BOOST_AUTO_TEST_CASE(events_with_same_name_inherited) +{ + char const* sourceCode = R"( + contract A { + event Deposit; + } + + contract B { + event Deposit(address _addr); + } + + contract ClientReceipt is A, B { + event Deposit(address _addr, uint _amount); + function deposit() returns (uint) { + Deposit(); + return 1; + } + function deposit(address _addr) returns (uint) { + Deposit(_addr); + return 1; + } + function deposit(address _addr, uint _amount) returns (uint) { + Deposit(_addr, _amount); + return 1; + } + } + )"; + u160 const c_loggedAddress = m_contractAddress; + + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("deposit()") == encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data.empty()); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); + + BOOST_CHECK(callContractFunction("deposit(address)", c_loggedAddress) == encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)"))); + + BOOST_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)) == encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); +} + BOOST_AUTO_TEST_CASE(event_anonymous) { char const* sourceCode = R"( From 399b7b695a2ffe5ae2b07628860712fc76dfe03c Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 23 Jan 2017 14:51:17 +0100 Subject: [PATCH 110/414] analysis: fix format --- libsolidity/analysis/DeclarationContainer.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index 04836603e..7e8cd2cac 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -42,9 +42,10 @@ Declaration const* DeclarationContainer::conflictingDeclaration( if (m_invisibleDeclarations.count(*_name)) declarations += m_invisibleDeclarations.at(*_name); - if (dynamic_cast(&_declaration) || + if ( + dynamic_cast(&_declaration) || dynamic_cast(&_declaration) - ) + ) { // check that all other declarations with the same name are functions or a public state variable or events. // And then check that the signatures are different. @@ -56,8 +57,10 @@ Declaration const* DeclarationContainer::conflictingDeclaration( continue; return declaration; } - if (!dynamic_cast(declaration) && - !dynamic_cast(declaration)) + if ( + !dynamic_cast(declaration) && + !dynamic_cast(declaration) + ) return declaration; // Or, continue. } From 3d8b56c2a4b3f3f29de7b9804c85a10d077502f8 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 23 Jan 2017 15:24:29 +0100 Subject: [PATCH 111/414] test: add tests about functions and events of the same name --- .../SolidityNameAndTypeResolution.cpp | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 6b9d50a92..39fbc019b 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1387,6 +1387,53 @@ BOOST_AUTO_TEST_CASE(event_call) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(event_function_inheritance_clash) +{ + char const* text = R"( + contract A { + function dup() returns (uint) { + return 1; + } + } + contract B { + event dup(); + } + contract C is A, B { + } + )"; + CHECK_ERROR(text, DeclarationError, "Identifier already declared."); +} + +BOOST_AUTO_TEST_CASE(function_event_inheritance_clash) +{ + char const* text = R"( + contract B { + event dup(); + } + contract A { + function dup() returns (uint) { + return 1; + } + } + contract C is B, A { + } + )"; + CHECK_ERROR(text, DeclarationError, "Identifier already declared."); +} + +BOOST_AUTO_TEST_CASE(function_event_in_contract_clash) +{ + char const* text = R"( + contract A { + event dup(); + function dup() returns (uint) { + return 1; + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Identifier already declared."); +} + BOOST_AUTO_TEST_CASE(event_inheritance) { char const* text = R"( From 4e1fd68b38346ec3e4117dc0454b65f4b236741b Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 23 Jan 2017 15:24:47 +0100 Subject: [PATCH 112/414] analysis: disallow overloading functions with events --- libsolidity/analysis/DeclarationContainer.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index 7e8cd2cac..b33c85685 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -58,7 +58,12 @@ Declaration const* DeclarationContainer::conflictingDeclaration( return declaration; } if ( - !dynamic_cast(declaration) && + dynamic_cast(&_declaration) && + !dynamic_cast(declaration) + ) + return declaration; + if ( + dynamic_cast(&_declaration) && !dynamic_cast(declaration) ) return declaration; From 997f5d751a21263b1f104d547486abca3ee3bff6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 24 Jan 2017 01:09:10 +0100 Subject: [PATCH 113/414] Create functional assembly output, if possible. --- libevmasm/Assembly.cpp | 100 +++++++++++++++++++++++++++++++------ libevmasm/Assembly.h | 1 - libevmasm/AssemblyItem.cpp | 12 ++++- libevmasm/AssemblyItem.h | 4 +- 4 files changed, 97 insertions(+), 20 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 0247593bf..b7859c1f0 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -94,7 +94,10 @@ unsigned Assembly::bytesRequired(unsigned subTagSize) const } } -string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const +namespace +{ + +string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) { if (_location.isEmpty() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0) return ""; @@ -115,27 +118,92 @@ string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocati return cut; } +class Functionalizer +{ +public: + Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes): + m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes) + {} + + void feed(AssemblyItem const& _item) + { + if (!_item.location().isEmpty() && _item.location() != m_location) + { + flush(); + printLocation(); + m_location = _item.location(); + } + if (!( + _item.canBeFunctional() && + _item.returnValues() <= 1 && + _item.arguments() <= int(m_pending.size()) + )) + { + flush(); + m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl; + return; + } + string expression = _item.toAssemblyText(); + if (_item.arguments() > 0) + { + expression += "("; + for (int i = 0; i < _item.arguments(); ++i) + { + expression += m_pending.back(); + m_pending.pop_back(); + if (i + 1 < _item.arguments()) + expression += ", "; + } + expression += ")"; + } + + m_pending.push_back(expression); + if (_item.returnValues() != 1) + flush(); + } + + void flush() + { + for (string const& expression: m_pending) + m_out << m_prefix << " " << expression << endl; + m_pending.clear(); + } + + void printLocation() + { + if (!m_location.sourceName && m_location.isEmpty()) + return; + m_out << m_prefix << " /*"; + if (m_location.sourceName) + m_out << " \"" + *m_location.sourceName + "\""; + if (!m_location.isEmpty()) + m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end); + m_out << " " << locationFromSources(m_sourceCodes, m_location); + m_out << " */" << endl; + } + +private: + strings m_pending; + SourceLocation m_location; + + ostream& m_out; + string const& m_prefix; + StringMap const& m_sourceCodes; +}; + +} + ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const { - for (size_t i = 0; i < m_items.size(); ++i) - { - AssemblyItem const& item = m_items[i]; - if (!item.location().isEmpty() && (i == 0 || m_items[i - 1].location() != item.location())) - { - _out << _prefix << " /*"; - if (item.location().sourceName) - _out << " \"" + *item.location().sourceName + "\""; - if (!item.location().isEmpty()) - _out << ":" << to_string(item.location().start) + ":" + to_string(item.location().end); - _out << " */" << endl; - } - _out << _prefix << (item.type() == Tag ? "" : " ") << item.toAssemblyText() << endl; - } + Functionalizer f(_out, _prefix, _sourceCodes); + + for (auto const& i: m_items) + f.feed(i); + f.flush(); if (!m_data.empty() || !m_subs.empty()) { _out << _prefix << "stop" << endl; - Json::Value data; for (auto const& i: m_data) assertThrow(u256(i.first) < m_subs.size(), AssemblyException, "Data not yet implemented."); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 9e7f9f7bd..528c9e74c 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -118,7 +118,6 @@ protected: /// returns the replaced tags. std::map optimiseInternal(bool _enable, bool _isCreation, size_t _runs); - std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } unsigned bytesRequired(unsigned subTagSize) const; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 6c7d5425e..26d9fded4 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -76,12 +76,20 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const BOOST_THROW_EXCEPTION(InvalidOpcode()); } -int AssemblyItem::deposit() const +int AssemblyItem::arguments() const +{ + if (type() == Operation) + return instructionInfo(instruction()).args; + else + return 0; +} + +int AssemblyItem::returnValues() const { switch (m_type) { case Operation: - return instructionInfo(instruction()).ret - instructionInfo(instruction()).args; + return instructionInfo(instruction()).ret; case Push: case PushString: case PushTag: diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 002b3c874..464368fb5 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -116,7 +116,9 @@ public: /// @returns an upper bound for the number of bytes required by this item, assuming that /// the value of a jump tag takes @a _addressLength bytes. unsigned bytesRequired(unsigned _addressLength) const; - int deposit() const; + int arguments() const; + int returnValues() const; + int deposit() const { return returnValues() - arguments(); } /// @returns true if the assembly item can be used in a functional context. bool canBeFunctional() const; From dea59bfbdc610b65018b613dc20322d98e9aa2b6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 12 Jan 2017 17:56:05 +0100 Subject: [PATCH 114/414] Test for initializing recursive structs. --- test/libsolidity/SolidityEndToEndTest.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index da7adbbf3..e8e5ced1a 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8992,6 +8992,28 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment) compileAndRun(sourceCode, 0, "C2"); } +BOOST_AUTO_TEST_CASE(recursive_structs) +{ + char const* sourceCode = R"( + contract C { + struct S { + S[] x; + } + S sstorage; + function f() returns (uint) { + S memory s; + s.x = new S[](10); + delete s; + sstorage.x.length++; + delete sstorage; + return 1; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1))); +} + BOOST_AUTO_TEST_SUITE_END() } From d0e8d340a53395de4c83e4e1d6ccf4c8eab5889a Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 17 Jan 2017 10:47:57 +0100 Subject: [PATCH 115/414] Low level named functions for CompilerContext. --- libsolidity/codegen/CompilerContext.cpp | 15 +++++++++++++++ libsolidity/codegen/CompilerContext.h | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index c14ab845d..fcd39b333 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -125,6 +125,21 @@ Declaration const* CompilerContext::nextFunctionToCompile() const return m_functionCompilationQueue.nextFunctionToCompile(); } +eth::AssemblyItem const* CompilerContext::lowLevelFunctionEntryPoint(string const& _name) const +{ + auto it = m_lowLevelFunctions.find(_name); + if (it == m_lowLevelFunctions.end()) + return nullptr; + else + return *it; +} + +void CompilerContext::addLowLevelFunction(string const& _name, eth::AssemblyItem const& _label) +{ + solAssert(lowLevelFunctionEntryPoint(_name) != nullptr, "Low level function with that name already exists."); + m_lowLevelFunctions[_name] = _label.pushTag(); +} + ModifierDefinition const& CompilerContext::functionModifier(string const& _name) const { solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 806715280..34befc13d 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -90,6 +90,12 @@ public: /// as "having code". void startFunction(Declaration const& _function); + /// Returns the label of the low level function with the given name or nullptr if it + /// does not exist. The lifetime of the returned pointer is short. + eth::AssemblyItem const* lowLevelFunctionEntryPoint(std::string const& _name) const; + /// Inserts a low level function entry point into the list of low level functions. + void addLowLevelFunction(std::string const& _name, eth::AssemblyItem const& _label); + ModifierDefinition const& functionModifier(std::string const& _name) const; /// Returns the distance of the given local variable from the bottom of the stack (of the current function). unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; @@ -248,6 +254,8 @@ private: CompilerContext *m_runtimeContext; /// The index of the runtime subroutine. size_t m_runtimeSub = -1; + /// An index of low-level function labels by name. + std::map m_lowLevelFunctions; }; } From b60623521f052b8a36c61f8632d868cac552bf29 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 19 Jan 2017 17:21:55 +0100 Subject: [PATCH 116/414] Move some util functions to low-level functions. --- libsolidity/codegen/ArrayUtils.cpp | 362 ++++++++++++----------- libsolidity/codegen/CompilerContext.cpp | 69 +++-- libsolidity/codegen/CompilerContext.h | 35 ++- libsolidity/codegen/CompilerUtils.cpp | 67 +++-- libsolidity/codegen/ContractCompiler.cpp | 1 + 5 files changed, 305 insertions(+), 229 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 352c7177a..6f270b4dd 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -502,60 +502,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord } } -void ArrayUtils::clearArray(ArrayType const& _type) const +void ArrayUtils::clearArray(ArrayType const& _typeIn) const { - unsigned stackHeightStart = m_context.stackHeight(); - solAssert(_type.location() == DataLocation::Storage, ""); - if (_type.baseType()->storageBytes() < 32) - { - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); - } - if (_type.baseType()->isValueType()) - solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); - - m_context << Instruction::POP; // remove byte offset - if (_type.isDynamicallySized()) - clearDynamicArray(_type); - else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) - m_context << Instruction::POP; - else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) - { - // unroll loop for small arrays @todo choose a good value - // Note that we loop over storage slots here, not elements. - for (unsigned i = 1; i < _type.storageSize(); ++i) - m_context - << u256(0) << Instruction::DUP2 << Instruction::SSTORE - << u256(1) << Instruction::ADD; - m_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; - } - else if (!_type.baseType()->isValueType() && _type.length() <= 4) - { - // unroll loop for small arrays @todo choose a good value - solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); - for (unsigned i = 1; i < _type.length(); ++i) + TypePointer type = _typeIn.shared_from_this(); + m_context.callLowLevelFunction( + "$clearArray_" + _typeIn.identifier(), + 2, + 0, + [type](CompilerContext& _context) { - m_context << u256(0); - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false); - m_context - << Instruction::POP - << u256(_type.baseType()->storageSize()) << Instruction::ADD; + ArrayType const& _type = dynamic_cast(*type); + unsigned stackHeightStart = _context.stackHeight(); + solAssert(_type.location() == DataLocation::Storage, ""); + if (_type.baseType()->storageBytes() < 32) + { + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); + } + if (_type.baseType()->isValueType()) + solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); + + _context << Instruction::POP; // remove byte offset + if (_type.isDynamicallySized()) + ArrayUtils(_context).clearDynamicArray(_type); + else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) + _context << Instruction::POP; + else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) + { + // unroll loop for small arrays @todo choose a good value + // Note that we loop over storage slots here, not elements. + for (unsigned i = 1; i < _type.storageSize(); ++i) + _context + << u256(0) << Instruction::DUP2 << Instruction::SSTORE + << u256(1) << Instruction::ADD; + _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; + } + else if (!_type.baseType()->isValueType() && _type.length() <= 4) + { + // unroll loop for small arrays @todo choose a good value + solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); + for (unsigned i = 1; i < _type.length(); ++i) + { + _context << u256(0); + StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false); + _context + << Instruction::POP + << u256(_type.baseType()->storageSize()) << Instruction::ADD; + } + _context << u256(0); + StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true); + } + else + { + _context << Instruction::DUP1 << _type.length(); + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::ADD << Instruction::SWAP1; + if (_type.baseType()->storageBytes() < 32) + ArrayUtils(_context).clearStorageLoop(IntegerType(256)); + else + ArrayUtils(_context).clearStorageLoop(*_type.baseType()); + _context << Instruction::POP; + } + solAssert(_context.stackHeight() == stackHeightStart - 2, ""); } - m_context << u256(0); - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); - } - else - { - m_context << Instruction::DUP1 << _type.length(); - convertLengthToSize(_type); - m_context << Instruction::ADD << Instruction::SWAP1; - if (_type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); - else - clearStorageLoop(*_type.baseType()); - m_context << Instruction::POP; - } - solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); + ); } void ArrayUtils::clearDynamicArray(ArrayType const& _type) const @@ -597,144 +607,154 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const m_context << Instruction::POP; } -void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const +void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const { - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + TypePointer type = _typeIn.shared_from_this(); + m_context.callLowLevelFunction( + "$resizeDynamicArray_" + _typeIn.identifier(), + 2, + 0, + [type](CompilerContext& _context) + { + ArrayType const& _type = dynamic_cast(*type); + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - unsigned stackHeightStart = m_context.stackHeight(); - eth::AssemblyItem resizeEnd = m_context.newTag(); + unsigned stackHeightStart = _context.stackHeight(); + eth::AssemblyItem resizeEnd = _context.newTag(); - // stack: ref new_length - // fetch old length - retrieveLength(_type, 1); - // stack: ref new_length old_length - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2"); + // stack: ref new_length + // fetch old length + ArrayUtils(_context).retrieveLength(_type, 1); + // stack: ref new_length old_length + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2"); - // Special case for short byte arrays, they are stored together with their length - if (_type.isByteArray()) - { - eth::AssemblyItem regularPath = m_context.newTag(); - // We start by a large case-distinction about the old and new length of the byte array. + // Special case for short byte arrays, they are stored together with their length + if (_type.isByteArray()) + { + eth::AssemblyItem regularPath = _context.newTag(); + // We start by a large case-distinction about the old and new length of the byte array. - m_context << Instruction::DUP3 << Instruction::SLOAD; - // stack: ref new_length current_length ref_value + _context << Instruction::DUP3 << Instruction::SLOAD; + // stack: ref new_length current_length ref_value - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context << Instruction::DUP2 << u256(31) << Instruction::LT; - eth::AssemblyItem currentIsLong = m_context.appendConditionalJump(); - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - eth::AssemblyItem newIsLong = m_context.appendConditionalJump(); + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context << Instruction::DUP2 << u256(31) << Instruction::LT; + eth::AssemblyItem currentIsLong = _context.appendConditionalJump(); + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + eth::AssemblyItem newIsLong = _context.appendConditionalJump(); - // Here: short -> short + // Here: short -> short - // Compute 1 << (256 - 8 * new_size) - eth::AssemblyItem shortToShort = m_context.newTag(); - m_context << shortToShort; - m_context << Instruction::DUP3 << u256(8) << Instruction::MUL; - m_context << u256(0x100) << Instruction::SUB; - m_context << u256(2) << Instruction::EXP; - // Divide and multiply by that value, clearing bits. - m_context << Instruction::DUP1 << Instruction::SWAP2; - m_context << Instruction::DIV << Instruction::MUL; - // Insert 2*length. - m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - m_context << Instruction::OR; - // Store. - m_context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - m_context.appendJumpTo(resizeEnd); + // Compute 1 << (256 - 8 * new_size) + eth::AssemblyItem shortToShort = _context.newTag(); + _context << shortToShort; + _context << Instruction::DUP3 << u256(8) << Instruction::MUL; + _context << u256(0x100) << Instruction::SUB; + _context << u256(2) << Instruction::EXP; + // Divide and multiply by that value, clearing bits. + _context << Instruction::DUP1 << Instruction::SWAP2; + _context << Instruction::DIV << Instruction::MUL; + // Insert 2*length. + _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; + _context << Instruction::OR; + // Store. + _context << Instruction::DUP4 << Instruction::SSTORE; + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + _context.appendJumpTo(resizeEnd); - m_context.adjustStackOffset(1); // we have to do that because of the jumps - // Here: short -> long + _context.adjustStackOffset(1); // we have to do that because of the jumps + // Here: short -> long - m_context << newIsLong; - // stack: ref new_length current_length ref_value - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - // Zero out lower-order byte. - m_context << u256(0xff) << Instruction::NOT << Instruction::AND; - // Store at data location. - m_context << Instruction::DUP4; - CompilerUtils(m_context).computeHashStatic(); - m_context << Instruction::SSTORE; - // stack: ref new_length current_length - // Store new length: Compule 2*length + 1 and store it. - m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; - m_context << u256(1) << Instruction::ADD; - // stack: ref new_length current_length 2*new_length+1 - m_context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - m_context.appendJumpTo(resizeEnd); + _context << newIsLong; + // stack: ref new_length current_length ref_value + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + // Zero out lower-order byte. + _context << u256(0xff) << Instruction::NOT << Instruction::AND; + // Store at data location. + _context << Instruction::DUP4; + CompilerUtils(_context).computeHashStatic(); + _context << Instruction::SSTORE; + // stack: ref new_length current_length + // Store new length: Compule 2*length + 1 and store it. + _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; + _context << u256(1) << Instruction::ADD; + // stack: ref new_length current_length 2*new_length+1 + _context << Instruction::DUP4 << Instruction::SSTORE; + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + _context.appendJumpTo(resizeEnd); - m_context.adjustStackOffset(1); // we have to do that because of the jumps + _context.adjustStackOffset(1); // we have to do that because of the jumps - m_context << currentIsLong; - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - m_context.appendConditionalJumpTo(regularPath); + _context << currentIsLong; + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + _context.appendConditionalJumpTo(regularPath); - // Here: long -> short - // Read the first word of the data and store it on the stack. Clear the data location and - // then jump to the short -> short case. + // Here: long -> short + // Read the first word of the data and store it on the stack. Clear the data location and + // then jump to the short -> short case. - // stack: ref new_length current_length ref_value - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context << Instruction::POP << Instruction::DUP3; - CompilerUtils(m_context).computeHashStatic(); - m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location - m_context << Instruction::DUP3; - convertLengthToSize(_type); - m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location_end data_location - clearStorageLoop(IntegerType(256)); - m_context << Instruction::POP; - // stack: ref new_length current_length first_word - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context.appendJumpTo(shortToShort); + // stack: ref new_length current_length ref_value + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context << Instruction::POP << Instruction::DUP3; + CompilerUtils(_context).computeHashStatic(); + _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location + _context << Instruction::DUP3; + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location_end data_location + ArrayUtils(_context).clearStorageLoop(IntegerType(256)); + _context << Instruction::POP; + // stack: ref new_length current_length first_word + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context.appendJumpTo(shortToShort); - m_context << regularPath; - // stack: ref new_length current_length ref_value - m_context << Instruction::POP; - } + _context << regularPath; + // stack: ref new_length current_length ref_value + _context << Instruction::POP; + } - // Change of length for a regular array (i.e. length at location, data at sha3(location)). - // stack: ref new_length old_length - // store new length - m_context << Instruction::DUP2; - if (_type.isByteArray()) - // For a "long" byte array, store length as 2*length+1 - m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; - m_context<< Instruction::DUP4 << Instruction::SSTORE; - // skip if size is not reduced - m_context << Instruction::DUP2 << Instruction::DUP2 - << Instruction::ISZERO << Instruction::GT; - m_context.appendConditionalJumpTo(resizeEnd); + // Change of length for a regular array (i.e. length at location, data at sha3(location)). + // stack: ref new_length old_length + // store new length + _context << Instruction::DUP2; + if (_type.isByteArray()) + // For a "long" byte array, store length as 2*length+1 + _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; + _context<< Instruction::DUP4 << Instruction::SSTORE; + // skip if size is not reduced + _context << Instruction::DUP2 << Instruction::DUP2 + << Instruction::ISZERO << Instruction::GT; + _context.appendConditionalJumpTo(resizeEnd); - // size reduced, clear the end of the array - // stack: ref new_length old_length - convertLengthToSize(_type); - m_context << Instruction::DUP2; - convertLengthToSize(_type); - // stack: ref new_length old_size new_size - // compute data positions - m_context << Instruction::DUP4; - CompilerUtils(m_context).computeHashStatic(); - // stack: ref new_length old_size new_size data_pos - m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; - // stack: ref new_length data_pos new_size delete_end - m_context << Instruction::SWAP2 << Instruction::ADD; - // stack: ref new_length delete_end delete_start - if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); - else - clearStorageLoop(*_type.baseType()); + // size reduced, clear the end of the array + // stack: ref new_length old_length + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::DUP2; + ArrayUtils(_context).convertLengthToSize(_type); + // stack: ref new_length old_size new_size + // compute data positions + _context << Instruction::DUP4; + CompilerUtils(_context).computeHashStatic(); + // stack: ref new_length old_size new_size data_pos + _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; + // stack: ref new_length data_pos new_size delete_end + _context << Instruction::SWAP2 << Instruction::ADD; + // stack: ref new_length delete_end delete_start + if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) + ArrayUtils(_context).clearStorageLoop(IntegerType(256)); + else + ArrayUtils(_context).clearStorageLoop(*_type.baseType()); - m_context << resizeEnd; - // cleanup - m_context << Instruction::POP << Instruction::POP << Instruction::POP; - solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); + _context << resizeEnd; + // cleanup + _context << Instruction::POP << Instruction::POP << Instruction::POP; + solAssert(_context.stackHeight() == stackHeightStart - 2, ""); + } + ); } void ArrayUtils::clearStorageLoop(Type const& _type) const diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index fcd39b333..dad227c7b 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -21,15 +21,18 @@ */ #include -#include -#include -#include +#include #include #include #include #include #include +#include + +#include +#include + using namespace std; namespace dev @@ -57,6 +60,51 @@ void CompilerContext::startFunction(Declaration const& _function) *this << functionEntryLabel(_function); } +void CompilerContext::callLowLevelFunction( + string const& _name, + unsigned _inArgs, + unsigned _outArgs, + function const& _generator +) +{ + eth::AssemblyItem retTag = pushNewTag(); + CompilerUtils(*this).moveIntoStack(_inArgs); + + auto it = m_lowLevelFunctions.find(_name); + if (it == m_lowLevelFunctions.end()) + { + eth::AssemblyItem tag = newTag().pushTag(); + m_lowLevelFunctions.insert(make_pair(_name, tag)); + m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator)); + *this << tag; + } + else + *this << it->second; + appendJump(eth::AssemblyItem::JumpType::IntoFunction); + adjustStackOffset(_outArgs - 1 - _inArgs); + *this << retTag.tag(); +} + +void CompilerContext::appendMissingLowLevelFunctions() +{ + while (!m_lowLevelFunctionGenerationQueue.empty()) + { + string name; + unsigned inArgs; + unsigned outArgs; + function generator; + tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front(); + m_lowLevelFunctionGenerationQueue.pop(); + + setStackOffset(inArgs + 1); + *this << m_lowLevelFunctions.at(name).tag(); + generator(*this); + CompilerUtils(*this).moveToStackTop(outArgs); + appendJump(eth::AssemblyItem::JumpType::OutOfFunction); + solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + "."); + } +} + void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { @@ -125,21 +173,6 @@ Declaration const* CompilerContext::nextFunctionToCompile() const return m_functionCompilationQueue.nextFunctionToCompile(); } -eth::AssemblyItem const* CompilerContext::lowLevelFunctionEntryPoint(string const& _name) const -{ - auto it = m_lowLevelFunctions.find(_name); - if (it == m_lowLevelFunctions.end()) - return nullptr; - else - return *it; -} - -void CompilerContext::addLowLevelFunction(string const& _name, eth::AssemblyItem const& _label) -{ - solAssert(lowLevelFunctionEntryPoint(_name) != nullptr, "Low level function with that name already exists."); - m_lowLevelFunctions[_name] = _label.pushTag(); -} - ModifierDefinition const& CompilerContext::functionModifier(string const& _name) const { solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 34befc13d..f024b0103 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -22,16 +22,20 @@ #pragma once +#include +#include +#include + +#include +#include + +#include + #include #include #include #include -#include -#include -#include -#include -#include -#include +#include namespace dev { namespace solidity { @@ -90,11 +94,18 @@ public: /// as "having code". void startFunction(Declaration const& _function); - /// Returns the label of the low level function with the given name or nullptr if it - /// does not exist. The lifetime of the returned pointer is short. - eth::AssemblyItem const* lowLevelFunctionEntryPoint(std::string const& _name) const; - /// Inserts a low level function entry point into the list of low level functions. - void addLowLevelFunction(std::string const& _name, eth::AssemblyItem const& _label); + /// Appends a call to the named low-level function and inserts the generator into the + /// list of low-level-functions to be generated, unless it already exists. + /// Note that the generator should not assume that objects are still alive when it is called, + /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example). + void callLowLevelFunction( + std::string const& _name, + unsigned _inArgs, + unsigned _outArgs, + std::function const& _generator + ); + /// Generates the code for missing low-level functions, i.e. calls the generators passed above. + void appendMissingLowLevelFunctions(); ModifierDefinition const& functionModifier(std::string const& _name) const; /// Returns the distance of the given local variable from the bottom of the stack (of the current function). @@ -256,6 +267,8 @@ private: size_t m_runtimeSub = -1; /// An index of low-level function labels by name. std::map m_lowLevelFunctions; + /// The queue of low-level functions to generate. + std::queue>> m_lowLevelFunctionGenerationQueue; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 7d382aba8..caf3b1acd 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -820,37 +820,46 @@ void CompilerUtils::pushZeroValue(Type const& _type) } solAssert(referenceType->location() == DataLocation::Memory, ""); - m_context << u256(max(32u, _type.calldataEncodedSize())); - allocateMemory(); - m_context << Instruction::DUP1; + TypePointer type = _type.shared_from_this(); + m_context.callLowLevelFunction( + "$pushZeroValue_" + referenceType->identifier(), + 0, + 1, + [type](CompilerContext& _context) { + CompilerUtils utils(_context); + _context << u256(max(32u, type->calldataEncodedSize())); + utils.allocateMemory(); + _context << Instruction::DUP1; - if (auto structType = dynamic_cast(&_type)) - for (auto const& member: structType->members(nullptr)) - { - pushZeroValue(*member.type); - storeInMemoryDynamic(*member.type); - } - else if (auto arrayType = dynamic_cast(&_type)) - { - if (arrayType->isDynamicallySized()) - { - // zero length - m_context << u256(0); - storeInMemoryDynamic(IntegerType(256)); - } - else if (arrayType->length() > 0) - { - m_context << arrayType->length() << Instruction::SWAP1; - // stack: items_to_do memory_pos - zeroInitialiseMemoryArray(*arrayType); - // stack: updated_memory_pos - } - } - else - solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); + if (auto structType = dynamic_cast(type.get())) + for (auto const& member: structType->members(nullptr)) + { + utils.pushZeroValue(*member.type); + utils.storeInMemoryDynamic(*member.type); + } + else if (auto arrayType = dynamic_cast(type.get())) + { + if (arrayType->isDynamicallySized()) + { + // zero length + _context << u256(0); + utils.storeInMemoryDynamic(IntegerType(256)); + } + else if (arrayType->length() > 0) + { + _context << arrayType->length() << Instruction::SWAP1; + // stack: items_to_do memory_pos + utils.zeroInitialiseMemoryArray(*arrayType); + // stack: updated_memory_pos + } + } + else + solAssert(false, "Requested initialisation for unknown type: " + type->toString()); - // remove the updated memory pointer - m_context << Instruction::POP; + // remove the updated memory pointer + _context << Instruction::POP; + } + ); } void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index bdff0da43..9dc1fb37c 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -827,6 +827,7 @@ void ContractCompiler::appendMissingFunctions() function->accept(*this); solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?"); } + m_context.appendMissingLowLevelFunctions(); } void ContractCompiler::appendModifierOrFunctionCode() From cea020b89e6f5bf20e27d7059ad27392e8526e9b Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 14:19:25 +0100 Subject: [PATCH 117/414] Convert ArrayUtils routines into low-level functions. --- libsolidity/codegen/ArrayUtils.cpp | 473 +++++++++++++++-------------- 1 file changed, 248 insertions(+), 225 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 6f270b4dd..f6cb7aac2 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack layout: [source_ref] [source length] target_ref (top) solAssert(_targetType.location() == DataLocation::Storage, ""); - IntegerType uint256(256); - Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType()); - Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType()); + TypePointer uint256 = make_shared(256); + TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType(); + TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType(); // TODO unroll loop for small sizes @@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons } // stack: target_ref source_ref source_length - m_context << Instruction::DUP3; - // stack: target_ref source_ref source_length target_ref - retrieveLength(_targetType); - // stack: target_ref source_ref source_length target_ref target_length - if (_targetType.isDynamicallySized()) - // store new target length - if (!_targetType.isByteArray()) - // Otherwise, length will be stored below. - m_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; - if (sourceBaseType->category() == Type::Category::Mapping) - { - solAssert(targetBaseType->category() == Type::Category::Mapping, ""); - solAssert(_sourceType.location() == DataLocation::Storage, ""); - // nothing to copy - m_context - << Instruction::POP << Instruction::POP - << Instruction::POP << Instruction::POP; - return; - } - // stack: target_ref source_ref source_length target_ref target_length - // compute hashes (data positions) - m_context << Instruction::SWAP1; - if (_targetType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref source_ref source_length target_length target_data_pos - m_context << Instruction::SWAP1; - convertLengthToSize(_targetType); - m_context << Instruction::DUP2 << Instruction::ADD; - // stack: target_ref source_ref source_length target_data_pos target_data_end - m_context << Instruction::SWAP3; - // stack: target_ref target_data_end source_length target_data_pos source_ref - - eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); - - // special case for short byte arrays: Store them together with their length. - if (_targetType.isByteArray()) - { - // stack: target_ref target_data_end source_length target_data_pos source_ref - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - eth::AssemblyItem longByteArray = m_context.appendConditionalJump(); - // store the short byte array - solAssert(_sourceType.isByteArray(), ""); - if (_sourceType.location() == DataLocation::Storage) + TypePointer targetType = _targetType.shared_from_this(); + TypePointer sourceType = _sourceType.shared_from_this(); + m_context.callLowLevelFunction( + "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), + 3, + 1, + [=](CompilerContext& _context) { - // just copy the slot, it contains length and data - m_context << Instruction::DUP1 << Instruction::SLOAD; - m_context << Instruction::DUP6 << Instruction::SSTORE; - } - else - { - m_context << Instruction::DUP1; - CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - // stack: target_ref target_data_end source_length target_data_pos source_ref value - // clear the lower-order byte - which will hold the length - m_context << u256(0xff) << Instruction::NOT << Instruction::AND; - // fetch the length and shift it left by one - m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; - // combine value and length and store them - m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; - } - // end of special case, jump right into cleaning target data area - m_context.appendJumpTo(copyLoopEndWithoutByteOffset); - m_context << longByteArray; - // Store length (2*length+1) - m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - m_context << u256(1) << Instruction::ADD; - m_context << Instruction::DUP6 << Instruction::SSTORE; - } + ArrayUtils utils(_context); + ArrayType const& _sourceType = dynamic_cast(*sourceType); + ArrayType const& _targetType = dynamic_cast(*targetType); + // stack: target_ref source_ref source_length + _context << Instruction::DUP3; + // stack: target_ref source_ref source_length target_ref + utils.retrieveLength(_targetType); + // stack: target_ref source_ref source_length target_ref target_length + if (_targetType.isDynamicallySized()) + // store new target length + if (!_targetType.isByteArray()) + // Otherwise, length will be stored below. + _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; + if (sourceBaseType->category() == Type::Category::Mapping) + { + solAssert(targetBaseType->category() == Type::Category::Mapping, ""); + solAssert(_sourceType.location() == DataLocation::Storage, ""); + // nothing to copy + _context + << Instruction::POP << Instruction::POP + << Instruction::POP << Instruction::POP; + return; + } + // stack: target_ref source_ref source_length target_ref target_length + // compute hashes (data positions) + _context << Instruction::SWAP1; + if (_targetType.isDynamicallySized()) + CompilerUtils(_context).computeHashStatic(); + // stack: target_ref source_ref source_length target_length target_data_pos + _context << Instruction::SWAP1; + utils.convertLengthToSize(_targetType); + _context << Instruction::DUP2 << Instruction::ADD; + // stack: target_ref source_ref source_length target_data_pos target_data_end + _context << Instruction::SWAP3; + // stack: target_ref target_data_end source_length target_data_pos source_ref - // skip copying if source length is zero - m_context << Instruction::DUP3 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); + eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag(); - if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref target_data_end source_length target_data_pos source_data_pos - m_context << Instruction::SWAP2; - convertLengthToSize(_sourceType); - m_context << Instruction::DUP3 << Instruction::ADD; - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end - if (haveByteOffsetTarget) - m_context << u256(0); - if (haveByteOffsetSource) - m_context << u256(0); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart; - // check for loop condition - m_context - << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) - << Instruction::GT << Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump(); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // copy - if (sourceBaseType->category() == Type::Category::Array) - { - solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); - auto const& sourceBaseArrayType = dynamic_cast(*sourceBaseType); - m_context << Instruction::DUP3; - if (sourceBaseArrayType.location() == DataLocation::Memory) - m_context << Instruction::MLOAD; - m_context << Instruction::DUP3; - copyArrayToStorage(dynamic_cast(*targetBaseType), sourceBaseArrayType); - m_context << Instruction::POP; - } - else if (directCopy) - { - solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); - m_context - << Instruction::DUP3 << Instruction::SLOAD - << Instruction::DUP3 << Instruction::SSTORE; - } - else - { - // Note that we have to copy each element on its own in case conversion is involved. - // We might copy too much if there is padding at the last element, but this way end - // checking is easier. - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - m_context << dupInstruction(3 + byteOffsetSize); - if (_sourceType.location() == DataLocation::Storage) - { + // special case for short byte arrays: Store them together with their length. + if (_targetType.isByteArray()) + { + // stack: target_ref target_data_end source_length target_data_pos source_ref + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + eth::AssemblyItem longByteArray = _context.appendConditionalJump(); + // store the short byte array + solAssert(_sourceType.isByteArray(), ""); + if (_sourceType.location() == DataLocation::Storage) + { + // just copy the slot, it contains length and data + _context << Instruction::DUP1 << Instruction::SLOAD; + _context << Instruction::DUP6 << Instruction::SSTORE; + } + else + { + _context << Instruction::DUP1; + CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + // stack: target_ref target_data_end source_length target_data_pos source_ref value + // clear the lower-order byte - which will hold the length + _context << u256(0xff) << Instruction::NOT << Instruction::AND; + // fetch the length and shift it left by one + _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; + // combine value and length and store them + _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; + } + // end of special case, jump right into cleaning target data area + _context.appendJumpTo(copyLoopEndWithoutByteOffset); + _context << longByteArray; + // Store length (2*length+1) + _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; + _context << u256(1) << Instruction::ADD; + _context << Instruction::DUP6 << Instruction::SSTORE; + } + + // skip copying if source length is zero + _context << Instruction::DUP3 << Instruction::ISZERO; + _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); + + if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) + CompilerUtils(_context).computeHashStatic(); + // stack: target_ref target_data_end source_length target_data_pos source_data_pos + _context << Instruction::SWAP2; + utils.convertLengthToSize(_sourceType); + _context << Instruction::DUP3 << Instruction::ADD; + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + if (haveByteOffsetTarget) + _context << u256(0); if (haveByteOffsetSource) - m_context << Instruction::DUP2; + _context << u256(0); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + eth::AssemblyItem copyLoopStart = _context.newTag(); + _context << copyLoopStart; + // check for loop condition + _context + << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) + << Instruction::GT << Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = _context.appendConditionalJump(); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // copy + if (sourceBaseType->category() == Type::Category::Array) + { + solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); + auto const& sourceBaseArrayType = dynamic_cast(*sourceBaseType); + _context << Instruction::DUP3; + if (sourceBaseArrayType.location() == DataLocation::Memory) + _context << Instruction::MLOAD; + _context << Instruction::DUP3; + utils.copyArrayToStorage(dynamic_cast(*targetBaseType), sourceBaseArrayType); + _context << Instruction::POP; + } + else if (directCopy) + { + solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); + _context + << Instruction::DUP3 << Instruction::SLOAD + << Instruction::DUP3 << Instruction::SSTORE; + } else - m_context << u256(0); - StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + { + // Note that we have to copy each element on its own in case conversion is involved. + // We might copy too much if there is padding at the last element, but this way end + // checking is easier. + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + _context << dupInstruction(3 + byteOffsetSize); + if (_sourceType.location() == DataLocation::Storage) + { + if (haveByteOffsetSource) + _context << Instruction::DUP2; + else + _context << u256(0); + StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + } + else if (sourceBaseType->isValueType()) + CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + else + solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] ... + solAssert( + 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, + "Stack too deep, try removing local variables." + ); + // fetch target storage reference + _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); + if (haveByteOffsetTarget) + _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); + else + _context << u256(0); + StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); + } + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // increment source + if (haveByteOffsetSource) + utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); + else + { + _context << swapInstruction(2 + byteOffsetSize); + if (sourceIsStorage) + _context << sourceBaseType->storageSize(); + else if (_sourceType.location() == DataLocation::Memory) + _context << sourceBaseType->memoryHeadSize(); + else + _context << sourceBaseType->calldataEncodedSize(true); + _context + << Instruction::ADD + << swapInstruction(2 + byteOffsetSize); + } + // increment target + if (haveByteOffsetTarget) + utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + else + _context + << swapInstruction(1 + byteOffsetSize) + << targetBaseType->storageSize() + << Instruction::ADD + << swapInstruction(1 + byteOffsetSize); + _context.appendJumpTo(copyLoopStart); + _context << copyLoopEnd; + if (haveByteOffsetTarget) + { + // clear elements that might be left over in the current slot in target + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] + _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; + eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump(); + _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); + StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true); + utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + _context.appendJumpTo(copyLoopEnd); + + _context << copyCleanupLoopEnd; + _context << Instruction::POP; // might pop the source, but then target is popped next + } + if (haveByteOffsetSource) + _context << Instruction::POP; + _context << copyLoopEndWithoutByteOffset; + + // zero-out leftovers in target + // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end + _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; + // stack: target_ref target_data_end target_data_pos_updated + utils.clearStorageLoop(*targetBaseType); + _context << Instruction::POP; } - else if (sourceBaseType->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - else - solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] ... - solAssert( - 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, - "Stack too deep, try removing local variables." - ); - // fetch target storage reference - m_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); - if (haveByteOffsetTarget) - m_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); - else - m_context << u256(0); - StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); - } - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // increment source - if (haveByteOffsetSource) - incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); - else - { - m_context << swapInstruction(2 + byteOffsetSize); - if (sourceIsStorage) - m_context << sourceBaseType->storageSize(); - else if (_sourceType.location() == DataLocation::Memory) - m_context << sourceBaseType->memoryHeadSize(); - else - m_context << sourceBaseType->calldataEncodedSize(true); - m_context - << Instruction::ADD - << swapInstruction(2 + byteOffsetSize); - } - // increment target - if (haveByteOffsetTarget) - incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - else - m_context - << swapInstruction(1 + byteOffsetSize) - << targetBaseType->storageSize() - << Instruction::ADD - << swapInstruction(1 + byteOffsetSize); - m_context.appendJumpTo(copyLoopStart); - m_context << copyLoopEnd; - if (haveByteOffsetTarget) - { - // clear elements that might be left over in the current slot in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] - m_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; - eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); - m_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); - StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true); - incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - m_context.appendJumpTo(copyLoopEnd); - - m_context << copyCleanupLoopEnd; - m_context << Instruction::POP; // might pop the source, but then target is popped next - } - if (haveByteOffsetSource) - m_context << Instruction::POP; - m_context << copyLoopEndWithoutByteOffset; - - // zero-out leftovers in target - // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end - m_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - clearStorageLoop(*targetBaseType); - m_context << Instruction::POP; + ); } void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const @@ -759,41 +773,50 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const void ArrayUtils::clearStorageLoop(Type const& _type) const { - unsigned stackHeightStart = m_context.stackHeight(); - if (_type.category() == Type::Category::Mapping) - { - m_context << Instruction::POP; - return; - } - // stack: end_pos pos + TypePointer type = _type.shared_from_this(); + m_context.callLowLevelFunction( + "$clearStorageLoop_" + _type.identifier(), + 2, + 1, + [type](CompilerContext& _context) + { + unsigned stackHeightStart = _context.stackHeight(); + if (type->category() == Type::Category::Mapping) + { + _context << Instruction::POP; + return; + } + // stack: end_pos pos - // jump to and return from the loop to allow for duplicate code removal - eth::AssemblyItem returnTag = m_context.pushNewTag(); - m_context << Instruction::SWAP2 << Instruction::SWAP1; + // jump to and return from the loop to allow for duplicate code removal + eth::AssemblyItem returnTag = _context.pushNewTag(); + _context << Instruction::SWAP2 << Instruction::SWAP1; - // stack: end_pos pos - eth::AssemblyItem loopStart = m_context.appendJumpToNew(); - m_context << loopStart; - // check for loop condition - m_context << Instruction::DUP1 << Instruction::DUP3 - << Instruction::GT << Instruction::ISZERO; - eth::AssemblyItem zeroLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(zeroLoopEnd); - // delete - m_context << u256(0); - StorageItem(m_context, _type).setToZero(SourceLocation(), false); - m_context << Instruction::POP; - // increment - m_context << _type.storageSize() << Instruction::ADD; - m_context.appendJumpTo(loopStart); - // cleanup - m_context << zeroLoopEnd; - m_context << Instruction::POP << Instruction::SWAP1; - // "return" - m_context << Instruction::JUMP; + // stack: end_pos pos + eth::AssemblyItem loopStart = _context.appendJumpToNew(); + _context << loopStart; + // check for loop condition + _context << Instruction::DUP1 << Instruction::DUP3 + << Instruction::GT << Instruction::ISZERO; + eth::AssemblyItem zeroLoopEnd = _context.newTag(); + _context.appendConditionalJumpTo(zeroLoopEnd); + // delete + _context << u256(0); + StorageItem(_context, *type).setToZero(SourceLocation(), false); + _context << Instruction::POP; + // increment + _context << type->storageSize() << Instruction::ADD; + _context.appendJumpTo(loopStart); + // cleanup + _context << zeroLoopEnd; + _context << Instruction::POP << Instruction::SWAP1; + // "return" + _context << Instruction::JUMP; - m_context << returnTag; - solAssert(m_context.stackHeight() == stackHeightStart - 1, ""); + _context << returnTag; + solAssert(_context.stackHeight() == stackHeightStart - 1, ""); + } + ); } void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const From 82a00e7dc50a8d8828fa6c192f78d4147944eccf Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 14:30:02 +0100 Subject: [PATCH 118/414] Use shared_ptrs to enable shared_from_this. --- libsolidity/codegen/ArrayUtils.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index f6cb7aac2..0b09cc1ad 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -572,7 +572,7 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::ADD << Instruction::SWAP1; if (_type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(IntegerType(256)); + ArrayUtils(_context).clearStorageLoop(*make_shared(256)); else ArrayUtils(_context).clearStorageLoop(*_type.baseType()); _context << Instruction::POP; @@ -613,7 +613,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const << Instruction::SWAP1; // stack: data_pos_end data_pos if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); + clearStorageLoop(*make_shared(256)); else clearStorageLoop(*_type.baseType()); // cleanup @@ -720,7 +720,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; // stack: ref new_length current_length first_word data_location_end data_location - ArrayUtils(_context).clearStorageLoop(IntegerType(256)); + ArrayUtils(_context).clearStorageLoop(*make_shared(256)); _context << Instruction::POP; // stack: ref new_length current_length first_word solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); @@ -759,7 +759,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const _context << Instruction::SWAP2 << Instruction::ADD; // stack: ref new_length delete_end delete_start if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(IntegerType(256)); + ArrayUtils(_context).clearStorageLoop(*make_shared(256)); else ArrayUtils(_context).clearStorageLoop(*_type.baseType()); From 1b87e08e046b786b9c85c08696c72ed162f5f9a1 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 14:44:44 +0100 Subject: [PATCH 119/414] Changelog entry. --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index cd3e31b20..7dd607d8d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,10 @@ Features: * AST: Use deterministic node identifiers. * Type system: Introduce type identifier strings. * Metadata: Do not include platform in the version number. + * Code generator: Extract array utils into low-level functions. + +Bugfixes: + * Code generator: Allow recursive structs. * Type checker: Allow multiple events of the same name (but with different arities or argument types) ### 0.4.8 (2017-01-13) From 23eca813f578463383afd37b76ce49d87f79c811 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 24 Jan 2017 12:06:11 +0100 Subject: [PATCH 120/414] Change clearStorageLoop to TypePointer. --- libsolidity/codegen/ArrayUtils.cpp | 29 ++++++++++++++--------------- libsolidity/codegen/ArrayUtils.h | 5 ++++- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 0b09cc1ad..4d100d1d2 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -276,7 +276,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; // stack: target_ref target_data_end target_data_pos_updated - utils.clearStorageLoop(*targetBaseType); + utils.clearStorageLoop(targetBaseType); _context << Instruction::POP; } ); @@ -572,9 +572,9 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::ADD << Instruction::SWAP1; if (_type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(*make_shared(256)); + ArrayUtils(_context).clearStorageLoop(make_shared(256)); else - ArrayUtils(_context).clearStorageLoop(*_type.baseType()); + ArrayUtils(_context).clearStorageLoop(_type.baseType()); _context << Instruction::POP; } solAssert(_context.stackHeight() == stackHeightStart - 2, ""); @@ -613,9 +613,9 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const << Instruction::SWAP1; // stack: data_pos_end data_pos if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(*make_shared(256)); + clearStorageLoop(make_shared(256)); else - clearStorageLoop(*_type.baseType()); + clearStorageLoop(_type.baseType()); // cleanup m_context << endTag; m_context << Instruction::POP; @@ -720,7 +720,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; // stack: ref new_length current_length first_word data_location_end data_location - ArrayUtils(_context).clearStorageLoop(*make_shared(256)); + ArrayUtils(_context).clearStorageLoop(make_shared(256)); _context << Instruction::POP; // stack: ref new_length current_length first_word solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); @@ -759,9 +759,9 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const _context << Instruction::SWAP2 << Instruction::ADD; // stack: ref new_length delete_end delete_start if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(*make_shared(256)); + ArrayUtils(_context).clearStorageLoop(make_shared(256)); else - ArrayUtils(_context).clearStorageLoop(*_type.baseType()); + ArrayUtils(_context).clearStorageLoop(_type.baseType()); _context << resizeEnd; // cleanup @@ -771,17 +771,16 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const ); } -void ArrayUtils::clearStorageLoop(Type const& _type) const +void ArrayUtils::clearStorageLoop(TypePointer const& _type) const { - TypePointer type = _type.shared_from_this(); m_context.callLowLevelFunction( - "$clearStorageLoop_" + _type.identifier(), + "$clearStorageLoop_" + _type->identifier(), 2, 1, - [type](CompilerContext& _context) + [_type](CompilerContext& _context) { unsigned stackHeightStart = _context.stackHeight(); - if (type->category() == Type::Category::Mapping) + if (_type->category() == Type::Category::Mapping) { _context << Instruction::POP; return; @@ -802,10 +801,10 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const _context.appendConditionalJumpTo(zeroLoopEnd); // delete _context << u256(0); - StorageItem(_context, *type).setToZero(SourceLocation(), false); + StorageItem(_context, *_type).setToZero(SourceLocation(), false); _context << Instruction::POP; // increment - _context << type->storageSize() << Instruction::ADD; + _context << _type->storageSize() << Instruction::ADD; _context.appendJumpTo(loopStart); // cleanup _context << zeroLoopEnd; diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index d0ba2892b..806fbea7b 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -22,6 +22,8 @@ #pragma once +#include + namespace dev { namespace solidity @@ -30,6 +32,7 @@ namespace solidity class CompilerContext; class Type; class ArrayType; +using TypePointer = std::shared_ptr; /** * Class that provides code generation for handling arrays. @@ -67,7 +70,7 @@ public: /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). /// Stack pre: end_ref start_ref /// Stack post: end_ref - void clearStorageLoop(Type const& _type) const; + void clearStorageLoop(TypePointer const& _type) const; /// Converts length to size (number of storage slots or calldata/memory bytes). /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. /// Stack pre: length From 7e6f1b3f0008d03e6cdfa186b8f9976570865d4e Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 24 Jan 2017 12:06:21 +0100 Subject: [PATCH 121/414] Use int arithmetics for stack adjustment. --- libsolidity/codegen/CompilerContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index dad227c7b..e26f96e8f 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -81,7 +81,7 @@ void CompilerContext::callLowLevelFunction( else *this << it->second; appendJump(eth::AssemblyItem::JumpType::IntoFunction); - adjustStackOffset(_outArgs - 1 - _inArgs); + adjustStackOffset(int(_outArgs) - 1 - _inArgs); *this << retTag.tag(); } From 8e5f1c0d50c0512ac3e3c51ebec3afc500f4721e Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 20:23:49 +0100 Subject: [PATCH 122/414] Test double inclusion of bytecode. --- test/libsolidity/SolidityEndToEndTest.cpp | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index e8e5ced1a..646017fbf 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8992,6 +8992,35 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment) compileAndRun(sourceCode, 0, "C2"); } +BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once) +{ + char const* sourceCode = R"( + contract D { + bytes a = hex"1237651237125387136581271652831736512837126583171583712358126123765123712538713658127165283173651283712658317158371235812612376512371253871365812716528317365128371265831715837123581261237651237125387136581271652831736512837126583171583712358126"; + bytes b = hex"1237651237125327136581271252831736512837126583171383712358126123765125712538713658127165253173651283712658357158371235812612376512371a5387136581271652a317365128371265a317158371235812612a765123712538a13658127165a83173651283712a58317158371235a126"; + function D(uint) {} + } + contract Double { + function f() { + new D(2); + } + function g() { + new D(3); + } + } + contract Single { + function f() { + new D(2); + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK_LE( + double(m_compiler.object("Double").bytecode.size()), + 1.1 * double(m_compiler.object("Single").bytecode.size()) + ); +} + BOOST_AUTO_TEST_CASE(recursive_structs) { char const* sourceCode = R"( From ead1a3b33fae83ad65210d6f82b2ca12150bf2bb Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Jan 2017 20:16:18 +0100 Subject: [PATCH 123/414] Include creation code only once. --- libsolidity/codegen/ExpressionCompiler.cpp | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 81d3409ea..fe0eeb1cd 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -556,20 +556,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arg->accept(*this); argumentTypes.push_back(arg->annotation().type); } - ContractDefinition const& contract = - dynamic_cast(*function.returnParameterTypes().front()).contractDefinition(); - // copy the contract's code into memory - eth::Assembly const& assembly = m_context.compiledContract(contract); - utils().fetchFreeMemoryPointer(); - // TODO we create a copy here, which is actually what we want. - // This should be revisited at the point where we fix - // https://github.com/ethereum/solidity/issues/1092 - // pushes size - auto subroutine = m_context.addSubroutine(make_shared(assembly)); - m_context << Instruction::DUP1 << subroutine; - m_context << Instruction::DUP4 << Instruction::CODECOPY; - - m_context << Instruction::ADD; + ContractDefinition const* contract = + &dynamic_cast(*function.returnParameterTypes().front()).contractDefinition(); + m_context.callLowLevelFunction( + "$copyContractCreationCodeToMemory_" + contract->type()->identifier(), + 0, + 1, + [contract](CompilerContext& _context) + { + // copy the contract's code into memory + eth::Assembly const& assembly = _context.compiledContract(*contract); + CompilerUtils(_context).fetchFreeMemoryPointer(); + // pushes size + auto subroutine = _context.addSubroutine(make_shared(assembly)); + _context << Instruction::DUP1 << subroutine; + _context << Instruction::DUP4 << Instruction::CODECOPY; + _context << Instruction::ADD; + } + ); utils().encodeToMemory(argumentTypes, function.parameterTypes()); // now on stack: memory_end_ptr // need: size, offset, endowment From 1316bb75651ea365c5246277d2dfd3d366be9070 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 24 Jan 2017 17:38:06 +0100 Subject: [PATCH 124/414] Warn about invalid checksums of addresses. --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 10 ++++ libsolidity/ast/AST.cpp | 43 +++++++++++++++- libsolidity/ast/AST.h | 5 ++ libsolidity/ast/Types.cpp | 8 +++ libsolidity/ast/Types.h | 2 + libsolidity/codegen/ExpressionCompiler.cpp | 1 + .../SolidityNameAndTypeResolution.cpp | 49 +++++++++++++++++++ 8 files changed, 117 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 7dd607d8d..a14e9ec89 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Features: * Compiler interface: Report source location for "stack too deep" errors. * AST: Use deterministic node identifiers. * Type system: Introduce type identifier strings. + * Type checker: Warn about invalid checksum for addresses and deduce type from valid ones. * Metadata: Do not include platform in the version number. * Code generator: Extract array utils into low-level functions. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index adb3d54f7..533c787b7 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1565,6 +1565,16 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(Literal const& _literal) { + if (_literal.looksLikeAddress()) + { + if (_literal.passesAddressChecksum()) + { + _literal.annotation().type = make_shared(0, IntegerType::Modifier::Address); + return; + } + else + warning(_literal.location(), "This looks like an address but has an invalid checksum."); + } _literal.annotation().type = Type::forLiteral(_literal); if (!_literal.annotation().type) fatalTypeError(_literal.location(), "Invalid literal value."); diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 8a43c3f75..e2b50dd71 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -20,8 +20,6 @@ * Solidity abstract syntax tree. */ -#include -#include #include #include #include @@ -30,6 +28,11 @@ #include +#include + +#include +#include + using namespace std; using namespace dev; using namespace dev::solidity; @@ -522,3 +525,39 @@ IdentifierAnnotation& Identifier::annotation() const m_annotation = new IdentifierAnnotation(); return static_cast(*m_annotation); } + +bool Literal::looksLikeAddress() const +{ + if (subDenomination() != SubDenomination::None) + return false; + + string lit = value(); + return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1; +} + +bool Literal::passesAddressChecksum() const +{ + string lit = value(); + solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix"); + lit = lit.substr(2); + + if (lit.length() != 40) + return false; + + h256 hash = keccak256(boost::algorithm::to_lower_copy(lit, std::locale::classic())); + for (size_t i = 0; i < 40; ++i) + { + char addressCharacter = lit[i]; + bool lowerCase; + if ('a' <= addressCharacter && addressCharacter <= 'f') + lowerCase = true; + else if ('A' <= addressCharacter && addressCharacter <= 'F') + lowerCase = false; + else + continue; + unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf; + if ((nibble >= 8) == lowerCase) + return false; + } + return true; +} diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 4c75f7ead..743fdaa1d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1584,6 +1584,11 @@ public: SubDenomination subDenomination() const { return m_subDenomination; } + /// @returns true if this looks like a checksummed address. + bool looksLikeAddress() const; + /// @returns true if it passes the address checksum test. + bool passesAddressChecksum() const; + private: Token::Value m_token; ASTPointer m_value; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index cefd0603d..971e1f184 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -405,6 +405,14 @@ string IntegerType::toString(bool) const return prefix + dev::toString(m_bits); } +u256 IntegerType::literalValue(Literal const* _literal) const +{ + solAssert(m_modifier == Modifier::Address, ""); + solAssert(_literal, ""); + solAssert(_literal->value().substr(0, 2) == "0x", ""); + return u256(_literal->value()); +} + TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { if ( diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 1e94631e0..770cbb300 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -314,6 +314,8 @@ public: virtual std::string toString(bool _short) const override; + virtual u256 literalValue(Literal const* _literal) const override; + virtual TypePointer encodingType() const override { return shared_from_this(); } virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index fe0eeb1cd..bda4e04d8 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1308,6 +1308,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal) { case Type::Category::RationalNumber: case Type::Category::Bool: + case Type::Category::Integer: m_context << type->literalValue(&_literal); break; case Type::Category::StringLiteral: diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index ce241c78f..b6067ea5c 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4983,6 +4983,55 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor) success(text); } +BOOST_AUTO_TEST_CASE(address_checksum_type_deduction) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xfA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + x.send(2); + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(invalid_address_checksum) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + } + } + )"; + CHECK_WARNING(text, "checksum"); +} + +BOOST_AUTO_TEST_CASE(invalid_address_no_checksum) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e; + } + } + )"; + CHECK_WARNING(text, "checksum"); +} + +BOOST_AUTO_TEST_CASE(invalid_address_length) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + } + } + )"; + CHECK_WARNING(text, "checksum"); +} + BOOST_AUTO_TEST_SUITE_END() } From 605455f96b64f0c4b229cc31186f2dcf22433e6d Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 24 Jan 2017 23:36:35 +0100 Subject: [PATCH 125/414] Tests for library checksums. --- scripts/tests.sh | 16 ++------------ test/cmdlineTests.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 14 deletions(-) create mode 100755 test/cmdlineTests.sh diff --git a/scripts/tests.sh b/scripts/tests.sh index dfbda7342..cf18cb262 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -30,20 +30,8 @@ set -e REPO_ROOT="$(dirname "$0")"/.. - # Compile all files in std and examples. - -for f in "$REPO_ROOT"/std/*.sol -do - echo "Compiling $f..." - set +e - output=$("$REPO_ROOT"/build/solc/solc "$f" 2>&1) - failed=$? - # Remove the pre-release warning from the compiler output - output=$(echo "$output" | grep -v 'pre-release') - echo "$output" - set -e - test -z "$output" -a "$failed" -eq 0 -done +echo "Running commandline tests..." +"$REPO_ROOT/test/cmdlineTests.sh" # This conditional is only needed because we don't have a working Homebrew # install for `eth` at the time of writing, so we unzip the ZIP file locally diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh new file mode 100755 index 000000000..fc48654a0 --- /dev/null +++ b/test/cmdlineTests.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +#------------------------------------------------------------------------------ +# Bash script to run commandline Solidity tests. +# +# The documentation for solidity is hosted at: +# +# https://solidity.readthedocs.org +# +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2016 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +REPO_ROOT="$(dirname "$0")"/.. +SOLC="$REPO_ROOT/build/solc/solc" + + # Compile all files in std and examples. + +for f in "$REPO_ROOT"/std/*.sol +do + echo "Compiling $f..." + set +e + output=$("$SOLC" "$f" 2>&1) + failed=$? + # Remove the pre-release warning from the compiler output + output=$(echo "$output" | grep -v 'pre-release') + echo "$output" + set -e + test -z "$output" -a "$failed" -eq 0 +done + +# Test library checksum +echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 +! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null From 3949624a61b1dd0c32e67d30fe7d46b2511c583f Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 24 Jan 2017 23:36:07 +0100 Subject: [PATCH 126/414] Also check library addresses. --- libdevcore/CommonData.cpp | 40 +++++++++++++++++++++++++++++++++-- libdevcore/CommonData.h | 5 +++++ libsolidity/ast/AST.cpp | 22 +------------------ solc/CommandLineInterface.cpp | 5 +++++ 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 062d1b29c..a53d9628e 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -19,8 +19,12 @@ * @date 2014 */ -#include "CommonData.h" -#include "Exceptions.h" +#include +#include +#include + +#include + using namespace std; using namespace dev; @@ -95,3 +99,35 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw) } return ret; } + + +bool dev::passesAddressChecksum(string const& _str, bool _strict) +{ + string s = _str.substr(0, 2) == "0x" ? _str.substr(2) : _str; + + if (s.length() != 40) + return false; + + if (!_strict && ( + _str.find_first_of("abcdef") == string::npos || + _str.find_first_of("ABCDEF") == string::npos + )) + return true; + + h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic())); + for (size_t i = 0; i < 40; ++i) + { + char addressCharacter = s[i]; + bool lowerCase; + if ('a' <= addressCharacter && addressCharacter <= 'f') + lowerCase = true; + else if ('A' <= addressCharacter && addressCharacter <= 'F') + lowerCase = false; + else + continue; + unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf; + if ((nibble >= 8) == lowerCase) + return false; + } + return true; +} diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 5ffcdcca8..e0a6d2210 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -179,4 +179,9 @@ bool contains(T const& _t, V const& _v) return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v); } +/// @returns true iff @a _str passess the hex address checksum test. +/// @param _strict if false, hex strings with only uppercase or only lowercase letters +/// are considered valid. +bool passesAddressChecksum(std::string const& _str, bool _strict); + } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index e2b50dd71..616de54e1 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -539,25 +539,5 @@ bool Literal::passesAddressChecksum() const { string lit = value(); solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix"); - lit = lit.substr(2); - - if (lit.length() != 40) - return false; - - h256 hash = keccak256(boost::algorithm::to_lower_copy(lit, std::locale::classic())); - for (size_t i = 0; i < 40; ++i) - { - char addressCharacter = lit[i]; - bool lowerCase; - if ('a' <= addressCharacter && addressCharacter <= 'f') - lowerCase = true; - else if ('A' <= addressCharacter && addressCharacter <= 'F') - lowerCase = false; - else - continue; - unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf; - if ((nibble >= 8) == lowerCase) - return false; - } - return true; + return dev::passesAddressChecksum(lit, true); } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index e49e8517d..b3ffa6bb0 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -432,6 +432,11 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) string addrString(lib.begin() + colon + 1, lib.end()); boost::trim(libName); boost::trim(addrString); + if (!passesAddressChecksum(addrString, false)) + { + cerr << "Invalid checksum on library address \"" << libName << "\": " << addrString << endl; + return false; + } bytes binAddr = fromHex(addrString); h160 address(binAddr, h160::AlignRight); if (binAddr.size() > 20 || address == h160()) From d855eaab13392ef2ee3b75709c84b314d29afab9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 24 Jan 2017 23:10:01 +0100 Subject: [PATCH 127/414] Documentation. --- docs/types.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/types.rst b/docs/types.rst index 6b67e6840..cb14c8acc 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -171,6 +171,19 @@ Fixed Point Numbers **COMING SOON...** +.. index:: address, literal;address + +.. _address_literals: + +Address Literals +---------------- + +Hexadecimal literals that pass the address checksum test, for example +``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address`` type. +Hexadecimal literals that are 20 bytes long (differ from that in at +most one hexadecimal digit) but do not pass the checksum test produce +a warning and are treated as regular rational number literals. + .. index:: literal, literal;rational .. _rational_literals: From 87f744ae5e91353f3608f0f5b8c271918b198989 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 25 Jan 2017 10:45:21 +0100 Subject: [PATCH 128/414] Add some more tests for the checksum routine. --- test/libdevcore/Checksum.cpp | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 test/libdevcore/Checksum.cpp diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp new file mode 100644 index 000000000..322608884 --- /dev/null +++ b/test/libdevcore/Checksum.cpp @@ -0,0 +1,83 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * Unit tests for the address checksum. + */ + +#include + +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(Checksum) + +BOOST_AUTO_TEST_CASE(regular) +{ + BOOST_CHECK(passesAddressChecksum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true)); + BOOST_CHECK(passesAddressChecksum("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true)); + BOOST_CHECK(passesAddressChecksum("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true)); + BOOST_CHECK(passesAddressChecksum("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true)); +} + +BOOST_AUTO_TEST_CASE(regular_negative) +{ + BOOST_CHECK(!passesAddressChecksum("0x6aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true)); + BOOST_CHECK(!passesAddressChecksum("0xeB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true)); + BOOST_CHECK(!passesAddressChecksum("0xebF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true)); + BOOST_CHECK(!passesAddressChecksum("0xE1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true)); +} + +BOOST_AUTO_TEST_CASE(regular_invalid_length) +{ + BOOST_CHECK(passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad60", true)); + BOOST_CHECK(!passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad6", true)); + BOOST_CHECK(passesAddressChecksum("0x08A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true)); + BOOST_CHECK(!passesAddressChecksum("0x8A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true)); + BOOST_CHECK(passesAddressChecksum("0x00c40cC30cb4675673c9ee382de805c19734986A", true)); + BOOST_CHECK(!passesAddressChecksum("0xc40cC30cb4675673c9ee382de805c19734986A", true)); + BOOST_CHECK(passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a00", true)); + BOOST_CHECK(!passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a", true)); +} + +BOOST_AUTO_TEST_CASE(homocaps_valid) +{ + BOOST_CHECK(passesAddressChecksum("0x52908400098527886E0F7030069857D2E4169EE7", true)); + BOOST_CHECK(passesAddressChecksum("0x8617E340B3D01FA5F11F306F4090FD50E238070D", true)); + BOOST_CHECK(passesAddressChecksum("0xde709f2102306220921060314715629080e2fb77", true)); + BOOST_CHECK(passesAddressChecksum("0x27b1fdb04752bbc536007a920d24acb045561c26", true)); +} + +BOOST_AUTO_TEST_CASE(homocaps_invalid) +{ + string upper = "0x00AA0000000012400000000DDEEFF000000000BB"; + BOOST_CHECK(passesAddressChecksum(upper, false)); + BOOST_CHECK(!passesAddressChecksum(upper, true)); + string lower = "0x11aa000000000000000d00cc00000000000000bb"; + BOOST_CHECK(passesAddressChecksum(lower, false)); + BOOST_CHECK(!passesAddressChecksum(lower, true)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} From 5738e865d5375751332d8f884f2b00b006d76945 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Wed, 30 Nov 2016 15:04:07 +0100 Subject: [PATCH 129/414] Accept any kind of whitespace after natspec tags --- libsolidity/parsing/DocStringParser.cpp | 62 ++++++++++++++++++------ test/libsolidity/SolidityNatspecJSON.cpp | 23 +++++++++ 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index bbee35f56..4c59c47ce 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -16,16 +16,39 @@ static inline string::const_iterator skipLineOrEOS( return (_nlPos == _end) ? _end : ++_nlPos; } -static inline string::const_iterator firstSpaceOrNl( +static inline string::const_iterator firstSpaceOrTab( string::const_iterator _pos, string::const_iterator _end ) { auto spacePos = find(_pos, _end, ' '); - auto nlPos = find(_pos, _end, '\n'); - return (spacePos < nlPos) ? spacePos : nlPos; + auto tabPos = find(_pos, _end, '\t'); + return (spacePos < tabPos) ? spacePos : tabPos; } +static inline string::const_iterator firstWsOrNl( + string::const_iterator _pos, + string::const_iterator _end +) +{ + auto wsPos = firstSpaceOrTab(_pos, _end); + auto nlPos = find(wsPos, _end, '\n'); + return (wsPos < nlPos) ? wsPos : nlPos; +} + + +static inline string::const_iterator skipWhitespace( + string::const_iterator _pos, + string::const_iterator _end +) +{ + auto currPos = _pos; + while ((*currPos == ' ' || *currPos == '\t') && currPos != _end) + currPos += 1; + return currPos; +} + + bool DocStringParser::parse(string const& _docString, ErrorList& _errors) { m_errors = &_errors; @@ -43,7 +66,7 @@ bool DocStringParser::parse(string const& _docString, ErrorList& _errors) if (tagPos != end && tagPos < nlPos) { // we found a tag - auto tagNameEndPos = firstSpaceOrNl(tagPos, end); + auto tagNameEndPos = firstWsOrNl(tagPos, end); if (tagNameEndPos == end) { appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found"); @@ -75,7 +98,7 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo { solAssert(!!m_lastTag, ""); auto nlPos = find(_pos, _end, '\n'); - if (_appending && _pos < _end && *_pos != ' ') + if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t') m_lastTag->content += " "; copy(_pos, nlPos, back_inserter(m_lastTag->content)); return skipLineOrEOS(nlPos, _end); @@ -83,19 +106,30 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) { - // find param name - auto currPos = find(_pos, _end, ' '); - if (currPos == _end) + // find param name start + auto nameStartPos = skipWhitespace(_pos, _end); + if (nameStartPos == _end) { - appendError("End of param name not found" + string(_pos, _end)); + appendError("No param name given" + string(nameStartPos, _end)); + return _end; + } + auto nameEndPos = firstSpaceOrTab(nameStartPos, _end); + if (nameEndPos == _end) + { + appendError("End of param name not found" + string(nameStartPos, _end)); + return _end; + } + auto paramName = string(nameStartPos, nameEndPos); + + auto descStartPos = skipWhitespace(nameEndPos, _end); + if (descStartPos == _end) + { + appendError("No description given for param" + paramName); return _end; } - auto paramName = string(_pos, currPos); - - currPos += 1; - auto nlPos = find(currPos, _end, '\n'); - auto paramDesc = string(currPos, nlPos); + auto nlPos = find(descStartPos, _end, '\n'); + auto paramDesc = string(descStartPos, nlPos); newTag("param"); m_lastTag->paramName = paramName; m_lastTag->content = paramDesc; diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index e32264c4d..c59b30c34 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -251,6 +251,29 @@ BOOST_AUTO_TEST_CASE(dev_multiple_params) checkNatspec(sourceCode, natspec, false); } +BOOST_AUTO_TEST_CASE(dev_multiple_params_mixed_whitespace) +{ + char const* sourceCode = "contract test {\n" + " /// @dev Multiplies a number by 7 and adds second parameter\n" + " /// @param a Documentation for the first parameter\n" + " /// @param second Documentation for the second parameter\n" + " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" + "}\n"; + + char const* natspec = "{" + "\"methods\":{" + " \"mul(uint256,uint256)\":{ \n" + " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" + " \"params\": {\n" + " \"a\": \"Documentation for the first parameter\",\n" + " \"second\": \"Documentation for the second parameter\"\n" + " }\n" + " }\n" + "}}"; + + checkNatspec(sourceCode, natspec, false); +} + BOOST_AUTO_TEST_CASE(dev_mutiline_param_description) { char const* sourceCode = R"( From 9ca0fde853735d183c3db5c77c83de7c70c4ec98 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 1 Dec 2016 10:14:19 +0100 Subject: [PATCH 130/414] Fix and better output for tests --- libsolidity/parsing/DocStringParser.cpp | 6 ++++++ test/libsolidity/SolidityNatspecJSON.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index 4c59c47ce..5d9a75eff 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -99,7 +99,13 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo solAssert(!!m_lastTag, ""); auto nlPos = find(_pos, _end, '\n'); if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t') + { m_lastTag->content += " "; + } + else if (!_appending) + { + _pos = skipWhitespace(_pos, _end); + } copy(_pos, nlPos, back_inserter(m_lastTag->content)); return skipLineOrEOS(nlPos, _end); } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index c59b30c34..0657c321f 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -56,7 +56,7 @@ public: m_reader.parse(_expectedDocumentationString, expectedDocumentation); BOOST_CHECK_MESSAGE( expectedDocumentation == generatedDocumentation, - "Expected " << expectedDocumentation.toStyledString() << + "Expected:\n" << expectedDocumentation.toStyledString() << "\n but got:\n" << generatedDocumentation.toStyledString() ); } From 900c56d996472cb2053b69c7104ef007c13b1e80 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 25 Jan 2017 10:33:09 +0000 Subject: [PATCH 131/414] Do not allow shadowing inline assembly instructions with variables --- Changelog.md | 1 + libsolidity/inlineasm/AsmParser.cpp | 11 ++++++++++- libsolidity/inlineasm/AsmParser.h | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 7dd607d8d..60def101e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Features: Bugfixes: * Code generator: Allow recursive structs. + * Inline assembly: reject shadowing instructions by variables. * Type checker: Allow multiple events of the same name (but with different arities or argument types) ### 0.4.8 (2017-01-13) diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index ef3da2554..c0efb651e 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -130,7 +130,7 @@ assembly::Statement Parser::parseExpression() return operation; } -assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) +std::map Parser::getInstructions() { // Allowed instructions, lowercase names. static map s_instructions; @@ -151,6 +151,12 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) // add alias for selfdestruct s_instructions["selfdestruct"] = solidity::Instruction::SUICIDE; } + return s_instructions; +} + +assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) +{ + map s_instructions = getInstructions(); Statement ret; switch (m_scanner->currentToken()) @@ -204,9 +210,12 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) assembly::VariableDeclaration Parser::parseVariableDeclaration() { + map s_instructions = getInstructions(); VariableDeclaration varDecl = createWithLocation(); expectToken(Token::Let); varDecl.name = m_scanner->currentLiteral(); + if (s_instructions.count(varDecl.name)) + fatalParserError("Cannot shadow instructions with variable declaration."); expectToken(Token::Identifier); expectToken(Token::Colon); expectToken(Token::Assign); diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 8b56ab902..764c53f66 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -64,6 +64,7 @@ protected: Statement parseStatement(); /// Parses a functional expression that has to push exactly one stack element Statement parseExpression(); + std::map getInstructions(); Statement parseElementaryOperation(bool _onlySinglePusher = false); VariableDeclaration parseVariableDeclaration(); FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction); From 7ff44bec94dd72acb73c2caa26786113c24c8360 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 25 Jan 2017 10:37:33 +0000 Subject: [PATCH 132/414] Add test for shadowing inline assembly instruction --- test/libsolidity/SolidityParser.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index a3bfab757..227663588 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1479,6 +1479,19 @@ BOOST_AUTO_TEST_CASE(function_type_state_variable) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction) +{ + char const* text = R"( + contract A { + function f() { + assembly { + let gas := 1 + } + } + } + )"; + CHECK_PARSE_ERROR(text, "Cannot shadow instructions with variable declaration."); +} BOOST_AUTO_TEST_SUITE_END() From 8e318181e9719fda44899a402038013b757a4a60 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 25 Jan 2017 17:24:25 +0100 Subject: [PATCH 133/414] Rewording in changelog. --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 60def101e..e50cb3bdc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,7 +10,7 @@ Features: Bugfixes: * Code generator: Allow recursive structs. - * Inline assembly: reject shadowing instructions by variables. + * Inline assembly: Disallow variables named like opcodes. * Type checker: Allow multiple events of the same name (but with different arities or argument types) ### 0.4.8 (2017-01-13) From 27ba665694c4a961e098559cb36176aeafd5ec44 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 25 Jan 2017 17:24:43 +0100 Subject: [PATCH 134/414] Moved test. --- libsolidity/inlineasm/AsmParser.cpp | 2 +- test/libsolidity/InlineAssembly.cpp | 6 ++++++ test/libsolidity/SolidityParser.cpp | 14 -------------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index c0efb651e..cd3ea0da2 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -215,7 +215,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() expectToken(Token::Let); varDecl.name = m_scanner->currentLiteral(); if (s_instructions.count(varDecl.name)) - fatalParserError("Cannot shadow instructions with variable declaration."); + fatalParserError("Cannot use instruction names for identifier names."); expectToken(Token::Identifier); expectToken(Token::Colon); expectToken(Token::Assign); diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 64073edce..c2dac287b 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -182,6 +182,12 @@ BOOST_AUTO_TEST_CASE(error_tag) BOOST_CHECK(successAssemble("{ invalidJumpLabel }")); } +BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction) +{ + // Error message: "Cannot use instruction names for identifier names." + BOOST_CHECK(!successAssemble("{ let gas := 1 }")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 227663588..e5362e786 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1479,20 +1479,6 @@ BOOST_AUTO_TEST_CASE(function_type_state_variable) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction) -{ - char const* text = R"( - contract A { - function f() { - assembly { - let gas := 1 - } - } - } - )"; - CHECK_PARSE_ERROR(text, "Cannot shadow instructions with variable declaration."); -} - BOOST_AUTO_TEST_SUITE_END() } From a5696e1f0a87a2912cc64d2538751836658e7c63 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 25 Jan 2017 17:24:50 +0100 Subject: [PATCH 135/414] Renamed function. --- libsolidity/inlineasm/AsmParser.cpp | 8 ++++---- libsolidity/inlineasm/AsmParser.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index cd3ea0da2..048d916dd 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -130,7 +130,7 @@ assembly::Statement Parser::parseExpression() return operation; } -std::map Parser::getInstructions() +std::map const& Parser::instructions() { // Allowed instructions, lowercase names. static map s_instructions; @@ -156,7 +156,7 @@ std::map Parser::getInstructions() assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) { - map s_instructions = getInstructions(); + map const& s_instructions = instructions(); Statement ret; switch (m_scanner->currentToken()) @@ -178,7 +178,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) // first search the set of instructions. if (s_instructions.count(literal)) { - dev::solidity::Instruction const& instr = s_instructions[literal]; + dev::solidity::Instruction const& instr = s_instructions.at(literal); if (_onlySinglePusher) { InstructionInfo info = dev::solidity::instructionInfo(instr); @@ -210,7 +210,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) assembly::VariableDeclaration Parser::parseVariableDeclaration() { - map s_instructions = getInstructions(); + map const& s_instructions = instructions(); VariableDeclaration varDecl = createWithLocation(); expectToken(Token::Let); varDecl.name = m_scanner->currentLiteral(); diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 764c53f66..643548dd0 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -64,7 +64,7 @@ protected: Statement parseStatement(); /// Parses a functional expression that has to push exactly one stack element Statement parseExpression(); - std::map getInstructions(); + std::map const& instructions(); Statement parseElementaryOperation(bool _onlySinglePusher = false); VariableDeclaration parseVariableDeclaration(); FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction); From 946a63c26f0c03c31c6342acc65d099ffdc3691f Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 25 Jan 2017 17:27:01 +0100 Subject: [PATCH 136/414] Add test for assignment. --- test/libsolidity/InlineAssembly.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index c2dac287b..9ee848678 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -182,12 +182,18 @@ BOOST_AUTO_TEST_CASE(error_tag) BOOST_CHECK(successAssemble("{ invalidJumpLabel }")); } -BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction) +BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration) { // Error message: "Cannot use instruction names for identifier names." BOOST_CHECK(!successAssemble("{ let gas := 1 }")); } +BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment) +{ + // Error message: "Cannot use instruction names for identifier names." + BOOST_CHECK(!successAssemble("{ 2 =: gas }")); +} + BOOST_AUTO_TEST_SUITE_END() } From f62e269115822f791a0ad2e3699815f0725a0ef7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 25 Jan 2017 17:29:06 +0100 Subject: [PATCH 137/414] Disallow instructions in assignment. --- libsolidity/inlineasm/AsmParser.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 048d916dd..bed90139e 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -101,6 +101,8 @@ assembly::Statement Parser::parseStatement() { // functional assignment FunctionalAssignment funAss = createWithLocation(identifier.location); + if (instructions().count(identifier.name)) + fatalParserError("Cannot use instruction names for identifier names."); m_scanner->next(); funAss.variableName = identifier; funAss.value.reset(new Statement(parseExpression())); @@ -156,8 +158,6 @@ std::map const& Parser::instructions() assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) { - map const& s_instructions = instructions(); - Statement ret; switch (m_scanner->currentToken()) { @@ -176,9 +176,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) else literal = m_scanner->currentLiteral(); // first search the set of instructions. - if (s_instructions.count(literal)) + if (instructions().count(literal)) { - dev::solidity::Instruction const& instr = s_instructions.at(literal); + dev::solidity::Instruction const& instr = instructions().at(literal); if (_onlySinglePusher) { InstructionInfo info = dev::solidity::instructionInfo(instr); @@ -210,11 +210,10 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) assembly::VariableDeclaration Parser::parseVariableDeclaration() { - map const& s_instructions = instructions(); VariableDeclaration varDecl = createWithLocation(); expectToken(Token::Let); varDecl.name = m_scanner->currentLiteral(); - if (s_instructions.count(varDecl.name)) + if (instructions().count(varDecl.name)) fatalParserError("Cannot use instruction names for identifier names."); expectToken(Token::Identifier); expectToken(Token::Colon); From 19833c95ebae4f8b394d0c3a0cacadf766dc81fe Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Thu, 26 Jan 2017 13:17:05 +0100 Subject: [PATCH 138/414] Add ethereum-git to archlinux dependencies It's needed for testing, since we need to have the `eth` client installed --- scripts/install_deps.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 255176ab5..f21c48d03 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -138,11 +138,13 @@ 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 \ base-devel \ boost \ cmake \ git \ + ethereum-git \ ;; #------------------------------------------------------------------------------ From 525758a130941c6f1dfde8a0f884a550e7a7bb50 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 13:40:40 +0100 Subject: [PATCH 139/414] Disallow assignment to non-identifiers. --- libsolidity/inlineasm/AsmParser.cpp | 2 ++ test/libsolidity/InlineAssembly.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index bed90139e..7ddc6d047 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -71,6 +71,8 @@ assembly::Statement Parser::parseStatement() expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = m_scanner->currentLiteral(); + if (instructions().count(assignment.variableName.name)) + fatalParserError("Identifier expected."); assignment.location.end = endPosition(); expectToken(Token::Identifier); return assignment; diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 9ee848678..33721bbf6 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -194,6 +194,12 @@ BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment) BOOST_CHECK(!successAssemble("{ 2 =: gas }")); } +BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment) +{ + // Error message: "Cannot use instruction names for identifier names." + BOOST_CHECK(!successAssemble("{ gas := 2 }")); +} + BOOST_AUTO_TEST_SUITE_END() } From 1b097fd3c795dc5f0e100858a45b2efd8efc59b8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 13:45:23 +0100 Subject: [PATCH 140/414] Proper error reporting for assembly mode. --- solc/CommandLineInterface.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index e49e8517d..0a6c17fe0 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -921,6 +921,7 @@ bool CommandLineInterface::assemble() m_assemblyStacks[src.first].assemble(); } for (auto const& stack: m_assemblyStacks) + { for (auto const& error: stack.second.errors()) SourceReferenceFormatter::printExceptionInformation( cerr, @@ -928,6 +929,9 @@ bool CommandLineInterface::assemble() (error->type() == Error::Type::Warning) ? "Warning" : "Error", [&](string const& _source) -> Scanner const& { return *scanners.at(_source); } ); + if (!Error::containsOnlyWarnings(stack.second.errors())) + successful = false; + } return successful; } From 8e29d636f710819d082e7b01de42da960d9f5b51 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 13:47:57 +0100 Subject: [PATCH 141/414] Header cleanup. --- solc/CommandLineInterface.cpp | 47 ++++++++++++++++++----------------- solc/CommandLineInterface.h | 8 +++--- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 0a6c17fe0..771621d0a 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -22,28 +22,8 @@ */ #include "CommandLineInterface.h" -#ifdef _WIN32 // windows - #include - #define isatty _isatty - #define fileno _fileno -#else // unix - #include -#endif -#include -#include -#include - -#include -#include -#include - #include "solidity/BuildInfo.h" -#include -#include -#include -#include -#include -#include + #include #include #include @@ -54,9 +34,31 @@ #include #include #include -#include #include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef _WIN32 // windows + #include + #define isatty _isatty + #define fileno _fileno +#else // unix + #include +#endif +#include +#include +#include + using namespace std; namespace po = boost::program_options; @@ -907,7 +909,6 @@ void CommandLineInterface::writeLinkedFiles() bool CommandLineInterface::assemble() { - //@TODO later, we will use the convenience interface and should also remove the include above bool successful = true; map> scanners; for (auto const& src: m_sourceCodes) diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index b8fc1823b..bcfc43d73 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -21,12 +21,14 @@ */ #pragma once -#include -#include -#include #include #include +#include +#include + +#include + namespace dev { namespace solidity From 873f2dddd635cb246af4368b9950123fb36d9b28 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 13:52:02 +0100 Subject: [PATCH 142/414] Update error message. --- libsolidity/inlineasm/AsmParser.cpp | 2 +- test/libsolidity/InlineAssembly.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 7ddc6d047..fcc92dbbf 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -72,7 +72,7 @@ assembly::Statement Parser::parseStatement() assignment.variableName.location = location(); assignment.variableName.name = m_scanner->currentLiteral(); if (instructions().count(assignment.variableName.name)) - fatalParserError("Identifier expected."); + fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); return assignment; diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 33721bbf6..c051a9822 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration) BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment) { - // Error message: "Cannot use instruction names for identifier names." + // Error message: "Identifier expected, got instruction name." BOOST_CHECK(!successAssemble("{ 2 =: gas }")); } From f610ba77a493f8a6f270c72222d34eedfb183722 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 13:56:50 +0100 Subject: [PATCH 143/414] Simplify length rule. --- docs/types.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index cb14c8acc..3fb1db953 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -180,8 +180,8 @@ Address Literals Hexadecimal literals that pass the address checksum test, for example ``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address`` type. -Hexadecimal literals that are 20 bytes long (differ from that in at -most one hexadecimal digit) but do not pass the checksum test produce +Hexadecimal literals that are between 39 and 41 digits +long and do not pass the checksum test produce a warning and are treated as regular rational number literals. .. index:: literal, literal;rational From fcf483ee6b284f7e6d6f3c7f593cd95fb298da0d Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 17 Jan 2017 12:02:01 +0000 Subject: [PATCH 144/414] Add option to store literal sources in metadata --- libsolidity/interface/CompilerStack.cpp | 13 +++++++++---- libsolidity/interface/CompilerStack.h | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index b26bd5011..3335c40e4 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -726,10 +726,15 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const solAssert(s.second.scanner, "Scanner not available"); meta["sources"][s.first]["keccak256"] = "0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes()); - meta["sources"][s.first]["urls"] = Json::arrayValue; - meta["sources"][s.first]["urls"].append( - "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes()) - ); + if (m_metadataLiteralSources) + meta["sources"][s.first]["content"] = s.second.scanner->source(); + else + { + meta["sources"][s.first]["urls"] = Json::arrayValue; + meta["sources"][s.first]["urls"].append( + "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes()) + ); + } } meta["settings"]["optimizer"]["enabled"] = m_optimize; meta["settings"]["optimizer"]["runs"] = m_optimizeRuns; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 61edc284a..9ee70215e 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -177,6 +177,7 @@ public: /// Can be one of 4 types defined at @c DocumentationType Json::Value const& metadata(std::string const& _contractName, DocumentationType _type) const; std::string const& onChainMetadata(std::string const& _contractName) const; + void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& scanner(std::string const& _sourceName = "") const; @@ -274,6 +275,7 @@ private: std::map m_contracts; std::string m_formalTranslation; ErrorList m_errors; + bool m_metadataLiteralSources = false; }; } From 84bf547f21669fd89e2455463cbb43d965450d1e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 25 Jan 2017 12:45:18 +0000 Subject: [PATCH 145/414] Add option to solc to use literal sources in metadta --- solc/CommandLineInterface.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index e49e8517d..d33b87a8f 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -98,6 +98,7 @@ static string const g_strSrcMap = "srcmap"; static string const g_strSrcMapRuntime = "srcmap-runtime"; static string const g_strVersion = "version"; static string const g_stdinFileNameStr = ""; +static string const g_strMetadataLiteral = "metadata-literal"; static string const g_argAbi = g_strAbi; static string const g_argAddStandard = g_strAddStandard; @@ -126,6 +127,7 @@ static string const g_argOutputDir = g_strOutputDir; static string const g_argSignatureHashes = g_strSignatureHashes; static string const g_argVersion = g_strVersion; static string const g_stdinFileName = g_stdinFileNameStr; +static string const g_argMetadataLiteral = g_strMetadataLiteral; /// Possible arguments to for --combined-json static set const g_combinedJsonArgs{ @@ -511,7 +513,8 @@ Allowed options)", g_argLink.c_str(), "Switch to linker mode, ignoring all options apart from --libraries " "and modify binaries in place." - ); + ) + (g_argMetadataLiteral.c_str(), "Store referenced sources are literal data in the metadata output."); po::options_description outputComponents("Output Components"); outputComponents.add_options() (g_argAst.c_str(), "AST of all source files.") @@ -634,6 +637,8 @@ bool CommandLineInterface::processInput() auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); }; try { + if (m_args.count(g_argMetadataLiteral) > 0) + m_compiler->useMetadataLiteralSources(true); if (m_args.count(g_argInputFile)) m_compiler->setRemappings(m_args[g_argInputFile].as>()); for (auto const& sourceCode: m_sourceCodes) From 4b321c653cf5f8cee2d5c1db6668d1dcffebc17b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 26 Jan 2017 13:26:04 +0000 Subject: [PATCH 146/414] Add literal metadata sources to changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index e50cb3bdc..a8b66abc3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Features: * AST: Use deterministic node identifiers. * Type system: Introduce type identifier strings. * Metadata: Do not include platform in the version number. + * Metadata: Add option to store sources as literal content. * Code generator: Extract array utils into low-level functions. Bugfixes: From 9bcbd93ac59a19320fd56e27c58a6283f2450666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20W=C3=BCstholz?= Date: Sun, 22 Jan 2017 20:49:12 +0100 Subject: [PATCH 147/414] Change translation of implicit throws (issue #1589). This adds a new invalid instruction that is used for encoding implicit throws that are emitted by the compiler. This makes it possible to distinguish such runtime errors from user-provided, explicit throws. --- libevmasm/Instruction.cpp | 2 ++ libevmasm/Instruction.h | 2 ++ libsolidity/codegen/ArrayUtils.cpp | 2 +- libsolidity/codegen/CompilerContext.cpp | 12 ++++++++++++ libsolidity/codegen/CompilerContext.h | 4 ++++ libsolidity/codegen/CompilerUtils.cpp | 6 +++--- libsolidity/codegen/ContractCompiler.cpp | 8 +++++--- libsolidity/codegen/ExpressionCompiler.cpp | 12 ++++++------ test/libsolidity/Assembly.cpp | 4 ++-- test/libsolidity/SolidityExpressionCompiler.cpp | 14 ++++++++++++-- 10 files changed, 49 insertions(+), 17 deletions(-) diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 17445c599..ea5b5a108 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -154,6 +154,7 @@ const std::map dev::solidity::c_instructions = { "LOG2", Instruction::LOG2 }, { "LOG3", Instruction::LOG3 }, { "LOG4", Instruction::LOG4 }, + { "INVALID", Instruction::INVALID }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "CALLCODE", Instruction::CALLCODE }, @@ -288,6 +289,7 @@ static const std::map c_instructionInfo = { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } }, { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } }, { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } }, + { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } }, { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } }, { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 2dd451cdf..7432f04d9 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -171,6 +171,8 @@ enum class Instruction: uint8_t LOG3, ///< Makes a log entry; 3 topics. LOG4, ///< Makes a log entry; 4 topics. + INVALID = 0xef, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) + CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 4d100d1d2..bdd29abd8 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -901,7 +901,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c // check out-of-bounds access m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; // out-of-bounds access throws exception - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } if (location == DataLocation::CallData && _arrayType.isDynamicallySized()) // remove length if present diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index e26f96e8f..454503503 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -215,6 +215,18 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy return *this << item; } +CompilerContext& CompilerContext::appendInvalid() +{ + return *this << Instruction::INVALID; +} + +CompilerContext& CompilerContext::appendConditionalInvalid() +{ + eth::AssemblyItem falseTag = appendConditionalJump(); + eth::AssemblyItem endTag = appendJumpToNew(); + return *this << falseTag << Instruction::INVALID << endTag; +} + void CompilerContext::resetVisitedNodes(ASTNode const* _node) { stack newStack; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index f024b0103..58d6cb2ad 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -127,6 +127,10 @@ public: eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); + /// Appends an INVALID instruction + CompilerContext& appendInvalid(); + /// Appends a conditional INVALID instruction + CompilerContext& appendConditionalInvalid(); /// Returns an "ErrorTag" eth::AssemblyItem errorTag() { return m_asm->errorTag(); } /// Appends a JUMP to a specific tag diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index caf3b1acd..67877bbfc 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -468,7 +468,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp EnumType const& enumType = dynamic_cast(_typeOnStack); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; } break; @@ -497,7 +497,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp EnumType const& enumType = dynamic_cast(_targetType); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; } else if (targetTypeCategory == Type::Category::FixedPoint) @@ -807,7 +807,7 @@ void CompilerUtils::pushZeroValue(Type const& _type) { if (funType->location() == FunctionType::Location::Internal) { - m_context << m_context.errorTag(); + m_context.appendInvalid(); return; } } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 9dc1fb37c..56d03a05f 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -106,7 +106,7 @@ void ContractCompiler::appendCallValueCheck() { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) @@ -271,7 +271,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary()); } else - m_context.appendJumpTo(m_context.errorTag()); + m_context.appendInvalid(); for (auto const& it: interfaceFunctions) { @@ -918,7 +918,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() a << Instruction::DELEGATECALL; //Propagate error condition (if DELEGATECALL pushes 0 on stack). a << Instruction::ISZERO; - a.appendJumpI(a.errorTag()); + eth::AssemblyItem falseTag = a.appendJumpI(); + eth::AssemblyItem endTag = a.appendJump().tag(); + a << falseTag << Instruction::INVALID << endTag; //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; return make_shared(a); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index bda4e04d8..b66a3e129 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -585,7 +585,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::CREATE; // Check if zero (out of stack or not enough balance). m_context << Instruction::DUP1 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; break; @@ -1234,7 +1234,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) m_context << u256(fixedBytesType.numBytes()); m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; // out-of-bounds access throws exception - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); m_context << Instruction::BYTE; m_context << (u256(1) << (256 - 8)) << Instruction::MUL; @@ -1416,7 +1416,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty { // Test for division by zero m_context << Instruction::DUP2 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); if (_operator == Token::Div) m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); @@ -1477,7 +1477,7 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co if (c_amountSigned) { m_context << u256(0) << Instruction::DUP3 << Instruction::SLT; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } switch (_operator) @@ -1663,7 +1663,7 @@ void ExpressionCompiler::appendExternalFunctionCall( if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); existenceChecked = true; } @@ -1699,7 +1699,7 @@ void ExpressionCompiler::appendExternalFunctionCall( { //Propagate error condition (if CALL pushes 0 on stack). m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } utils().popStackSlots(remainsSize); diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 155dd5c9b..aed3c854b 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -116,8 +116,8 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr n = make_shared(""); AssemblyItems items = compileContract(sourceCode); vector locations = - vector(18, SourceLocation(2, 75, n)) + - vector(27, SourceLocation(20, 72, n)) + + vector(17, SourceLocation(2, 75, n)) + + vector(32, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector(2, SourceLocation(58, 67, n)) + vector(3, SourceLocation(20, 72, n)); diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 0c5a09c3e..ca6301696 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -337,13 +337,23 @@ BOOST_AUTO_TEST_CASE(arithmetics) byte(Instruction::ADD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x0, + byte(Instruction::PUSH1), 0x1e, byte(Instruction::JUMPI), + byte(Instruction::PUSH1), 0x20, + byte(Instruction::JUMP), + byte(Instruction::JUMPDEST), + byte(Instruction::INVALID), + byte(Instruction::JUMPDEST), byte(Instruction::MOD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x0, + byte(Instruction::PUSH1), 0x2a, byte(Instruction::JUMPI), + byte(Instruction::PUSH1), 0x2c, + byte(Instruction::JUMP), + byte(Instruction::JUMPDEST), + byte(Instruction::INVALID), + byte(Instruction::JUMPDEST), byte(Instruction::DIV), byte(Instruction::MUL)}); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); From 5b7cc018f0b256fb42f7bee38ad8d1ec4e2ec634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20W=C3=BCstholz?= Date: Mon, 23 Jan 2017 10:46:50 +0100 Subject: [PATCH 148/414] Address feedback from code review. --- docs/control-structures.rst | 2 +- libsolidity/codegen/CompilerContext.cpp | 6 +++--- libsolidity/codegen/ContractCompiler.cpp | 6 +++--- test/libsolidity/Assembly.cpp | 2 +- test/libsolidity/SolidityExpressionCompiler.cpp | 12 ++++-------- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 3f012b128..a398d8570 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -395,7 +395,7 @@ Currently, Solidity automatically generates a runtime exception in the following #. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). #. If your contract receives Ether via a public accessor function. -Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. +Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid (i.e., non-existent) operation if a runtime exception is encountered. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. .. index:: ! assembly, ! asm, ! evmasm diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 454503503..3bb6c9533 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -222,9 +222,9 @@ CompilerContext& CompilerContext::appendInvalid() CompilerContext& CompilerContext::appendConditionalInvalid() { - eth::AssemblyItem falseTag = appendConditionalJump(); - eth::AssemblyItem endTag = appendJumpToNew(); - return *this << falseTag << Instruction::INVALID << endTag; + *this << Instruction::ISZERO; + eth::AssemblyItem afterTag = appendConditionalJump(); + return *this << Instruction::INVALID << afterTag; } void CompilerContext::resetVisitedNodes(ASTNode const* _node) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 56d03a05f..3ca2f3758 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -918,9 +918,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() a << Instruction::DELEGATECALL; //Propagate error condition (if DELEGATECALL pushes 0 on stack). a << Instruction::ISZERO; - eth::AssemblyItem falseTag = a.appendJumpI(); - eth::AssemblyItem endTag = a.appendJump().tag(); - a << falseTag << Instruction::INVALID << endTag; + a << Instruction::ISZERO; + eth::AssemblyItem afterTag = a.appendJumpI(); + a << Instruction::INVALID << afterTag; //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; return make_shared(a); diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index aed3c854b..497bfc777 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(location_test) AssemblyItems items = compileContract(sourceCode); vector locations = vector(17, SourceLocation(2, 75, n)) + - vector(32, SourceLocation(20, 72, n)) + + vector(30, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector(2, SourceLocation(58, 67, n)) + vector(3, SourceLocation(20, 72, n)); diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index ca6301696..a769776e9 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -337,21 +337,17 @@ BOOST_AUTO_TEST_CASE(arithmetics) byte(Instruction::ADD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x1e, + byte(Instruction::ISZERO), + byte(Instruction::PUSH1), 0x1d, byte(Instruction::JUMPI), - byte(Instruction::PUSH1), 0x20, - byte(Instruction::JUMP), - byte(Instruction::JUMPDEST), byte(Instruction::INVALID), byte(Instruction::JUMPDEST), byte(Instruction::MOD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x2a, + byte(Instruction::ISZERO), + byte(Instruction::PUSH1), 0x26, byte(Instruction::JUMPI), - byte(Instruction::PUSH1), 0x2c, - byte(Instruction::JUMP), - byte(Instruction::JUMPDEST), byte(Instruction::INVALID), byte(Instruction::JUMPDEST), byte(Instruction::DIV), From c2b3d8bcd28a3047a832cf813df14a97a5b01daa Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 15:57:49 +0100 Subject: [PATCH 149/414] Change code for INVALID opcode to 0xfe. --- libevmasm/Instruction.cpp | 4 ++-- libevmasm/Instruction.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index ea5b5a108..b0f063dae 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -154,12 +154,12 @@ const std::map dev::solidity::c_instructions = { "LOG2", Instruction::LOG2 }, { "LOG3", Instruction::LOG3 }, { "LOG4", Instruction::LOG4 }, - { "INVALID", Instruction::INVALID }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "CALLCODE", Instruction::CALLCODE }, { "RETURN", Instruction::RETURN }, { "DELEGATECALL", Instruction::DELEGATECALL }, + { "INVALID", Instruction::INVALID }, { "SUICIDE", Instruction::SUICIDE } }; @@ -289,12 +289,12 @@ static const std::map c_instructionInfo = { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } }, { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } }, { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } }, - { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } }, { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } }, { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, + { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, Tier::Zero } } }; diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 7432f04d9..a8a72234d 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -171,13 +171,13 @@ enum class Instruction: uint8_t LOG3, ///< Makes a log entry; 3 topics. LOG4, ///< Makes a log entry; 4 topics. - INVALID = 0xef, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) - CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only RETURN, ///< halt execution returning output data DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender + + INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) SUICIDE = 0xff ///< halt execution and register account for later deletion }; From ae2b59d18a181bfbcb563b91866611d2e5e55b41 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 15:59:29 +0100 Subject: [PATCH 150/414] Fix optimizer with regards to INVALID instruction. --- libevmasm/PeepholeOptimiser.cpp | 1 + libevmasm/SemanticInformation.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 923ffa67d..528ce1c47 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -199,6 +199,7 @@ struct UnreachableCode it[0] != Instruction::JUMP && it[0] != Instruction::RETURN && it[0] != Instruction::STOP && + it[0] != Instruction::INVALID && it[0] != Instruction::SUICIDE ) return false; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 23a00d951..d3ce4735c 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -118,6 +118,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) case Instruction::RETURN: case Instruction::SUICIDE: case Instruction::STOP: + case Instruction::INVALID: return true; default: return false; From 390bebaaf9e2af51c7e2f72337d1e7b23f51486a Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 15:59:48 +0100 Subject: [PATCH 151/414] Split line. --- libsolidity/codegen/CompilerContext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3bb6c9533..7577a6067 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -224,7 +224,9 @@ CompilerContext& CompilerContext::appendConditionalInvalid() { *this << Instruction::ISZERO; eth::AssemblyItem afterTag = appendConditionalJump(); - return *this << Instruction::INVALID << afterTag; + *this << Instruction::INVALID; + *this << afterTag; + return *this; } void CompilerContext::resetVisitedNodes(ASTNode const* _node) From d9fbb83861153499b4aec5525db85ec59445abd1 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 16:35:51 +0100 Subject: [PATCH 152/414] Allow inserting low-level functions without calling them. --- libsolidity/codegen/CompilerContext.cpp | 21 ++++++++++++++++----- libsolidity/codegen/CompilerContext.h | 10 ++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 7577a6067..a83161098 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -70,19 +70,30 @@ void CompilerContext::callLowLevelFunction( eth::AssemblyItem retTag = pushNewTag(); CompilerUtils(*this).moveIntoStack(_inArgs); + *this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator); + + appendJump(eth::AssemblyItem::JumpType::IntoFunction); + adjustStackOffset(int(_outArgs) - 1 - _inArgs); + *this << retTag.tag(); +} + +eth::AssemblyItem CompilerContext::lowLevelFunctionTag( + string const& _name, + unsigned _inArgs, + unsigned _outArgs, + function const& _generator +) +{ auto it = m_lowLevelFunctions.find(_name); if (it == m_lowLevelFunctions.end()) { eth::AssemblyItem tag = newTag().pushTag(); m_lowLevelFunctions.insert(make_pair(_name, tag)); m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator)); - *this << tag; + return tag; } else - *this << it->second; - appendJump(eth::AssemblyItem::JumpType::IntoFunction); - adjustStackOffset(int(_outArgs) - 1 - _inArgs); - *this << retTag.tag(); + return it->second; } void CompilerContext::appendMissingLowLevelFunctions() diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 58d6cb2ad..c37142c91 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -104,6 +104,16 @@ public: unsigned _outArgs, std::function const& _generator ); + /// Returns the tag of the named low-level function and inserts the generator into the + /// list of low-level-functions to be generated, unless it already exists. + /// Note that the generator should not assume that objects are still alive when it is called, + /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example). + eth::AssemblyItem lowLevelFunctionTag( + std::string const& _name, + unsigned _inArgs, + unsigned _outArgs, + std::function const& _generator + ); /// Generates the code for missing low-level functions, i.e. calls the generators passed above. void appendMissingLowLevelFunctions(); From a98fa41897950b84b0217c9ce3c79c20009d0c8d Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 16:24:03 +0100 Subject: [PATCH 153/414] Uninitialized internal function should call INVALID. --- libsolidity/codegen/CompilerUtils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 67877bbfc..477f021a8 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -807,7 +807,9 @@ void CompilerUtils::pushZeroValue(Type const& _type) { if (funType->location() == FunctionType::Location::Internal) { - m_context.appendInvalid(); + m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { + _context.appendInvalid(); + }); return; } } From 7660736aa269c69a69ef728924f566d72661638a Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 16:28:44 +0100 Subject: [PATCH 154/414] Document special case of zero-initialized internal function. --- docs/control-structures.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index a398d8570..d7005717b 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -394,8 +394,13 @@ Currently, Solidity automatically generates a runtime exception in the following #. If you perform an external function call targeting a contract that contains no code. #. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). #. If your contract receives Ether via a public accessor function. +#. If you call a zero-initialized variable of internal function type. -Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid (i.e., non-existent) operation if a runtime exception is encountered. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. +Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation +(code ``0xfe``) if a runtime exception is encountered. In both cases, this causes +the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect +did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction +(or at least call) without effect. .. index:: ! assembly, ! asm, ! evmasm From a9c6ff4ac8c271aaada1965894b34933b662c044 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 26 Jan 2017 18:20:54 +0100 Subject: [PATCH 155/414] Refactor json return type generation. --- libsolidity/ast/Types.cpp | 18 ------- libsolidity/ast/Types.h | 2 - libsolidity/interface/InterfaceHandler.cpp | 48 +++++++++++-------- libsolidity/interface/InterfaceHandler.h | 10 ++++ .../SolidityNameAndTypeResolution.cpp | 22 ++++----- 5 files changed, 49 insertions(+), 51 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 971e1f184..dbabc8db5 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2512,24 +2512,6 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) ); } -vector const FunctionType::parameterTypeNames(bool _addDataLocation) const -{ - vector names; - for (TypePointer const& t: parameterTypes()) - names.push_back(t->canonicalName(_addDataLocation)); - - return names; -} - -vector const FunctionType::returnParameterTypeNames(bool _addDataLocation) const -{ - vector names; - for (TypePointer const& t: m_returnParameterTypes) - names.push_back(t->canonicalName(_addDataLocation)); - - return names; -} - TypePointer const& FunctionType::selfType() const { solAssert(bound(), "Function is not bound."); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 770cbb300..a5147f176 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -915,10 +915,8 @@ public: TypePointers parameterTypes() const; std::vector parameterNames() const; - std::vector const parameterTypeNames(bool _addDataLocation) const; TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; } std::vector const& returnParameterNames() const { return m_returnParameterNames; } - std::vector const returnParameterTypeNames(bool _addDataLocation) const; /// @returns the "self" parameter type for a bound function TypePointer const& selfType() const; diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 9944bb22d..6c1bb0c4d 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -30,20 +30,6 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe { Json::Value abi(Json::arrayValue); - auto populateParameters = [](vector const& _paramNames, vector const& _paramTypes) - { - Json::Value params(Json::arrayValue); - solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); - for (unsigned i = 0; i < _paramNames.size(); ++i) - { - Json::Value param; - param["name"] = _paramNames[i]; - param["type"] = _paramTypes[i]; - params.append(param); - } - return params; - }; - for (auto it: _contractDef.interfaceFunctions()) { auto externalFunctionType = it.second->interfaceFunctionType(); @@ -52,13 +38,15 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe method["name"] = it.second->declaration().name(); method["constant"] = it.second->isConstant(); method["payable"] = it.second->isPayable(); - method["inputs"] = populateParameters( + method["inputs"] = formatTypeList( externalFunctionType->parameterNames(), - externalFunctionType->parameterTypeNames(_contractDef.isLibrary()) + externalFunctionType->parameterTypes(), + _contractDef.isLibrary() ); - method["outputs"] = populateParameters( + method["outputs"] = formatTypeList( externalFunctionType->returnParameterNames(), - externalFunctionType->returnParameterTypeNames(_contractDef.isLibrary()) + externalFunctionType->returnParameterTypes(), + _contractDef.isLibrary() ); abi.append(method); } @@ -69,9 +57,10 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); solAssert(!!externalFunction, ""); method["payable"] = externalFunction->isPayable(); - method["inputs"] = populateParameters( + method["inputs"] = formatTypeList( externalFunction->parameterNames(), - externalFunction->parameterTypeNames(_contractDef.isLibrary()) + externalFunction->parameterTypes(), + _contractDef.isLibrary() ); abi.append(method); } @@ -179,6 +168,25 @@ Json::Value InterfaceHandler::devDocumentation(ContractDefinition const& _contra return doc; } +Json::Value InterfaceHandler::formatTypeList( + vector const& _names, + vector const& _types, + bool _forLibrary +) +{ + Json::Value params(Json::arrayValue); + solAssert(_names.size() == _types.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _names.size(); ++i) + { + solAssert(_types[i], ""); + Json::Value param; + param["name"] = _names[i]; + param["type"] = _types[i]->canonicalName(_forLibrary); + params.append(param); + } + return params; +} + string InterfaceHandler::extractDoc(multimap const& _tags, string const& _name) { string value; diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/InterfaceHandler.h index b7e1bb003..56927d44e 100644 --- a/libsolidity/interface/InterfaceHandler.h +++ b/libsolidity/interface/InterfaceHandler.h @@ -37,6 +37,8 @@ namespace solidity // Forward declarations class ContractDefinition; +class Type; +using TypePointer = std::shared_ptr; struct DocTag; enum class DocumentationType: uint8_t; @@ -84,6 +86,14 @@ public: static Json::Value devDocumentation(ContractDefinition const& _contractDef); private: + /// @returns a json value suitable for a list of types in function input or output + /// parameters or other places. If @a _forLibrary is true, complex types are referenced + /// by name, otherwise they are anonymously expanded. + static Json::Value formatTypeList( + std::vector const& _names, + std::vector const& _types, + bool _forLibrary + ); /// @returns concatenation of all content under the given tag name. static std::string extractDoc(std::multimap const& _tags, std::string const& _name); }; diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index b6067ea5c..472067ecd 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1102,25 +1102,25 @@ BOOST_AUTO_TEST_CASE(state_variable_accessors) BOOST_REQUIRE((contract = retrieveContract(source, 0)) != nullptr); FunctionTypePointer function = retrieveFunctionBySignature(*contract, "foo()"); BOOST_REQUIRE(function && function->hasDeclaration()); - auto returnParams = function->returnParameterTypeNames(false); - BOOST_CHECK_EQUAL(returnParams.at(0), "uint256"); + auto returnParams = function->returnParameterTypes(); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "uint256"); BOOST_CHECK(function->isConstant()); function = retrieveFunctionBySignature(*contract, "map(uint256)"); BOOST_REQUIRE(function && function->hasDeclaration()); - auto params = function->parameterTypeNames(false); - BOOST_CHECK_EQUAL(params.at(0), "uint256"); - returnParams = function->returnParameterTypeNames(false); - BOOST_CHECK_EQUAL(returnParams.at(0), "bytes4"); + auto params = function->parameterTypes(); + BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256"); + returnParams = function->returnParameterTypes(); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4"); BOOST_CHECK(function->isConstant()); function = retrieveFunctionBySignature(*contract, "multiple_map(uint256,uint256)"); BOOST_REQUIRE(function && function->hasDeclaration()); - params = function->parameterTypeNames(false); - BOOST_CHECK_EQUAL(params.at(0), "uint256"); - BOOST_CHECK_EQUAL(params.at(1), "uint256"); - returnParams = function->returnParameterTypeNames(false); - BOOST_CHECK_EQUAL(returnParams.at(0), "bytes4"); + params = function->parameterTypes(); + BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256"); + BOOST_CHECK_EQUAL(params.at(1)->canonicalName(false), "uint256"); + returnParams = function->returnParameterTypes(); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4"); BOOST_CHECK(function->isConstant()); } From cc7834f2a96e305bba3c7470271560dbf99d17f9 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Fri, 27 Jan 2017 00:00:05 +0100 Subject: [PATCH 156/414] Doc tags followed by newline are now parsed properly --- libsolidity/parsing/DocStringParser.cpp | 2 +- test/libsolidity/SolidityNatspecJSON.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index 5d9a75eff..481a33497 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -31,8 +31,8 @@ static inline string::const_iterator firstWsOrNl( string::const_iterator _end ) { + auto nlPos = find(_pos, _end, '\n'); auto wsPos = firstSpaceOrTab(_pos, _end); - auto nlPos = find(wsPos, _end, '\n'); return (wsPos < nlPos) ? wsPos : nlPos; } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index 0657c321f..42b989dd1 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(dev_desc_after_nl) char const* natspec = "{" "\"methods\":{" " \"mul(uint256,uint256)\":{ \n" - " \"details\": \" Multiplies a number by 7 and adds second parameter\",\n" + " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" " \"params\": {\n" " \"a\": \"Documentation for the first parameter\",\n" " \"second\": \"Documentation for the second parameter\"\n" @@ -402,7 +402,7 @@ BOOST_AUTO_TEST_CASE(dev_return_desc_after_nl) " \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" " \"second\": \"Documentation for the second parameter\"\n" " },\n" - " \"return\": \" The result of the multiplication\"\n" + " \"return\": \"The result of the multiplication\"\n" " }\n" "}}"; From 98b51b378e42ba67d727c0d2245314cdaab2fd53 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Fri, 27 Jan 2017 00:09:00 +0100 Subject: [PATCH 157/414] More verbose function naming --- libsolidity/parsing/DocStringParser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index 481a33497..c2af82de9 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -26,7 +26,7 @@ static inline string::const_iterator firstSpaceOrTab( return (spacePos < tabPos) ? spacePos : tabPos; } -static inline string::const_iterator firstWsOrNl( +static inline string::const_iterator firstWhitespaceOrNewline( string::const_iterator _pos, string::const_iterator _end ) @@ -66,7 +66,7 @@ bool DocStringParser::parse(string const& _docString, ErrorList& _errors) if (tagPos != end && tagPos < nlPos) { // we found a tag - auto tagNameEndPos = firstWsOrNl(tagPos, end); + auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end); if (tagNameEndPos == end) { appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found"); From bff8fc23e6cc602511b52aaa665e63b948eba068 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 27 Jan 2017 10:18:53 +0100 Subject: [PATCH 158/414] Changelog and review suggestions. --- Changelog.md | 2 ++ docs/control-structures.rst | 2 +- libsolidity/codegen/ContractCompiler.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 71a1e096a..c87d16477 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,8 @@ Features: * Metadata: Do not include platform in the version number. * Metadata: Add option to store sources as literal content. * Code generator: Extract array utils into low-level functions. + * Code generator: Internal errors (array out of bounds, etc.) now cause a reversion by using an invalid + instruction (0xfe) instead of an invalid jump. Invalid jump is still kept for explicit throws. Bugfixes: * Code generator: Allow recursive structs. diff --git a/docs/control-structures.rst b/docs/control-structures.rst index d7005717b..ff9b245a9 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -397,7 +397,7 @@ Currently, Solidity automatically generates a runtime exception in the following #. If you call a zero-initialized variable of internal function type. Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation -(code ``0xfe``) if a runtime exception is encountered. In both cases, this causes +(instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 3ca2f3758..4d33927db 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -919,7 +919,7 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() //Propagate error condition (if DELEGATECALL pushes 0 on stack). a << Instruction::ISZERO; a << Instruction::ISZERO; - eth::AssemblyItem afterTag = a.appendJumpI(); + eth::AssemblyItem afterTag = a.appendJumpI().tag(); a << Instruction::INVALID << afterTag; //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; From 0e021e76a58d0ae7a7fe1647f079e7e5087b4641 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 27 Jan 2017 11:19:48 +0100 Subject: [PATCH 159/414] Minor changes. --- libsolidity/parsing/DocStringParser.cpp | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index c2af82de9..8e9121266 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -1,14 +1,19 @@ #include -#include #include +#include +#include + using namespace std; using namespace dev; using namespace dev::solidity; -static inline string::const_iterator skipLineOrEOS( +namespace +{ + +string::const_iterator skipLineOrEOS( string::const_iterator _nlPos, string::const_iterator _end ) @@ -16,38 +21,35 @@ static inline string::const_iterator skipLineOrEOS( return (_nlPos == _end) ? _end : ++_nlPos; } -static inline string::const_iterator firstSpaceOrTab( +string::const_iterator firstSpaceOrTab( string::const_iterator _pos, string::const_iterator _end ) { - auto spacePos = find(_pos, _end, ' '); - auto tabPos = find(_pos, _end, '\t'); - return (spacePos < tabPos) ? spacePos : tabPos; + return boost::range::find_first_of(make_pair(_pos, _end), " \t"); } -static inline string::const_iterator firstWhitespaceOrNewline( +string::const_iterator firstWhitespaceOrNewline( string::const_iterator _pos, string::const_iterator _end ) { - auto nlPos = find(_pos, _end, '\n'); - auto wsPos = firstSpaceOrTab(_pos, _end); - return (wsPos < nlPos) ? wsPos : nlPos; + return boost::range::find_first_of(make_pair(_pos, _end), " \t\n"); } -static inline string::const_iterator skipWhitespace( +string::const_iterator skipWhitespace( string::const_iterator _pos, string::const_iterator _end ) { auto currPos = _pos; - while ((*currPos == ' ' || *currPos == '\t') && currPos != _end) + while (currPos != _end && (*currPos == ' ' || *currPos == '\t')) currPos += 1; return currPos; } +} bool DocStringParser::parse(string const& _docString, ErrorList& _errors) { @@ -99,13 +101,9 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo solAssert(!!m_lastTag, ""); auto nlPos = find(_pos, _end, '\n'); if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t') - { m_lastTag->content += " "; - } else if (!_appending) - { _pos = skipWhitespace(_pos, _end); - } copy(_pos, nlPos, back_inserter(m_lastTag->content)); return skipLineOrEOS(nlPos, _end); } @@ -116,13 +114,13 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) auto nameStartPos = skipWhitespace(_pos, _end); if (nameStartPos == _end) { - appendError("No param name given" + string(nameStartPos, _end)); + appendError("No param name given"); return _end; } auto nameEndPos = firstSpaceOrTab(nameStartPos, _end); if (nameEndPos == _end) { - appendError("End of param name not found" + string(nameStartPos, _end)); + appendError("End of param name not found: " + string(nameStartPos, _end)); return _end; } auto paramName = string(nameStartPos, nameEndPos); @@ -130,7 +128,7 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) auto descStartPos = skipWhitespace(nameEndPos, _end); if (descStartPos == _end) { - appendError("No description given for param" + paramName); + appendError("No description given for param " + paramName); return _end; } From f01c8c07e5647e02a45ad0802578153c7273668a Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Fri, 27 Jan 2017 12:13:14 +0100 Subject: [PATCH 160/414] Tests for natspect parsing failure cases --- test/libsolidity/SolidityNatspecJSON.cpp | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index 42b989dd1..ac55382bf 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -635,6 +635,48 @@ BOOST_AUTO_TEST_CASE(dev_documenting_nonexistent_param) expectNatspecError(sourceCode); } +BOOST_AUTO_TEST_CASE(dev_documenting_no_paramname) +{ + char const* sourceCode = R"( + contract test { + /// @dev Multiplies a number by 7 and adds second parameter + /// @param a Documentation for the first parameter + /// @param + function mul(uint a, uint second) returns(uint d) { return a * 7 + second; } + } + )"; + + expectNatspecError(sourceCode); +} + +BOOST_AUTO_TEST_CASE(dev_documenting_no_paramname_end) +{ + char const* sourceCode = R"( + contract test { + /// @dev Multiplies a number by 7 and adds second parameter + /// @param a Documentation for the first parameter + /// @param se + function mul(uint a, uint second) returns(uint d) { return a * 7 + second; } + } + )"; + + expectNatspecError(sourceCode); +} + +BOOST_AUTO_TEST_CASE(dev_documenting_no_param_description) +{ + char const* sourceCode = R"( + contract test { + /// @dev Multiplies a number by 7 and adds second parameter + /// @param a Documentation for the first parameter + /// @param second + function mul(uint a, uint second) returns(uint d) { return a * 7 + second; } + } + )"; + + expectNatspecError(sourceCode); +} + BOOST_AUTO_TEST_SUITE_END() } From c08f659634b5808a1283861939053df2584cab7d Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 27 Jan 2017 14:09:19 +0100 Subject: [PATCH 161/414] Changelog entry. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 7dd607d8d..4edf76f0e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ Features: Bugfixes: * Code generator: Allow recursive structs. * Type checker: Allow multiple events of the same name (but with different arities or argument types) + * Natspec parser: Fix error with ``@param`` parsing and whitespace. ### 0.4.8 (2017-01-13) From 965dc7201626ceb9f5406ca84189a478cd8a55bd Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 27 Jan 2017 13:24:05 +0000 Subject: [PATCH 162/414] Mention in changelog that invalid as an opcode is valid inline assembly --- Changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 8dd1b89c7..7a9d48180 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,13 +4,14 @@ Features: * Compiler interface: Contracts and libraries can be referenced with a ``file:`` prefix to make them unique. * Compiler interface: Report source location for "stack too deep" errors. * AST: Use deterministic node identifiers. + * Inline assembly: introduce ``invalid`` (EIP141) as an opcode. * Type system: Introduce type identifier strings. * Type checker: Warn about invalid checksum for addresses and deduce type from valid ones. * Metadata: Do not include platform in the version number. * Metadata: Add option to store sources as literal content. * Code generator: Extract array utils into low-level functions. * Code generator: Internal errors (array out of bounds, etc.) now cause a reversion by using an invalid - instruction (0xfe) instead of an invalid jump. Invalid jump is still kept for explicit throws. + instruction (0xfe - EIP141) instead of an invalid jump. Invalid jump is still kept for explicit throws. Bugfixes: * Code generator: Allow recursive structs. From bfa3b4ca78fd1afe0756631ce0d1ccb9a6d9c467 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 27 Jan 2017 13:26:40 +0000 Subject: [PATCH 163/414] Mention invalid in docs --- docs/control-structures.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index ff9b245a9..c83d654e7 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -632,6 +632,8 @@ The opcodes ``pushi`` and ``jumpdest`` cannot be used directly. +-------------------------+------+-----------------------------------------------------------------+ | selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a | +-------------------------+------+-----------------------------------------------------------------+ +| invalid | `-` | end execution with invalid instruction | ++-------------------------+------+-----------------------------------------------------------------+ | log0(p, s) | `-` | log without topics and data mem[p..(p+s)) | +-------------------------+------+-----------------------------------------------------------------+ | log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) | From eb530aa217387092a84057b550c7665a4acf72b6 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 27 Jan 2017 21:24:58 +0000 Subject: [PATCH 164/414] Add tests for invalid instruction --- test/libsolidity/InlineAssembly.cpp | 5 +++++ test/libsolidity/SolidityEndToEndTest.cpp | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index c051a9822..cf0343a92 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -182,6 +182,11 @@ BOOST_AUTO_TEST_CASE(error_tag) BOOST_CHECK(successAssemble("{ invalidJumpLabel }")); } +BOOST_AUTO_TEST_CASE(designated_invalid_instruction) +{ + BOOST_CHECK(successAssemble("{ invalid }")); +} + BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration) { // Error message: "Cannot use instruction names for identifier names." diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 646017fbf..4075a0160 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9043,6 +9043,21 @@ BOOST_AUTO_TEST_CASE(recursive_structs) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1))); } +BOOST_AUTO_TEST_CASE(invalid_instruction) +{ + char const* sourceCode = R"( + contract C { + function f() { + assembly { + invalid + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); +} + BOOST_AUTO_TEST_SUITE_END() } From b3f0d713a18b78b577e95175876646e53be5df41 Mon Sep 17 00:00:00 2001 From: VoR0220 Date: Mon, 30 Jan 2017 13:41:33 -0600 Subject: [PATCH 165/414] fix for linker wrt binaries generated with import statements Signed-off-by: VoR0220 --- solc/CommandLineInterface.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 2c1f06440..dd80e1894 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -426,7 +426,9 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) for (string const& lib: libraries) if (!lib.empty()) { - auto colon = lib.find(':'); + //search for last colon in string as our binaries output placeholders in the form of file:Name + //so we need to search for the second `:` in the string + auto colon = lib.rfind(':'); if (colon == string::npos) { cerr << "Colon separator missing in library address specifier \"" << lib << "\"" << endl; From 7b18c9df1dfa0076566bfa1e4a3bc5e5ba9c8594 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 31 Jan 2017 17:54:03 +0100 Subject: [PATCH 166/414] Release date for 0.4.9 --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 7a9d48180..03ee21937 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,4 @@ -### 0.4.9 (unreleased) +### 0.4.9 (2017-01-31) Features: * Compiler interface: Contracts and libraries can be referenced with a ``file:`` prefix to make them unique. From ffe4ce08912ffdf622e7e9666a875491c2284ac7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 31 Jan 2017 19:37:55 +0100 Subject: [PATCH 167/414] Version update --- CMakeLists.txt | 2 +- Changelog.md | 2 ++ docs/conf.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68bb71f4a..cea219ff8 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.9") +set(PROJECT_VERSION "0.4.10") project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies diff --git a/Changelog.md b/Changelog.md index 03ee21937..be0e83295 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,5 @@ +### 0.4.10 (unreleased) + ### 0.4.9 (2017-01-31) Features: diff --git a/docs/conf.py b/docs/conf.py index e97eff3a6..159cd3eab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,9 +56,9 @@ copyright = '2016-2017, Ethereum' # built documents. # # The short X.Y version. -version = '0.4.9' +version = '0.4.10' # The full version, including alpha/beta/rc tags. -release = '0.4.9-develop' +release = '0.4.10-develop' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From a39adc44d453e5f3ba9f6124a9b41128f2b37015 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 31 Jan 2017 23:31:25 +0100 Subject: [PATCH 168/414] Integrate missed changes. --- docs/assembly.rst | 42 +++- docs/control-structures.rst | 478 +----------------------------------- 2 files changed, 30 insertions(+), 490 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 57c0bf9b3..79137b7e9 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -37,8 +37,9 @@ arising when writing manual assembly by the following features: We now want to describe the inline assembly language in detail. .. warning:: - Inline assembly is still a relatively new feature and might change if it does not prove useful, - so please try to keep up to date. + Inline assembly is a way to access the Ethereum Virtual Machine + at a low level. This discards several important safety + features of Solidity. Example ------- @@ -49,6 +50,8 @@ idea is that assembly libraries will be used to enhance the language in such way .. code:: + pragma solidity ^0.4.0; + library GetCode { function at(address _addr) returns (bytes o_code) { assembly { @@ -69,11 +72,13 @@ idea is that assembly libraries will be used to enhance the language in such way Inline assembly could also be beneficial in cases where the optimizer fails to produce efficient code. Please be aware that assembly is much more difficult to write because -the compiler does not perform checks, so you should use it only if +the compiler does not perform checks, so you should use it for complex things only if you really know what you are doing. .. code:: + pragma solidity ^0.4.0; + library VectorSum { // This function is less efficient because the optimizer currently fails to // remove the bounds checks in array access. @@ -104,7 +109,7 @@ these curly braces, the following can be used (see the later sections for more d - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - opcodes (in "instruction style"), e.g. ``mload sload dup1 sstore``, for a list see below - - opcode in functional style, e.g. ``add(1, mlod(0))`` + - opcodes in functional style, e.g. ``add(1, mlod(0))`` - labels, e.g. ``name:`` - variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)`` - identifiers (labels or assembly-local variables and externals if used as inline assembly), e.g. ``jump(name)``, ``3 x add`` @@ -119,7 +124,7 @@ This document does not want to be a full description of the Ethereum virtual mac following list can be used as a reference of its opcodes. If an opcode takes arguments (always from the top of the stack), they are given in parentheses. -Note that the order of arguments can be seed to be reversed in non-functional style (explained below). +Note that the order of arguments can be seen to be reversed in non-functional style (explained below). Opcodes marked with ``-`` do not push an item onto the stack, those marked with ``*`` are special and all others push exactly one item onte the stack. @@ -185,7 +190,7 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+------+-----------------------------------------------------------------+ | pc | | current position in code | +-------------------------+------+-----------------------------------------------------------------+ -| pop | `*` | remove topmost stack slot | +| pop(x) | `-` | remove the element pushed by x | +-------------------------+------+-----------------------------------------------------------------+ | dup1 ... dup16 | | copy ith stack slot to the top (counting from top) | +-------------------------+------+-----------------------------------------------------------------+ @@ -230,19 +235,22 @@ In the grammar, opcodes are represented as pre-defined identifiers. | create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei | | | | and return the new address | +-------------------------+------+-----------------------------------------------------------------+ -| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)] | +| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)) | | insize, out, outsize) | | providing g gas and v wei and output area | -| | | mem[out..(out+outsize)] returting 1 on error (out of gas) | +| | | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) | +| | | and 1 on success | +-------------------------+------+-----------------------------------------------------------------+ -| callcode(g, a, v, in, | | identical to call but only use the code from a and stay | +| callcode(g, a, v, in, | | identical to `call` but only use the code from a and stay | | insize, out, outsize) | | in the context of the current contract otherwise | +-------------------------+------+-----------------------------------------------------------------+ -| delegatecall(g, a, in, | | identical to callcode but also keep ``caller`` | +| delegatecall(g, a, in, | | identical to `callcode` but also keep ``caller`` | | insize, out, outsize) | | and ``callvalue`` | +-------------------------+------+-----------------------------------------------------------------+ -| return(p, s) | `*` | end execution, return data mem[p..(p+s)) | +| return(p, s) | `-` | end execution, return data mem[p..(p+s)) | +-------------------------+------+-----------------------------------------------------------------+ -| selfdestruct(a) | `*` | end execution, destroy current contract and send funds to a | +| selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a | ++-------------------------+------+-----------------------------------------------------------------+ +| invalid | `-` | end execution with invalid instruction | +-------------------------+------+-----------------------------------------------------------------+ | log0(p, s) | `-` | log without topics and data mem[p..(p+s)) | +-------------------------+------+-----------------------------------------------------------------+ @@ -331,6 +339,8 @@ It is planned that the stack height changes can be specified in inline assembly. .. code:: + pragma solidity ^0.4.0; + contract C { uint b; function f(uint x) returns (uint r) { @@ -392,6 +402,10 @@ will have a wrong impression about the stack height at label ``two``: three: } +.. note:: + + ``invalidJumpLabel`` is a pre-defined label. Jumping to this location will always + result in an invalid jump, effectively aborting execution of the code. Declaring Assembly-Local Variables ---------------------------------- @@ -405,6 +419,8 @@ be just ``0``, but it can also be a complex functional-style expression. .. code:: + pragma solidity ^0.4.0; + contract C { function f(uint x) returns (uint b) { assembly { @@ -542,7 +558,7 @@ Conventions in Solidity In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits, e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just -treat them as 256 bit numbers and the higher-order bits are only cleaned at the +treat them as 256-bit numbers and the higher-order bits are only cleaned at the point where it is necessary, i.e. just shortly before they are written to memory or before comparisons are performed. This means that if you access such a variable from within inline assembly, you might have to manually clean the higher order bits diff --git a/docs/control-structures.rst b/docs/control-structures.rst index c83d654e7..757988cca 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -400,480 +400,4 @@ Internally, Solidity performs an "invalid jump" when a user-provided exception i (instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction -(or at least call) without effect. - -.. index:: ! assembly, ! asm, ! evmasm - -Inline Assembly -=============== - -For more fine-grained control especially in order to enhance the language by writing libraries, -it is possible to interleave Solidity statements with inline assembly in a language close -to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is -often hard to address the correct stack slot and provide arguments to opcodes at the correct -point on the stack. Solidity's inline assembly tries to facilitate that and other issues -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) } }`` -* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`` - -We now want to describe the inline assembly language in detail. - -.. warning:: - Inline assembly is a way to access the Ethereum Virtual Machine - at a low level. This discards several important safety - features of Solidity. - -Example -------- - -The following example provides library code to access the code of another contract and -load it into a ``bytes`` variable. This is not possible at all with "plain Solidity" and the -idea is that assembly libraries will be used to enhance the language in such ways. - -.. code:: - - pragma solidity ^0.4.0; - - library GetCode { - function at(address _addr) returns (bytes o_code) { - assembly { - // retrieve the size of the code, this needs assembly - let size := extcodesize(_addr) - // allocate output byte array - this could also be done without assembly - // by using o_code = new bytes(size) - o_code := mload(0x40) - // new "memory end" including padding - mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - // store length in memory - mstore(o_code, size) - // actually retrieve the code, this needs assembly - extcodecopy(_addr, add(o_code, 0x20), 0, size) - } - } - } - -Inline assembly could also be beneficial in cases where the optimizer fails to produce -efficient code. Please be aware that assembly is much more difficult to write because -the compiler does not perform checks, so you should use it for complex things only if -you really know what you are doing. - -.. code:: - - pragma solidity ^0.4.0; - - 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) { - for (uint i = 0; i < _data.length; ++i) - o_sum += _data[i]; - } - - // 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) { - for (uint i = 0; i < _data.length; ++i) { - assembly { - o_sum := mload(add(add(_data, 0x20), i)) - } - } - } - } - -Syntax ------- - -Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the -usual ``//`` and ``/* */`` comments. Inline assembly is initiated by ``assembly { ... }`` and inside -these curly braces, the following can be used (see the later sections for more details) - - - literals, e.g. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - - opcodes (in "instruction style"), e.g. ``mload sload dup1 sstore``, for a list see below - - opcodes in functional style, e.g. ``add(1, mload(0))`` - - labels, e.g. ``name:`` - - variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)`` - - identifiers (externals, labels or assembly-local variables), e.g. ``jump(name)``, ``3 x add`` - - assignments (in "instruction style"), e.g. ``3 =: x`` - - assignments in functional style, e.g. ``x := add(y, 3)`` - - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` - -Opcodes -------- - -This document does not want to be a full description of the Ethereum virtual machine, but the -following list can be used as a reference of its opcodes. - -If an opcode takes arguments (always from the top of the stack), they are given in parentheses. -Note that the order of arguments can be seen as being reversed compared to the instructional style (explained below). -Opcodes marked with ``-`` do not push an item onto the stack, those marked with ``*`` are -special and all others push exactly one item onte the stack. - -In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to -(excluding) position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``. - -The opcodes ``pushi`` and ``jumpdest`` cannot be used directly. - -+-------------------------+------+-----------------------------------------------------------------+ -| stop + `-` | stop execution, identical to return(0,0) | -+-------------------------+------+-----------------------------------------------------------------+ -| add(x, y) | | x + y | -+-------------------------+------+-----------------------------------------------------------------+ -| sub(x, y) | | x - y | -+-------------------------+------+-----------------------------------------------------------------+ -| mul(x, y) | | x * y | -+-------------------------+------+-----------------------------------------------------------------+ -| div(x, y) | | x / y | -+-------------------------+------+-----------------------------------------------------------------+ -| sdiv(x, y) | | x / y, for signed numbers in two's complement | -+-------------------------+------+-----------------------------------------------------------------+ -| mod(x, y) | | x % y | -+-------------------------+------+-----------------------------------------------------------------+ -| smod(x, y) | | x % y, for signed numbers in two's complement | -+-------------------------+------+-----------------------------------------------------------------+ -| exp(x, y) | | x to the power of y | -+-------------------------+------+-----------------------------------------------------------------+ -| not(x) | | ~x, every bit of x is negated | -+-------------------------+------+-----------------------------------------------------------------+ -| lt(x, y) | | 1 if x < y, 0 otherwise | -+-------------------------+------+-----------------------------------------------------------------+ -| gt(x, y) | | 1 if x > y, 0 otherwise | -+-------------------------+------+-----------------------------------------------------------------+ -| slt(x, y) | | 1 if x < y, 0 otherwise, for signed numbers in two's complement | -+-------------------------+------+-----------------------------------------------------------------+ -| sgt(x, y) | | 1 if x > y, 0 otherwise, for signed numbers in two's complement | -+-------------------------+------+-----------------------------------------------------------------+ -| eq(x, y) | | 1 if x == y, 0 otherwise | -+-------------------------+------+-----------------------------------------------------------------+ -| iszero(x) | | 1 if x == 0, 0 otherwise | -+-------------------------+------+-----------------------------------------------------------------+ -| and(x, y) | | bitwise and of x and y | -+-------------------------+------+-----------------------------------------------------------------+ -| or(x, y) | | bitwise or of x and y | -+-------------------------+------+-----------------------------------------------------------------+ -| xor(x, y) | | bitwise xor of x and y | -+-------------------------+------+-----------------------------------------------------------------+ -| byte(n, x) | | nth byte of x, where the most significant byte is the 0th byte | -+-------------------------+------+-----------------------------------------------------------------+ -| addmod(x, y, m) | | (x + y) % m with arbitrary precision arithmetics | -+-------------------------+------+-----------------------------------------------------------------+ -| mulmod(x, y, m) | | (x * y) % m with arbitrary precision arithmetics | -+-------------------------+------+-----------------------------------------------------------------+ -| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant | -+-------------------------+------+-----------------------------------------------------------------+ -| sha3(p, n) | | keccak(mem[p...(p+n))) | -+-------------------------+------+-----------------------------------------------------------------+ -| jump(label) | `-` | jump to label / code position | -+-------------------------+------+-----------------------------------------------------------------+ -| jumpi(label, cond) | `-` | jump to label if cond is nonzero | -+-------------------------+------+-----------------------------------------------------------------+ -| pc | | current position in code | -+-------------------------+------+-----------------------------------------------------------------+ -| pop(x) | `-` | remove the element pushed by x | -+-------------------------+------+-----------------------------------------------------------------+ -| dup1 ... dup16 | | copy ith stack slot to the top (counting from top) | -+-------------------------+------+-----------------------------------------------------------------+ -| swap1 ... swap16 | `*` | swap topmost and ith stack slot below it | -+-------------------------+------+-----------------------------------------------------------------+ -| mload(p) | | mem[p..(p+32)) | -+-------------------------+------+-----------------------------------------------------------------+ -| mstore(p, v) | `-` | mem[p..(p+32)) := v | -+-------------------------+------+-----------------------------------------------------------------+ -| mstore8(p, v) | `-` | mem[p] := v & 0xff - only modifies a single byte | -+-------------------------+------+-----------------------------------------------------------------+ -| sload(p) | | storage[p] | -+-------------------------+------+-----------------------------------------------------------------+ -| sstore(p, v) | `-` | storage[p] := v | -+-------------------------+------+-----------------------------------------------------------------+ -| msize | | size of memory, i.e. largest accessed memory index | -+-------------------------+------+-----------------------------------------------------------------+ -| gas | | gas still available to execution | -+-------------------------+------+-----------------------------------------------------------------+ -| address | | address of the current contract / execution context | -+-------------------------+------+-----------------------------------------------------------------+ -| balance(a) | | wei balance at address a | -+-------------------------+------+-----------------------------------------------------------------+ -| caller | | call sender (excluding delegatecall) | -+-------------------------+------+-----------------------------------------------------------------+ -| callvalue | | wei sent together with the current call | -+-------------------------+------+-----------------------------------------------------------------+ -| calldataload(p) | | calldata starting from position p (32 bytes) | -+-------------------------+------+-----------------------------------------------------------------+ -| calldatasize | | size of calldata in bytes | -+-------------------------+------+-----------------------------------------------------------------+ -| calldatacopy(t, f, s) | `-` | copy s bytes from calldata at position f to mem at position t | -+-------------------------+------+-----------------------------------------------------------------+ -| codesize | | size of the code of the current contract / execution context | -+-------------------------+------+-----------------------------------------------------------------+ -| codecopy(t, f, s) | `-` | copy s bytes from code at position f to mem at position t | -+-------------------------+------+-----------------------------------------------------------------+ -| extcodesize(a) | | size of the code at address a | -+-------------------------+------+-----------------------------------------------------------------+ -| extcodecopy(a, t, f, s) | `-` | like codecopy(t, f, s) but take code at address a | -+-------------------------+------+-----------------------------------------------------------------+ -| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei | -| | | and return the new address | -+-------------------------+------+-----------------------------------------------------------------+ -| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)) | -| insize, out, outsize) | | providing g gas and v wei and output area | -| | | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) | -| | | and 1 on success | -+-------------------------+------+-----------------------------------------------------------------+ -| callcode(g, a, v, in, | | identical to `call` but only use the code from a and stay | -| insize, out, outsize) | | in the context of the current contract otherwise | -+-------------------------+------+-----------------------------------------------------------------+ -| delegatecall(g, a, in, | | identical to `callcode` but also keep ``caller`` | -| insize, out, outsize) | | and ``callvalue`` | -+-------------------------+------+-----------------------------------------------------------------+ -| return(p, s) | `-` | end execution, return data mem[p..(p+s)) | -+-------------------------+------+-----------------------------------------------------------------+ -| selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a | -+-------------------------+------+-----------------------------------------------------------------+ -| invalid | `-` | end execution with invalid instruction | -+-------------------------+------+-----------------------------------------------------------------+ -| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) | -+-------------------------+------+-----------------------------------------------------------------+ -| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) | -+-------------------------+------+-----------------------------------------------------------------+ -| log2(p, s, t1, t2) | `-` | log with topics t1, t2 and data mem[p..(p+s)) | -+-------------------------+------+-----------------------------------------------------------------+ -| log3(p, s, t1, t2, t3) | `-` | log with topics t1, t2, t3 and data mem[p..(p+s)) | -+-------------------------+------+-----------------------------------------------------------------+ -| log4(p, s, t1, t2, t3, | `-` | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | -| t4) | | | -+-------------------------+------+-----------------------------------------------------------------+ -| origin | | transaction sender | -+-------------------------+------+-----------------------------------------------------------------+ -| gasprice | | gas price of the transaction | -+-------------------------+------+-----------------------------------------------------------------+ -| blockhash(b) | | hash of block nr b - only for last 256 blocks excluding current | -+-------------------------+------+-----------------------------------------------------------------+ -| coinbase | | current mining beneficiary | -+-------------------------+------+-----------------------------------------------------------------+ -| timestamp | | timestamp of the current block in seconds since the epoch | -+-------------------------+------+-----------------------------------------------------------------+ -| number | | current block number | -+-------------------------+------+-----------------------------------------------------------------+ -| difficulty | | difficulty of the current block | -+-------------------------+------+-----------------------------------------------------------------+ -| gaslimit | | block gas limit of the current block | -+-------------------------+------+-----------------------------------------------------------------+ - -Literals --------- - -You can use integer constants by typing them in decimal or hexadecimal notation and an -appropriate ``PUSHi`` instruction will automatically be generated. The following creates code -to add 2 and 3 resulting in 5 and then computes the bitwise and with the string "abc". -Strings are stored left-aligned and cannot be longer than 32 bytes. - -.. code:: - - assembly { 2 3 add "abc" and } - -Functional Style ------------------ - -You can type opcode after opcode in the same way they will end up in bytecode. For example -adding ``3`` to the contents in memory at position ``0x80`` would be - -.. code:: - - 3 0x80 mload add 0x80 mstore - -As it is often hard to see what the actual arguments for certain opcodes are, -Solidity inline assembly also provides a "functional style" notation where the same code -would be written as follows - -.. code:: - - mstore(0x80, add(mload(0x80), 3)) - -Functional style and instructional style can be mixed, but any opcode inside a -functional style expression has to return exactly one stack slot (most of the opcodes do). - -Note that the order of arguments is reversed in functional-style as opposed to the instruction-style -way. If you use functional-style, the first argument will end up on the stack top. - - -Access to External Variables and Functions ------------------------------------------- - -Solidity variables and other identifiers can be accessed by simply using their name. -For storage and memory variables, this will push the address and not the value onto the -stack. Also note that non-struct and non-array storage variable addresses occupy two slots -on the stack: One for the address and one for the byte offset inside the storage slot. -In assignments (see below), we can even use local Solidity variables to assign to. - -Functions external to inline assembly can also be accessed: The assembly will -push their entry label (with virtual function resolution applied). The calling semantics -in solidity are: - - - the caller pushes return label, arg1, arg2, ..., argn - - the call returns with ret1, ret2, ..., retn - -This feature is still a bit cumbersome to use, because the stack offset essentially -changes during the call, and thus references to local variables will be wrong. -It is planned that the stack height changes can be specified in inline assembly. - -.. code:: - - pragma solidity ^0.4.0; - - contract C { - uint b; - function f(uint x) returns (uint r) { - assembly { - b pop // remove the offset, we know it is zero - sload - x - mul - =: r // assign to return variable r - } - } - } - -Labels ------- - -Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses -which can change easily. Solidity inline assembly provides labels to make the use of -jumps easier. The following code computes an element in the Fibonacci series. - -.. code:: - - { - let n := calldataload(4) - let a := 1 - let b := a - loop: - jumpi(loopend, eq(n, 0)) - a add swap1 - n := sub(n, 1) - jump(loop) - loopend: - mstore(0, a) - return(0, 0x20) - } - -Please note that automatically accessing stack variables can only work if the -assembler knows the current stack height. This fails to work if the jump source -and target have different stack heights. It is still fine to use such jumps, -you should just not access any stack variables (even assembly variables) in that case. - -Furthermore, the stack height analyser goes through the code opcode by opcode -(and not according to control flow), so in the following case, the assembler -will have a wrong impression about the stack height at label ``two``: - -.. code:: - - { - jump(two) - one: - // Here the stack height is 1 (because we pushed 7), - // but the assembler thinks it is 0 because it reads - // from top to bottom. - // Accessing stack variables here will lead to errors. - jump(three) - two: - 7 // push something onto the stack - jump(one) - three: - } - -.. note:: - - ``invalidJumpLabel`` is a pre-defined label. Jumping to this location will always - result in an invalid jump, effectively aborting execution of the code. - -Declaring Assembly-Local Variables ----------------------------------- - -You can use the ``let`` keyword to declare variables that are only visible in -inline assembly and actually only in the current ``{...}``-block. What happens -is that the ``let`` instruction will create a new stack slot that is reserved -for the variable and automatically removed again when the end of the block -is reached. You need to provide an initial value for the variable which can -be just ``0``, but it can also be a complex functional-style expression. - -.. code:: - - pragma solidity ^0.4.0; - - contract C { - function f(uint x) returns (uint b) { - assembly { - let v := add(x, 1) - mstore(0x80, v) - { - let y := add(sload(v), 1) - b := y - } // y is "deallocated" here - b := add(b, v) - } // v is "deallocated" here - } - } - - -Assignments ------------ - -Assignments are possible to assembly-local variables and to function-local -variables. Take care that when you assign to variables that point to -memory or storage, you will only change the pointer and not the data. - -There are two kinds of assignments: Functional-style and instruction-style. -For functional-style assignments (``variable := value``), you need to provide a value in a -functional-style expression that results in exactly one stack value -and for instruction-style (``=: variable``), the value is just taken from the stack top. -For both ways, the colon points to the name of the variable. - -.. code:: - - assembly { - let v := 0 // functional-style assignment as part of variable declaration - let g := add(v, 2) - sload(10) - =: v // instruction style assignment, puts the result of sload(10) into v - } - - -Things to Avoid ---------------- - -Inline assembly might have a quite high-level look, but it actually is extremely -low-level. The only thing the assembler does for you is re-arranging -functional-style opcodes, managing jump labels, counting stack height for -variable access and removing stack slots for assembly-local variables when the end -of their block is reached. Especially for those two last cases, it is important -to know that the assembler only counts stack height from top to bottom, not -necessarily following control flow. Furthermore, operations like swap will only -swap the contents of the stack but not the location of variables. - -Conventions in Solidity ------------------------ - -In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits, -e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just -treat them as 256-bit numbers and the higher-order bits are only cleaned at the -point where it is necessary, i.e. just shortly before they are written to memory -or before comparisons are performed. This means that if you access such a variable -from within inline assembly, you might have to manually clean the higher order bits -first. - -Solidity manages memory in a very simple way: There is a "free memory pointer" -at position ``0x40`` in memory. If you want to allocate memory, just use the memory -from that point on and update the pointer accordingly. - -Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is -even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory -arrays are pointers to memory arrays. The length of a dynamic array is stored at the -first slot of the array and then only the array elements follow. - -.. warning:: - Statically-sized memory arrays do not have a length field, but it will be added soon - to allow better convertibility between statically- and dynamically-sized arrays, so - please do not rely on that. +(or at least call) without effect. \ No newline at end of file From 767ec1d670808cd479ac74780bff51a1f1900f04 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 1 Dec 2016 12:12:10 +0000 Subject: [PATCH 169/414] Support explicit conversion of external function type to address --- libsolidity/ast/Types.cpp | 11 +++++++++++ libsolidity/ast/Types.h | 1 + libsolidity/codegen/CompilerUtils.cpp | 12 ++++++++++++ libsolidity/codegen/ContractCompiler.cpp | 2 +- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index dbabc8db5..4a64b4c81 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2158,6 +2158,17 @@ bool FunctionType::operator==(Type const& _other) const return true; } +bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (m_location == Location::External && _convertTo.category() == Category::Integer) + { + IntegerType const& convertTo = dynamic_cast(_convertTo); + if (convertTo.isAddress()) + return true; + } + return _convertTo.category() == category(); +} + TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const { if (_operator == Token::Value::Delete) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index a5147f176..3917dca21 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -922,6 +922,7 @@ public: virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 477f021a8..469bd0edf 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -787,6 +787,18 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp if (_cleanupNeeded) m_context << Instruction::ISZERO << Instruction::ISZERO; break; + case Type::Category::Function: + { + solAssert(targetTypeCategory == Type::Category::Integer, "Invalid conversion for function type."); + IntegerType const& targetType = dynamic_cast(_targetType); + solAssert(targetType.isAddress(), "Function type can only be converted to address."); + FunctionType const& typeOnStack = dynamic_cast(_typeOnStack); + solAssert(typeOnStack.location() == FunctionType::Location::External, "Only external function type can be converted."); + + // stack:
+ m_context << Instruction::POP; + break; + } default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 4d33927db..f42799064 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -42,7 +42,7 @@ class StackHeightChecker public: StackHeightChecker(CompilerContext const& _context): m_context(_context), stackHeight(m_context.stackHeight()) {} - void check() { solAssert(m_context.stackHeight() == stackHeight, "I sense a disturbance in the stack."); } + void check() { solAssert(m_context.stackHeight() == stackHeight, "I sense a disturbance in the stack. "); } private: CompilerContext const& m_context; unsigned stackHeight; From ce62c7c01c01c859f6b310309a186a966e835d77 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 2 Dec 2016 13:58:14 +0000 Subject: [PATCH 170/414] Be more verbose on the stack-mismatch errors --- libsolidity/codegen/ContractCompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index f42799064..9d6129a36 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -42,7 +42,7 @@ class StackHeightChecker public: StackHeightChecker(CompilerContext const& _context): m_context(_context), stackHeight(m_context.stackHeight()) {} - void check() { solAssert(m_context.stackHeight() == stackHeight, "I sense a disturbance in the stack. "); } + void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + std::to_string(m_context.stackHeight()) + " vs " + std::to_string(stackHeight)); } private: CompilerContext const& m_context; unsigned stackHeight; From ef7add8c2bfacea43aa32455c80ac9575aa46a67 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 2 Dec 2016 21:53:36 +0000 Subject: [PATCH 171/414] Add tests for explicity fuction type to address casting --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 472067ecd..1aca7ab98 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4725,6 +4725,18 @@ BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(external_function_type_to_address) +{ + char const* text = R"( + contract C { + function f() return (address) { + return address(this.f); + } + } + )"; + CHECK_SUCCESS(text); +} + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( From 0b61f13c7f83667c524197fcfd53f04ab7645e1e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 27 Jan 2017 11:46:36 +0000 Subject: [PATCH 172/414] Add more tests for function type conversion --- test/libsolidity/SolidityEndToEndTest.cpp | 19 +++++++++++++++++++ .../SolidityNameAndTypeResolution.cpp | 14 +++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 4075a0160..4924b55d0 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8462,6 +8462,25 @@ BOOST_AUTO_TEST_CASE(function_array_cross_calls) BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(5), u256(6), u256(7))); } +BOOST_AUTO_TEST_CASE(external_function_to_address) +{ + char const* sourceCode = R"( + contract C { + function f() returns (bool) { + return address(this.f) == address(this); + } + function g(function() external cb) returns (address) { + return address(cb); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); + BOOST_CHECK(callContractFunction("g(function)", fromHex("00000000000000000000000000000000000004226121ff00000000000000000")) == encodeArgs(u160(0x42))); +} + + BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 1aca7ab98..587f10751 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4729,7 +4729,7 @@ BOOST_AUTO_TEST_CASE(external_function_type_to_address) { char const* text = R"( contract C { - function f() return (address) { + function f() returns (address) { return address(this.f); } } @@ -4737,6 +4737,18 @@ BOOST_AUTO_TEST_CASE(external_function_type_to_address) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(internal_function_type_to_address) +{ + char const* text = R"( + contract C { + function f() returns (address) { + return address(f); + } + } + )"; + CHECK_ERROR(text, TypeError, ""); +} + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( From 4361797ddca240dc55c9f88c6579383eb558b309 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 1 Feb 2017 12:22:14 +0000 Subject: [PATCH 173/414] Only capture function type to address conversion --- libsolidity/codegen/CompilerUtils.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 469bd0edf..9f019d279 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -789,15 +789,17 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp break; case Type::Category::Function: { - solAssert(targetTypeCategory == Type::Category::Integer, "Invalid conversion for function type."); - IntegerType const& targetType = dynamic_cast(_targetType); - solAssert(targetType.isAddress(), "Function type can only be converted to address."); - FunctionType const& typeOnStack = dynamic_cast(_typeOnStack); - solAssert(typeOnStack.location() == FunctionType::Location::External, "Only external function type can be converted."); + if (targetTypeCategory == Type::Category::Integer) + { + IntegerType const& targetType = dynamic_cast(_targetType); + solAssert(targetType.isAddress(), "Function type can only be converted to address."); + FunctionType const& typeOnStack = dynamic_cast(_typeOnStack); + solAssert(typeOnStack.location() == FunctionType::Location::External, "Only external function type can be converted."); - // stack:
- m_context << Instruction::POP; - break; + // stack:
+ m_context << Instruction::POP; + break; + } } default: // All other types should not be convertible to non-equal types. From bab7f8f455dae78c4da1019b131abd01d28f63a6 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 1 Feb 2017 12:25:02 +0000 Subject: [PATCH 174/414] Add changelog for function types to address conversion --- Changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog.md b/Changelog.md index be0e83295..c1098bdf2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ ### 0.4.10 (unreleased) +Features: + * Type system: Support explicit conversion of external function to address. + ### 0.4.9 (2017-01-31) Features: From ee147e14d392e62dae92d345735ec768bd486633 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 1 Feb 2017 20:47:56 +0000 Subject: [PATCH 175/414] Cover both failure cases --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 587f10751..f57680221 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4746,7 +4746,19 @@ BOOST_AUTO_TEST_CASE(internal_function_type_to_address) } } )"; - CHECK_ERROR(text, TypeError, ""); + CHECK_ERROR(text, TypeError, "Explicit type conversion not allowed"); +} + +BOOST_AUTO_TEST_CASE(external_function_type_to_uint) +{ + char const* text = R"( + contract C { + function f() returns (uint) { + return uint(this.f); + } + } + )"; + CHECK_ERROR(text, TypeError, "Explicit type conversion not allowed"); } BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) From c01f5699e673f600fe87056b414996fef2224242 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 00:16:50 +0000 Subject: [PATCH 176/414] Add isNegative to RationalNumberType --- libsolidity/ast/Types.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 3917dca21..e280b32c2 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -411,6 +411,9 @@ public: /// @returns true if the value is not an integer. bool isFractional() const { return m_value.denominator() != 1; } + /// @returns true if the value is negative. + bool isNegative() const { return m_value < 0; } + private: rational m_value; }; From 697db80b48fbed596996a2dab20948f42fdb1dfb Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 00:24:45 +0000 Subject: [PATCH 177/414] Disallow arrays with negative length --- Changelog.md | 3 +++ libsolidity/analysis/ReferencesResolver.cpp | 2 ++ test/libsolidity/SolidityNameAndTypeResolution.cpp | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/Changelog.md b/Changelog.md index c1098bdf2..79d2fe44a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,9 @@ Features: * Type system: Support explicit conversion of external function to address. +Bugfixes: + * Type system: Disallow arrays with negative length. + ### 0.4.9 (2017-01-31) Features: diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index df579c3d9..d589f4a0a 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -130,6 +130,8 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) auto const* lengthType = dynamic_cast(length->annotation().type.get()); if (!lengthType || lengthType->isFractional()) fatalTypeError(length->location(), "Invalid array length, expected integer literal."); + else if (lengthType->isNegative()) + fatalTypeError(length->location(), "Array with negative length specified."); else _typeName.annotation().type = make_shared(DataLocation::Storage, baseType, lengthType->literalValue(nullptr)); } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index f57680221..0151d244a 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1919,6 +1919,16 @@ BOOST_AUTO_TEST_CASE(array_with_nonconstant_length) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(array_with_negative_length) +{ + char const* text = R"( + contract c { + function f(uint a) { uint8[-1] x; } + } + )"; + CHECK_ERROR(text, TypeError, "Array with negative length specified"); +} + BOOST_AUTO_TEST_CASE(array_copy_with_different_types1) { char const* text = R"( From 902f69640b1e6c3e8d1769fa94b34b78186cb52a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 10:06:28 +0000 Subject: [PATCH 178/414] Replace cpp-ethereum with solidity in the license headers --- libdevcore/ABI.h | 8 ++++---- libdevcore/Assertions.h | 8 ++++---- libdevcore/Common.h | 8 ++++---- libdevcore/CommonData.cpp | 8 ++++---- libdevcore/CommonData.h | 8 ++++---- libdevcore/CommonIO.cpp | 8 ++++---- libdevcore/CommonIO.h | 8 ++++---- libdevcore/Exceptions.h | 8 ++++---- libdevcore/FixedHash.h | 8 ++++---- libdevcore/JSON.h | 8 ++++---- libdevcore/SHA3.cpp | 8 ++++---- libdevcore/SHA3.h | 8 ++++---- libdevcore/SwarmHash.cpp | 8 ++++---- libdevcore/SwarmHash.h | 8 ++++---- libdevcore/UTF8.cpp | 8 ++++---- libdevcore/UTF8.h | 8 ++++---- libdevcore/UndefMacros.h | 8 ++++---- liblll/CodeFragment.cpp | 8 ++++---- liblll/CodeFragment.h | 8 ++++---- liblll/Compiler.cpp | 8 ++++---- liblll/Compiler.h | 8 ++++---- liblll/CompilerState.cpp | 8 ++++---- liblll/CompilerState.h | 8 ++++---- liblll/Exceptions.h | 8 ++++---- liblll/Parser.cpp | 8 ++++---- liblll/Parser.h | 8 ++++---- lllc/main.cpp | 8 ++++---- test/libdevcore/Checksum.cpp | 8 ++++---- test/libdevcore/SwarmHash.cpp | 8 ++++---- 29 files changed, 116 insertions(+), 116 deletions(-) diff --git a/libdevcore/ABI.h b/libdevcore/ABI.h index 423cfda8b..8b9e5c980 100644 --- a/libdevcore/ABI.h +++ b/libdevcore/ABI.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file ABI.h * @author Gav Wood diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h index 0fb5837c7..e54b9d556 100644 --- a/libdevcore/Assertions.h +++ b/libdevcore/Assertions.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** * @file Assertions.h diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 225f38ac0..dc981ff68 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file Common.h * @author Gav Wood diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index a53d9628e..14caf4947 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CommonData.cpp * @author Gav Wood diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index e0a6d2210..98ad548d7 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CommonData.h * @author Gav Wood diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 60ac518d4..8dbcb00a1 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CommonIO.cpp * @author Gav Wood diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index 8238fe0ff..d84362cfc 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CommonIO.h * @author Gav Wood diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index 667ec31cb..4622774a4 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file Exceptions.h * @author Gav Wood diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index a23aecc60..5b1c7acf5 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file FixedHash.h * @author Gav Wood diff --git a/libdevcore/JSON.h b/libdevcore/JSON.h index 9f7d9a038..8499d6239 100644 --- a/libdevcore/JSON.h +++ b/libdevcore/JSON.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file JSON.h * @date 2016 diff --git a/libdevcore/SHA3.cpp b/libdevcore/SHA3.cpp index 3b12f39f3..4d82ec85b 100644 --- a/libdevcore/SHA3.cpp +++ b/libdevcore/SHA3.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file SHA3.cpp * @author Gav Wood diff --git a/libdevcore/SHA3.h b/libdevcore/SHA3.h index c481bfc92..ce0755218 100644 --- a/libdevcore/SHA3.h +++ b/libdevcore/SHA3.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file SHA3.h * @author Gav Wood diff --git a/libdevcore/SwarmHash.cpp b/libdevcore/SwarmHash.cpp index aa98eafd6..781886681 100644 --- a/libdevcore/SwarmHash.cpp +++ b/libdevcore/SwarmHash.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file SwarmHash.cpp */ diff --git a/libdevcore/SwarmHash.h b/libdevcore/SwarmHash.h index f474ce119..a5da96f53 100644 --- a/libdevcore/SwarmHash.h +++ b/libdevcore/SwarmHash.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file SwarmHash.h */ diff --git a/libdevcore/UTF8.cpp b/libdevcore/UTF8.cpp index 1c7ed17c7..9fbf4b45a 100644 --- a/libdevcore/UTF8.cpp +++ b/libdevcore/UTF8.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file UTF8.cpp * @author Alex Beregszaszi diff --git a/libdevcore/UTF8.h b/libdevcore/UTF8.h index 753914e3f..1f755e70e 100644 --- a/libdevcore/UTF8.h +++ b/libdevcore/UTF8.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file UTF8.h * @author Alex Beregszaszi diff --git a/libdevcore/UndefMacros.h b/libdevcore/UndefMacros.h index 91249523b..d2da3323d 100644 --- a/libdevcore/UndefMacros.h +++ b/libdevcore/UndefMacros.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file UndefMacros.h * @author Lefteris diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index af7d7f0a6..2b8822a6d 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CodeFragment.cpp * @author Gav Wood diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h index 637d169fb..6622de3e7 100644 --- a/liblll/CodeFragment.h +++ b/liblll/CodeFragment.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CodeFragment.h * @author Gav Wood diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index cd909f347..ea8b27af8 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file Compiler.cpp * @author Gav Wood diff --git a/liblll/Compiler.h b/liblll/Compiler.h index 0fadd2785..04aa1e268 100644 --- a/liblll/Compiler.h +++ b/liblll/Compiler.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file Compiler.h * @author Gav Wood diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp index 91c2452d8..88e43e188 100644 --- a/liblll/CompilerState.cpp +++ b/liblll/CompilerState.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CompilerState.cpp * @author Gav Wood diff --git a/liblll/CompilerState.h b/liblll/CompilerState.h index bfe56f927..c29d3b7d3 100644 --- a/liblll/CompilerState.h +++ b/liblll/CompilerState.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file CompilerState.h * @author Gav Wood diff --git a/liblll/Exceptions.h b/liblll/Exceptions.h index aa4bf4e82..e8ca99db0 100644 --- a/liblll/Exceptions.h +++ b/liblll/Exceptions.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file Exceptions.h * @author Gav Wood diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp index ad5e1885b..44d2a2ae6 100644 --- a/liblll/Parser.cpp +++ b/liblll/Parser.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file Parser.cpp * @author Gav Wood diff --git a/liblll/Parser.h b/liblll/Parser.h index e45428882..694d0cc6e 100644 --- a/liblll/Parser.h +++ b/liblll/Parser.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file Parser.h * @author Gav Wood diff --git a/lllc/main.cpp b/lllc/main.cpp index 9763e820e..adf181c7d 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** @file main.cpp * @author Gav Wood diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp index 322608884..17a17d228 100644 --- a/test/libdevcore/Checksum.cpp +++ b/test/libdevcore/Checksum.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** * Unit tests for the address checksum. diff --git a/test/libdevcore/SwarmHash.cpp b/test/libdevcore/SwarmHash.cpp index 7f3186ace..1ed1da181 100644 --- a/test/libdevcore/SwarmHash.cpp +++ b/test/libdevcore/SwarmHash.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of solidity. - cpp-ethereum is free software: you can redistribute it and/or modify + 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. - cpp-ethereum is distributed in the hope that it will be useful, + 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 cpp-ethereum. If not, see . + along with solidity. If not, see . */ /** * Unit tests for the swarm hash computation routine. From 821314aa270fe8b83a05d0b68fefe80fdeeef76c Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 10:19:22 +0000 Subject: [PATCH 179/414] Explain the difference between solc and solcjs --- docs/installing-solidity.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 345780d71..66e8038f1 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -25,24 +25,25 @@ 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. - npm / Node.js ============= This is probably the most portable and most convenient way to install Solidity locally. A platform-independent JavaScript library is provided by compiling the C++ source -into JavaScript using Emscripten for browser-solidity and there is also an npm -package available. +into JavaScript using Emscripten. It can be used in projects directly (such as Browser-Solidity). +Please refer to the `solc-js `_ repository for instructions. -To install it, simply use +It also contains a commandline tool called `solcjs`, which can be installed via npm: .. code:: bash - npm install solc + npm install -g solc -Details about the usage of the Node.js package can be found in the -`solc-js repository `_. +.. note:: + + 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`. Docker ====== From 7ec3dd9bbc9f212b6f631db0e42fde47de091c63 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 01:29:09 +0000 Subject: [PATCH 180/414] More verbose log of when using invalid instructions --- libevmasm/Instruction.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index a8a72234d..be71a4994 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -202,28 +202,28 @@ inline unsigned getSwapNumber(Instruction _inst) /// @returns the PUSH<_number> instruction inline Instruction pushInstruction(unsigned _number) { - assertThrow(1 <= _number && _number <= 32, InvalidOpcode, "Invalid PUSH instruction requested."); + assertThrow(1 <= _number && _number <= 32, InvalidOpcode, std::string("Invalid PUSH instruction requested (") + std::to_string(_number) + ")."); return Instruction(unsigned(Instruction::PUSH1) + _number - 1); } /// @returns the DUP<_number> instruction inline Instruction dupInstruction(unsigned _number) { - assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid DUP instruction requested."); + assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid DUP instruction requested (") + std::to_string(_number) + ")."); return Instruction(unsigned(Instruction::DUP1) + _number - 1); } /// @returns the SWAP<_number> instruction inline Instruction swapInstruction(unsigned _number) { - assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid SWAP instruction requested."); + assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid SWAP instruction requested (") + std::to_string(_number) + ")."); return Instruction(unsigned(Instruction::SWAP1) + _number - 1); } /// @returns the LOG<_number> instruction inline Instruction logInstruction(unsigned _number) { - assertThrow(_number <= 4, InvalidOpcode, "Invalid LOG instruction requested."); + assertThrow(_number <= 4, InvalidOpcode, std::string("Invalid LOG instruction requested (") + std::to_string(_number) + ")."); return Instruction(unsigned(Instruction::LOG0) + _number); } From ba0015cf256b3cf5f90a0daf225072286d8d3da4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 01:29:24 +0000 Subject: [PATCH 181/414] Warn early when exhausting stack --- libsolidity/codegen/CompilerUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 9f019d279..42323abd2 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -200,6 +200,7 @@ void CompilerUtils::encodeToMemory( // leave end_of_mem as dyn head pointer m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; dynPointers++; + solAssert((argSize + dynPointers) < 16, "Stack too deep, try using less variables."); } else { From 51a150e82ae04c7470ad1aa8fdc07f45baf79b59 Mon Sep 17 00:00:00 2001 From: Will White Date: Thu, 2 Feb 2017 15:19:27 +0000 Subject: [PATCH 182/414] Other contracts can't write to a public variable The removed words implied that other contracts can write to a public variable. --- docs/introduction-to-smart-contracts.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 4c134abcf..524cbcb0e 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -109,8 +109,7 @@ that does not allow any arithmetic operations. It is suitable for storing addresses of contracts or keypairs belonging to external persons. The keyword ``public`` automatically generates a function that allows you to access the current value of the state variable. -Without this keyword, other contracts have no way to access the variable -and only the code of this contract can write to it. +Without this keyword, other contracts have no way to access the variable. The function will look something like this:: function minter() returns (address) { return minter; } From ace583d0a1ec21cc184fb5734a9fd8c2147391e4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 23:52:34 +0000 Subject: [PATCH 183/414] Rename accessor to getter --- docs/contracts.rst | 24 ++++++++++++------------ docs/control-structures.rst | 2 +- docs/frequently-asked-questions.rst | 2 +- docs/introduction-to-smart-contracts.rst | 2 +- docs/miscellaneous.rst | 4 ++-- docs/structure-of-a-contract.rst | 4 ++-- docs/types.rst | 10 +++++----- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index edc42c3d7..1516ba0bb 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -145,11 +145,11 @@ This means that cyclic creation dependencies are impossible. .. index:: ! visibility, external, public, private, internal -.. _visibility-and-accessors: +.. _visibility-and-getters: -************************ -Visibility and Accessors -************************ +********************** +Visibility and Getters +********************** Since Solidity knows two kinds of function calls (internal ones that do not create an actual EVM call (also called @@ -173,7 +173,7 @@ and the default is ``internal``. ``public``: Public functions are part of the contract interface and can be either called internally or via - messages. For public state variables, an automatic accessor + messages. For public state variables, an automatic getter function (see below) is generated. ``internal``: @@ -243,12 +243,12 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value } } -.. index:: ! accessor;function, ! function;accessor +.. index:: ! getter;function, ! function;getter -Accessor Functions -================== +Getter Functions +================ -The compiler automatically creates accessor functions for +The compiler automatically creates getter functions for all **public** state variables. For the contract given below, the compiler will generate a function called ``data`` that does not take any arguments and returns a ``uint``, the value of the state @@ -271,7 +271,7 @@ be done at declaration. } } -The accessor functions have external visibility. If the +The getter functions have external visibility. If the symbol is accessed internally (i.e. without ``this.``), it is evaluated as a state variable and if it is accessed externally (i.e. with ``this.``), it is evaluated as a function. @@ -462,7 +462,7 @@ Functions can be declared constant. These functions promise not to modify the st } .. note:: - Accessor methods are marked constant. + Getter methods are marked constant. .. warning:: The compiler does not enforce yet that a constant method is not modifying state. @@ -882,7 +882,7 @@ Inheriting Different Kinds of Members of the Same Name When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. -As an exception, a state variable accessor can override a public function. +As an exception, a state variable getter can override a public function. .. index:: ! contract;abstract, ! abstract contract diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 757988cca..1c7d71f26 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -393,7 +393,7 @@ Currently, Solidity automatically generates a runtime exception in the following #. If you convert a value too big or negative into an enum type. #. If you perform an external function call targeting a contract that contains no code. #. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). -#. If your contract receives Ether via a public accessor function. +#. If your contract receives Ether via a public getter function. #. If you call a zero-initialized variable of internal function type. Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index 43fba3322..d2fc5b1ce 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -641,7 +641,7 @@ Not yet, as this requires two levels of dynamic arrays (``string`` is a dynamic If you issue a call for an array, it is possible to retrieve the whole array? Or must you write a helper function for that? =========================================================================================================================== -The automatic accessor function for a public state variable of array type only returns +The automatic getter function for a public state variable of array type only returns individual elements. If you want to return the complete array, you have to manually write a function to do that. diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 524cbcb0e..f02447cf4 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -131,7 +131,7 @@ too far, though, as it is neither possible to obtain a list of all keys of a mapping, nor a list of all values. So either keep in mind (or better, keep a list or use a more advanced data type) what you added to the mapping or use it in a context where this is not needed, -like this one. The accessor function created by the ``public`` keyword +like this one. The getter function created by the ``public`` keyword is a bit more complex in this case. It roughly looks like the following:: diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 378c3c969..c5a0262ff 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -427,7 +427,7 @@ Tips and Tricks * Use ``delete`` on arrays to delete all its elements. * Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple SSTORE operations might be combined into a single (SSTORE costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! -* Make your state variables public - the compiler will create :ref:`getters ` for you for free. +* Make your state variables public - the compiler will create :ref:`getters ` for you for free. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * If your contract has a function called ``send`` but you want to use the built-in send-function, use ``address(contractVariable).send(amount)``. * Initialise storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});`` @@ -541,7 +541,7 @@ Function Visibility Specifiers return true; } -- ``public``: visible externally and internally (creates accessor function for storage/state variables) +- ``public``: visible externally and internally (creates getter function for storage/state variables) - ``private``: only visible in the current contract - ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``) - ``internal``: only visible internally diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index c7af0c8ce..24ef69a6e 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -28,7 +28,7 @@ State variables are values which are permanently stored in contract storage. } See the :ref:`types` section for valid state variable types and -:ref:`visibility-and-accessors` for possible choices for +:ref:`visibility-and-getters` for possible choices for visibility. .. _structure-functions: @@ -49,7 +49,7 @@ Functions are the executable units of code within a contract. } :ref:`function-calls` can happen internally or externally -and have different levels of visibility (:ref:`visibility-and-accessors`) +and have different levels of visibility (:ref:`visibility-and-getters`) towards other contracts. .. _structure-function-modifiers: diff --git a/docs/types.rst b/docs/types.rst index 3fb1db953..69c23e6e0 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -543,8 +543,8 @@ So ``bytes`` should always be preferred over ``byte[]`` because it is cheaper. that you are accessing the low-level bytes of the UTF-8 representation, and not the individual characters! -It is possible to mark arrays ``public`` and have Solidity create an accessor. -The numeric index will become a required parameter for the accessor. +It is possible to mark arrays ``public`` and have Solidity create a getter. +The numeric index will become a required parameter for the getter. .. index:: ! array;allocating, new @@ -792,11 +792,11 @@ Because of this, mappings do not have a length or a concept of a key or value be Mappings are only allowed for state variables (or as storage reference types in internal functions). -It is possible to mark mappings ``public`` and have Solidity create an accessor. -The ``_KeyType`` will become a required parameter for the accessor and it will +It is possible to mark mappings ``public`` and have Solidity create a getter. +The ``_KeyType`` will become a required parameter for the getter and it will return ``_ValueType``. -The ``_ValueType`` can be a mapping too. The accessor will have one parameter +The ``_ValueType`` can be a mapping too. The getter will have one parameter for each ``_KeyType``, recursively. :: From f50caa967c8cf0f2fa2688350f91dae3ef836c80 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 2 Feb 2017 18:00:46 -0600 Subject: [PATCH 184/414] implement a build script Signed-off-by: RJ Catalano --- .travis.yml | 6 +----- docs/installing-solidity.rst | 10 ++++++++-- scripts/build.sh | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100755 scripts/build.sh diff --git a/.travis.yml b/.travis.yml index 017f1a611..d4b37f200 100644 --- a/.travis.yml +++ b/.travis.yml @@ -148,11 +148,7 @@ install: - echo -n "$TRAVIS_COMMIT" > commit_hash.txt before_script: - test $TRAVIS_EMSCRIPTEN != On || ./scripts/build_emscripten.sh - - test $TRAVIS_RELEASE != On || (mkdir -p build - && cd build - && cmake .. -DCMAKE_BUILD_TYPE=$TRAVIS_BUILD_TYPE - && make -j2 - && cd .. + - test $TRAVIS_RELEASE != On || (./scripts/build.sh $TRAVIS_BUILD_TYPE && ./scripts/release.sh $ZIP_SUFFIX && ./scripts/create_source_tarball.sh ) script: diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 66e8038f1..bedf62ea0 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -198,7 +198,13 @@ Building Solidity is quite similar on Linux, macOS and other Unices: cd build cmake .. && make -And even on Windows: +or even easier: + +.. code:: bash + + ./scripts/build.sh + +And even for Windows: .. code:: bash @@ -251,4 +257,4 @@ Example: 3. a breaking change is introduced - version is bumped to 0.5.0 4. the 0.5.0 release is made -This behaviour works well with the version pragma. +This behaviour works well with the version pragma. \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 000000000..e056ae4a8 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ -z "$1" ]; then + BUILD_TYPE=Release +else + BUILD_TYPE="$1" +fi + +cd $(dirname "$0") +mkdir -p build +cd build +cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" +make -j2 + +if [ -z $CI ]; then + install solc/solc /usr/local/bin && install test/soltest /usr/local/bin +fi \ No newline at end of file From 60e884b0a39a5f116291ae03e98b228847831f2d Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 2 Feb 2017 18:38:38 -0600 Subject: [PATCH 185/414] clarified binaries installation Signed-off-by: RJ Catalano --- docs/installing-solidity.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index bedf62ea0..44a2d45fe 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -201,7 +201,8 @@ Building Solidity is quite similar on Linux, macOS and other Unices: or even easier: .. code:: bash - + + #note: this will install binaries solc and soltest at usr/local/bin ./scripts/build.sh And even for Windows: From 9f9807f95dcf3bed2055e4612bc2ef2bfa2c0c71 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 23:45:00 +0000 Subject: [PATCH 186/414] Remove obsolete esoteric features section --- docs/miscellaneous.rst | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index c5a0262ff..cc40d6a75 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -137,31 +137,6 @@ Different types have different rules for cleaning up invalid values: | | |will be thrown | +---------------+---------------+-------------------+ - -***************** -Esoteric Features -***************** - -There are some types in Solidity's type system that have no counterpart in the syntax. One of these types are the types of functions. But still, using ``var`` it is possible to have local variables of these types:: - - contract FunctionSelector { - function select(bool useB, uint x) returns (uint z) { - var f = a; - if (useB) f = b; - return f(x); - } - - function a(uint x) returns (uint z) { - return x * x; - } - - function b(uint x) returns (uint z) { - return 2 * x; - } - } - -Calling ``select(false, x)`` will compute ``x * x`` and ``select(true, x)`` will compute ``2 * x``. - .. index:: optimizer, common subexpression elimination, constant propagation ************************* From 46412473b6d198106a0b80dc79645eeb693f1492 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sun, 5 Feb 2017 19:19:29 +0000 Subject: [PATCH 187/414] Always escape filenames in solc --- Changelog.md | 1 + solc/CommandLineInterface.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Changelog.md b/Changelog.md index 79d2fe44a..038d944f6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Features: * Type system: Support explicit conversion of external function to address. Bugfixes: + * Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``). * Type system: Disallow arrays with negative length. ### 0.4.9 (2017-01-31) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index dd80e1894..26355353a 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -200,7 +200,7 @@ void CommandLineInterface::handleBinary(string const& _contract) if (m_args.count(g_argCloneBinary)) { if (m_args.count(g_argOutputDir)) - createFile(_contract + ".clone_bin", m_compiler->cloneObject(_contract).toHex()); + createFile(m_compiler->filesystemFriendlyName(_contract) + ".clone_bin", m_compiler->cloneObject(_contract).toHex()); else { cout << "Clone Binary: " << endl; @@ -210,7 +210,7 @@ void CommandLineInterface::handleBinary(string const& _contract) if (m_args.count(g_argBinaryRuntime)) { if (m_args.count(g_argOutputDir)) - createFile(_contract + ".bin-runtime", m_compiler->runtimeObject(_contract).toHex()); + createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin-runtime", m_compiler->runtimeObject(_contract).toHex()); else { cout << "Binary of the runtime part: " << endl; @@ -222,7 +222,7 @@ void CommandLineInterface::handleBinary(string const& _contract) void CommandLineInterface::handleOpcode(string const& _contract) { if (m_args.count(g_argOutputDir)) - createFile(_contract + ".opcode", solidity::disassemble(m_compiler->object(_contract).bytecode)); + createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", solidity::disassemble(m_compiler->object(_contract).bytecode)); else { cout << "Opcodes: " << endl; @@ -249,7 +249,7 @@ void CommandLineInterface::handleSignatureHashes(string const& _contract) out += toHex(it.first.ref()) + ": " + it.second->externalSignature() + "\n"; if (m_args.count(g_argOutputDir)) - createFile(_contract + ".signatures", out); + createFile(m_compiler->filesystemFriendlyName(_contract) + ".signatures", out); else cout << "Function signatures: " << endl << out; } @@ -261,7 +261,7 @@ void CommandLineInterface::handleOnChainMetadata(string const& _contract) string data = m_compiler->onChainMetadata(_contract); if (m_args.count("output-dir")) - createFile(_contract + "_meta.json", data); + createFile(m_compiler->filesystemFriendlyName(_contract) + "_meta.json", data); else cout << "Metadata: " << endl << data << endl; } @@ -302,7 +302,7 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co output = dev::jsonPrettyPrint(m_compiler->metadata(_contract, _type)); if (m_args.count(g_argOutputDir)) - createFile(_contract + suffix, output); + createFile(m_compiler->filesystemFriendlyName(_contract) + suffix, output); else { cout << title << endl; @@ -981,7 +981,7 @@ void CommandLineInterface::outputCompilationResults() { stringstream data; m_compiler->streamAssembly(data, contract, m_sourceCodes, m_args.count(g_argAsmJson)); - createFile(contract + (m_args.count(g_argAsmJson) ? "_evm.json" : ".evm"), data.str()); + createFile(m_compiler->filesystemFriendlyName(contract) + (m_args.count(g_argAsmJson) ? "_evm.json" : ".evm"), data.str()); } else { From a5d0fd9c8a21af4a524ae60470c9381e94af446a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sun, 5 Feb 2017 19:39:30 +0000 Subject: [PATCH 188/414] Do not create directories . and .. --- Changelog.md | 1 + solc/CommandLineInterface.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 038d944f6..0c4e83294 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Features: Bugfixes: * Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``). + * Commandline interface: Do not try creating paths ``.`` and ``..``. * Type system: Disallow arrays with negative length. ### 0.4.9 (2017-01-31) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 26355353a..6759727fe 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -461,7 +461,9 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da namespace fs = boost::filesystem; // create directory if not existent fs::path p(m_args.at(g_argOutputDir).as()); - fs::create_directories(p); + // Do not try creating the directory if the first item is . or .. + if (p.filename() != "." && p.filename() != "..") + fs::create_directories(p); string pathName = (p / _fileName).string(); ofstream outFile(pathName); outFile << _data; From e5e0eae057816c6ed7f53a3972b7dff3032348ea Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 16:00:29 +0000 Subject: [PATCH 189/414] Take documentation version numbers from CMake --- docs/conf.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 159cd3eab..ca8c0fecc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,6 +15,7 @@ import sys import os +import re # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -56,9 +57,14 @@ copyright = '2016-2017, Ethereum' # built documents. # # The short X.Y version. -version = '0.4.10' +with open('../CMakeLists.txt', 'r') as f: + version = re.search('PROJECT_VERSION "([^"]+)"', f.read()).group(1) # The full version, including alpha/beta/rc tags. -release = '0.4.10-develop' +if os.path.isfile('../prerelease.txt') != True or os.path.getsize('../prerelease.txt') == 0: + release = version +else: + # This is a prerelease version + release = version + '-develop' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 53c71c8be3ee6caa2f93d0745f2131d1ff6e3ff4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 15:17:23 +0000 Subject: [PATCH 190/414] Require node.js >= 5 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d4b37f200..c31d9b2b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ # You should have received a copy of the GNU General Public License # along with solidity. If not, see # -# (c) 2016 solidity contributors. +# (c) 2016-2017 solidity contributors. #------------------------------------------------------------------------------ language: cpp @@ -73,7 +73,7 @@ matrix: dist: trusty sudo: required compiler: gcc - node_js: stable + node_js: "5" services: - docker before_install: From 82a512fb2f81d776dead0c595677415b9fc4fa3f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 18:23:49 +0000 Subject: [PATCH 191/414] Add archlinux installation instructions --- docs/installing-solidity.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 44a2d45fe..42905ede4 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -83,6 +83,12 @@ If you want to use the cutting edge developer version: sudo apt-get update sudo apt-get install solc +Arch Linux also has packages, albeit limited to the latest development version: + +.. code:: bash + + pacman -S solidity-git + Homebrew is missing pre-built bottles at the time of writing, following a Jenkins to TravisCI migration, but Homebrew should still work just fine as a means to build-from-source. From 693226b1abc8e0557943482b9ca23b61e859ceba Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 18:35:18 +0000 Subject: [PATCH 192/414] Rename SUICIDE opcode to SELFDESTRUCT in libevmasm --- libevmasm/EVMSchedule.h | 2 +- libevmasm/GasMeter.h | 2 +- libevmasm/Instruction.cpp | 6 +++--- libevmasm/Instruction.h | 2 +- libevmasm/PeepholeOptimiser.cpp | 2 +- libevmasm/SemanticInformation.cpp | 2 +- libsolidity/codegen/ExpressionCompiler.cpp | 2 +- libsolidity/inlineasm/AsmParser.cpp | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libevmasm/EVMSchedule.h b/libevmasm/EVMSchedule.h index f882f0068..ce9003bdd 100644 --- a/libevmasm/EVMSchedule.h +++ b/libevmasm/EVMSchedule.h @@ -47,7 +47,7 @@ struct EVMSchedule unsigned callStipend = 2300; unsigned callValueTransferGas = 9000; unsigned callNewAccountGas = 25000; - unsigned suicideRefundGas = 24000; + unsigned selfdestructRefundGas = 24000; unsigned memoryGas = 3; unsigned quadCoeffDiv = 512; unsigned createDataGas = 200; diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 0bc10f1f2..8ade838a2 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -61,7 +61,7 @@ namespace GasCosts static unsigned const callStipend = 2300; static unsigned const callValueTransferGas = 9000; static unsigned const callNewAccountGas = 25000; - static unsigned const suicideRefundGas = 24000; + static unsigned const selfdestructRefundGas = 24000; static unsigned const memoryGas = 3; static unsigned const quadCoeffDiv = 512; static unsigned const createDataGas = 200; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index b0f063dae..f9ee9be1f 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -160,7 +160,7 @@ const std::map dev::solidity::c_instructions = { "RETURN", Instruction::RETURN }, { "DELEGATECALL", Instruction::DELEGATECALL }, { "INVALID", Instruction::INVALID }, - { "SUICIDE", Instruction::SUICIDE } + { "SELFDESTRUCT", Instruction::SELFDESTRUCT } }; static const std::map c_instructionInfo = @@ -293,9 +293,9 @@ static const std::map c_instructionInfo = { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } }, { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, - { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, + { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, - { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, Tier::Zero } } + { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } } }; void dev::solidity::eachInstruction( diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index be71a4994..7f56ad3a9 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -178,7 +178,7 @@ enum class Instruction: uint8_t DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) - SUICIDE = 0xff ///< halt execution and register account for later deletion + SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion }; /// @returns the number of PUSH Instruction _inst diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 528ce1c47..9a8341ab9 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -200,7 +200,7 @@ struct UnreachableCode it[0] != Instruction::RETURN && it[0] != Instruction::STOP && it[0] != Instruction::INVALID && - it[0] != Instruction::SUICIDE + it[0] != Instruction::SELFDESTRUCT ) return false; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index d3ce4735c..3a0843b8f 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -116,7 +116,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) case Instruction::JUMP: case Instruction::JUMPI: case Instruction::RETURN: - case Instruction::SUICIDE: + case Instruction::SELFDESTRUCT: case Instruction::STOP: case Instruction::INVALID: return true; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index b66a3e129..d74d9dd3d 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -648,7 +648,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case Location::Selfdestruct: arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); - m_context << Instruction::SUICIDE; + m_context << Instruction::SELFDESTRUCT; break; case Location::SHA3: { diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index fcc92dbbf..46a2730d9 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -152,8 +152,8 @@ std::map const& Parser::instructions() s_instructions[name] = instruction.second; } - // add alias for selfdestruct - s_instructions["selfdestruct"] = solidity::Instruction::SUICIDE; + // add alias for suicide + s_instructions["suicide"] = solidity::Instruction::SELFDESTRUCT; } return s_instructions; } From 9c3b28e21e78519c280c076b0756413422ad5eeb Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 17:40:26 +0000 Subject: [PATCH 193/414] Fix tests on mac (wc produces whitespace) --- scripts/tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tests.sh b/scripts/tests.sh index f21429464..ba932da59 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -35,7 +35,7 @@ echo "Running commandline tests..." echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode..." output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l) -test "$output" = "3" +test "${output//[[:blank:]]/}" = "3" # This conditional is only needed because we don't have a working Homebrew # install for `eth` at the time of writing, so we unzip the ZIP file locally From 1fc42d733df0d85eaf61495a53906ca65a9544be Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Fri, 3 Feb 2017 03:37:24 -0300 Subject: [PATCH 194/414] grammar.txt: Add rule for tuple destructuring --- docs/grammar.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/grammar.txt b/docs/grammar.txt index a9f328c06..dc1885729 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -68,7 +68,8 @@ Continue = 'continue' Break = 'break' Return = 'return' Expression? Throw = 'throw' -VariableDefinition = VariableDeclaration ( '=' Expression )? +VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )? +IdentifierList = '(' ( Identifier? ',' )* Identifier? ')' // Precedence by order (see github.com/ethereum/solidity/pull/732) Expression = From 82c2bf8ed263133b4228a9b1ee5e011251004f92 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 20:47:24 +0000 Subject: [PATCH 195/414] Ensure that all commands succed and move back to root --- scripts/build.sh | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index e056ae4a8..3785e1c13 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,17 +1,23 @@ #!/usr/bin/env bash -if [ -z "$1" ]; then +if [ -z "$1" ]; then BUILD_TYPE=Release -else +else BUILD_TYPE="$1" fi -cd $(dirname "$0") -mkdir -p build -cd build -cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" +cd $(dirname "$0")/.. && +mkdir -p build && +cd build && +cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" && make -j2 +if [ $? -ne 0 ]; then + echo "Failed to build" + exit 1 +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 From 43bae9dd0bd924ad4a7277ea3c0a8f69d85f4870 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 12:32:23 +0000 Subject: [PATCH 196/414] Ensure that a valid RPC response is received through IPC --- test/RPCSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 44d21d69a..f67696d96 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -302,7 +302,7 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector const& //cout << "Reply: " << reply << endl; Json::Value result; - Json::Reader().parse(reply, result, false); + BOOST_REQUIRE(Json::Reader().parse(reply, result, false)); if (result.isMember("error")) { From 3be6d105252d2153a6c05a4e788f615ed0921d6b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 12:32:58 +0000 Subject: [PATCH 197/414] Avoid crash if fdopen failed in IPC --- test/RPCSession.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index f67696d96..43f10824c 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -75,6 +75,8 @@ IPCSocket::IPCSocket(string const& _path): m_path(_path) BOOST_FAIL("Error connecting to IPC socket: " << _path); m_fp = fdopen(m_socket, "r"); + if (!m_fp) + BOOST_FAIL("Error opening IPC socket: " << _path); #endif } From f9357dbb22de8690940d605fc72ef6df4deb7f6b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 12:35:27 +0000 Subject: [PATCH 198/414] Check the return value of RPC calls --- test/RPCSession.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 43f10824c..ffdb48b1e 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -111,7 +111,9 @@ string IPCSocket::sendRequest(string const& _req) if (!fSuccess) BOOST_FAIL("ReadFile from pipe failed"); - cerr << "."; //Output for log activity + // This is needed for Appveyor, otherwise it may terminate + // the session due to the inactivity. + cerr << "."; return returnStr; #else send(m_socket, _req.c_str(), _req.length(), 0); @@ -189,7 +191,7 @@ string RPCSession::eth_getStorageRoot(string const& _address, string const& _blo void RPCSession::personal_unlockAccount(string const& _address, string const& _password, int _duration) { - rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }); + BOOST_CHECK(rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }) == true); } string RPCSession::personal_newAccount(string const& _password) @@ -233,18 +235,18 @@ void RPCSession::test_setChainParams(vector const& _accounts) void RPCSession::test_setChainParams(string const& _config) { - rpcCall("test_setChainParams", { _config }); + BOOST_CHECK(rpcCall("test_setChainParams", { _config }) == true); } void RPCSession::test_rewindToBlock(size_t _blockNr) { - rpcCall("test_rewindToBlock", { to_string(_blockNr) }); + BOOST_CHECK(rpcCall("test_rewindToBlock", { to_string(_blockNr) }) == true); } void RPCSession::test_mineBlocks(int _number) { u256 startBlock = fromBigEndian(fromHex(rpcCall("eth_blockNumber").asString())); - rpcCall("test_mineBlocks", { to_string(_number) }, true); + BOOST_CHECK(rpcCall("test_mineBlocks", { to_string(_number) }, true) == true); bool mined = false; @@ -283,7 +285,7 @@ void RPCSession::test_mineBlocks(int _number) void RPCSession::test_modifyTimestamp(size_t _timestamp) { - rpcCall("test_modifyTimestamp", { to_string(_timestamp) }); + BOOST_CHECK(rpcCall("test_modifyTimestamp", { to_string(_timestamp) }) == true); } Json::Value RPCSession::rpcCall(string const& _methodName, vector const& _args, bool _canFail) From af6ab7fa916ac0f56c2caeae03be47bbefd8a50f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 12:31:26 +0000 Subject: [PATCH 199/414] Use BOOST_REQUIRE() and stop at the first failure --- test/RPCSession.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index ffdb48b1e..5cd1bc7ed 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -191,7 +191,7 @@ string RPCSession::eth_getStorageRoot(string const& _address, string const& _blo void RPCSession::personal_unlockAccount(string const& _address, string const& _password, int _duration) { - BOOST_CHECK(rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }) == true); + BOOST_REQUIRE(rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }) == true); } string RPCSession::personal_newAccount(string const& _password) @@ -235,18 +235,18 @@ void RPCSession::test_setChainParams(vector const& _accounts) void RPCSession::test_setChainParams(string const& _config) { - BOOST_CHECK(rpcCall("test_setChainParams", { _config }) == true); + BOOST_REQUIRE(rpcCall("test_setChainParams", { _config }) == true); } void RPCSession::test_rewindToBlock(size_t _blockNr) { - BOOST_CHECK(rpcCall("test_rewindToBlock", { to_string(_blockNr) }) == true); + BOOST_REQUIRE(rpcCall("test_rewindToBlock", { to_string(_blockNr) }) == true); } void RPCSession::test_mineBlocks(int _number) { u256 startBlock = fromBigEndian(fromHex(rpcCall("eth_blockNumber").asString())); - BOOST_CHECK(rpcCall("test_mineBlocks", { to_string(_number) }, true) == true); + BOOST_REQUIRE(rpcCall("test_mineBlocks", { to_string(_number) }, true) == true); bool mined = false; @@ -285,7 +285,7 @@ void RPCSession::test_mineBlocks(int _number) void RPCSession::test_modifyTimestamp(size_t _timestamp) { - BOOST_CHECK(rpcCall("test_modifyTimestamp", { to_string(_timestamp) }) == true); + BOOST_REQUIRE(rpcCall("test_modifyTimestamp", { to_string(_timestamp) }) == true); } Json::Value RPCSession::rpcCall(string const& _methodName, vector const& _args, bool _canFail) From fba3b849293c7c88f0d11316b97edeb55c44f173 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 21:19:05 +0000 Subject: [PATCH 200/414] Include --show-progress in soltest --- appveyor.yml | 2 +- scripts/tests.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 85fb36f2b..86a689a70 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -62,7 +62,7 @@ test_script: - ps: Start-Sleep -s 100 - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" . - - soltest.exe -- --ipcpath \\.\pipe\geth.ipc + - soltest.exe --show-progress -- --ipcpath \\.\pipe\geth.ipc artifacts: - path: solidity-windows.zip diff --git a/scripts/tests.sh b/scripts/tests.sh index ba932da59..8edfda0bc 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -62,9 +62,9 @@ echo "--> IPC available." # And then run the Solidity unit-tests (once without optimization, once with), # pointing to that IPC endpoint. echo "--> Running tests without optimizer..." - "$REPO_ROOT"/build/test/soltest -- --ipcpath /tmp/test/geth.ipc && \ + "$REPO_ROOT"/build/test/soltest --show-progress -- --ipcpath /tmp/test/geth.ipc && \ echo "--> Running tests WITH optimizer..." && \ - "$REPO_ROOT"/build/test/soltest -- --optimize --ipcpath /tmp/test/geth.ipc + "$REPO_ROOT"/build/test/soltest --show-progress -- --optimize --ipcpath /tmp/test/geth.ipc ERROR_CODE=$? pkill eth || true sleep 4 From 92fb07c78319c256a01c6146d83a7c9c7419a240 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 21:19:27 +0000 Subject: [PATCH 201/414] Do not log dots in soltest on windows --- test/RPCSession.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 5cd1bc7ed..968d77b1d 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -111,9 +111,6 @@ string IPCSocket::sendRequest(string const& _req) if (!fSuccess) BOOST_FAIL("ReadFile from pipe failed"); - // This is needed for Appveyor, otherwise it may terminate - // the session due to the inactivity. - cerr << "."; return returnStr; #else send(m_socket, _req.c_str(), _req.length(), 0); From 4fccb5fdaca054905bfab7cb01e7eba6fb47c114 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 10 Jun 2016 17:25:51 +0200 Subject: [PATCH 202/414] Document input description and metadata output. --- docs/index.rst | 1 + docs/miscellaneous.rst | 39 ---------- docs/using-the-compiler.rst | 141 ++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 39 deletions(-) create mode 100644 docs/using-the-compiler.rst diff --git a/docs/index.rst b/docs/index.rst index cb79687b9..904c3a544 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -130,6 +130,7 @@ Contents solidity-by-example.rst solidity-in-depth.rst security-considerations.rst + using-the-compiler.rst style-guide.rst common-patterns.rst contributing.rst diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index cc40d6a75..476500676 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -221,45 +221,6 @@ This means the following source mappings represent the same information: ``1:2:1;:9;2::2;;`` - -.. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker - -.. _commandline-compiler: - -****************************** -Using the Commandline Compiler -****************************** - -One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler. -Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage. -If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``. - -The commandline compiler will automatically read imported files from the filesystem, but -it is also possible to provide path redirects using ``context:prefix=path`` in the following way: - -:: - - solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol - -This essentially instructs the compiler to search for anything starting with -``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin`` and if it does not -find the file there, it will look at ``/usr/local/lib/fallback`` (the empty prefix -always matches). ``solc`` will not read files from the filesystem that lie outside of -the remapping targets and outside of the directories where explicitly specified source -files reside, so things like ``import "/etc/passwd";`` only work if you add ``=/`` as a remapping. - -You can restrict remappings to only certain source files by prefixing a context. - -The section on :ref:`import` provides more details on remappings. - -If there are multiple matches due to remappings, the one with the longest common prefix is selected. - -If your contracts use :ref:`libraries `, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points: - -Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``. - -If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case. - ***************** Contract Metadata ***************** diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst new file mode 100644 index 000000000..47d7c7175 --- /dev/null +++ b/docs/using-the-compiler.rst @@ -0,0 +1,141 @@ +.. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker + +.. _commandline-compiler: + +****************************** +Using the Commandline Compiler +****************************** + +One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler. +Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage. +If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``. + +The commandline compiler will automatically read imported files from the filesystem, but +it is also possible to provide path redirects using ``prefix=path`` in the following way: + +:: + + solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol + +This essentially instructs the compiler to search for anything starting with +``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin`` and if it does not +find the file there, it will look at ``/usr/local/lib/fallback`` (the empty prefix +always matches). ``solc`` will not read files from the filesystem that lie outside of +the remapping targets and outside of the directories where explicitly specified source +files reside, so things like ``import "/etc/passwd";`` only work if you add ``=/`` as a remapping. + +If there are multiple matches due to remappings, the one with the longest common prefix is selected. + +If your contracts use :ref:`libraries `, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points: + +Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``. + +If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case. + + +************************************************** +Standardized Input Description and Metadata Output +************************************************** + +In order to ease source code verification of complex contracts that are spread across several files, +there is a standardized for describing the relations between those files. +Furthermore, the compiler can generate a json file while compiling that includes +the source, natspec comments and other metadata whose hash is included in the +actual bytecode. + +There is some overlap between the input description and the metadata output +and due to the fact that some fields are optional, the metadata can be used as +input to the compiler. In order to verify the metadata, you actually take it, +re-run the compiler on the metadata and check that it again produces the same +metadata. + +If the compiler is invoked in a different way, not using the input +description (for example by using a file content retrieval callback), +the compiler can still generate the metadata alongside the bytecode of each +contract. + +The metadata standard is versioned. Future versions are only required to provide the "version" field, +the two keys inside the "compiler" field. The field compiler.keccak should be the keccak hash +of a binary of the compiler with the given version. + +The example below is presented in a human-readable way. Properly formatted metadata +should use quotes correctly, reduce whitespace to a minimum and sort the keys of all objects +to arrive at a unique formatting. + +Comments are of course not permitted and used here only for explanatory purposes. + +Input Description +----------------- + +The input description could change with each compiler version, but it +should be backwards compatible if possible. + + { + sources: + { + "abc": "contract b{}", + "def": {keccak: "0x123..."}, // source has to be retrieved by its hash + "dir/file.sol": "contract a {}" + }, + settings: + { + remappings: [":g/dir"], + optimizer: {enabled: true, runs: 500}, + compilationTarget: "myFile.sol:MyContract", // Can also be an array + // To be backwards compatible, use the full name of the contract in the output + // only if there are name clashes. + // If the full name was given as "compilationTargets", use the full name. + libraries: { + "def:MyLib": "0x123123..." + }, + // The following can be used to restrict the fields the compiler will output. + outputSelection: { + // to be defined + } + } + } + +Metadata Output +--------------- + +Note that the actual bytecode is not part of the metadata because the hash +of the metadata structure will be included in the bytecode itself. + +This requires the compiler to be able to compute the hash of its own binary, +which requires it to be statically linked. The hash of the binary is not +too important. It is much more important to have the commit hash because +that can be used to query a location of the binary (and whether the version is +"official") at a registry contract. + + { + version: "1", + compiler: { + version: "soljson-2313-2016-12-12", + keccak: "0x123..." + }, + sources: + { + "abc": "contract b{}", + "def": {keccak: "0x123..."}, // source has to be retrieved by its hash + "dir/file.sol": "contract a {}" + }, + settings: + { + remappings: [":g/dir"], + optimizer: {enabled: true, runs: 500}, + compilationTarget: "myFile.sol:MyContract", + // To be backwards compatible, use the full name of the contract in the output + // only if there are name clashes. + // If the full name was given as "compilationTargets", use the full name. + libraries: { + "def:MyLib": "0x123123..." + } + }, + output: + { + abi: [ /* abi definition */ ], + userDocumentation: [ /* user documentation comments */ ], + developerDocumentation: [ /* developer documentation comments */ ], + natspec: [ /* natspec comments */ ] + } + } From 57662e1bf3cba937289c50b3b711918a456101a6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 10 Jun 2016 22:03:22 +0200 Subject: [PATCH 203/414] Add language and some minor corrections and clarifications. --- docs/using-the-compiler.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 47d7c7175..8ceb52a74 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -38,10 +38,11 @@ Standardized Input Description and Metadata Output ************************************************** In order to ease source code verification of complex contracts that are spread across several files, -there is a standardized for describing the relations between those files. +there is a standardized way for describing the relations between those files. Furthermore, the compiler can generate a json file while compiling that includes the source, natspec comments and other metadata whose hash is included in the -actual bytecode. +actual bytecode. Specifically, the creation data for a contract has to begin with +`push32 pop`. There is some overlap between the input description and the metadata output and due to the fact that some fields are optional, the metadata can be used as @@ -55,8 +56,8 @@ the compiler can still generate the metadata alongside the bytecode of each contract. The metadata standard is versioned. Future versions are only required to provide the "version" field, -the two keys inside the "compiler" field. The field compiler.keccak should be the keccak hash -of a binary of the compiler with the given version. +the "language" field and the two keys inside the "compiler" field. +The field compiler.keccak should be the keccak hash of a binary of the compiler with the given version. The example below is presented in a human-readable way. Properly formatted metadata should use quotes correctly, reduce whitespace to a minimum and sort the keys of all objects @@ -67,7 +68,7 @@ Comments are of course not permitted and used here only for explanatory purposes Input Description ----------------- -The input description could change with each compiler version, but it +The input description is language-specific and could change with each compiler version, but it should be backwards compatible if possible. { @@ -105,11 +106,13 @@ This requires the compiler to be able to compute the hash of its own binary, which requires it to be statically linked. The hash of the binary is not too important. It is much more important to have the commit hash because that can be used to query a location of the binary (and whether the version is -"official") at a registry contract. +"official") at a registry contract. { version: "1", + language: "Solidity", compiler: { + commit: "55db20e32c97098d13230ab7500758e8e3b31d64", version: "soljson-2313-2016-12-12", keccak: "0x123..." }, From 77b934c86118fe9148265dbb6b4db9ed1335ac27 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 16 Nov 2016 13:05:24 +0000 Subject: [PATCH 204/414] Update with https://pad.riseup.net/p/7x3G896a3NLA --- docs/using-the-compiler.rst | 148 +++++++++++++++++++++++++++++------- 1 file changed, 121 insertions(+), 27 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 8ceb52a74..5b8a4b5c2 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -40,21 +40,10 @@ Standardized Input Description and Metadata Output In order to ease source code verification of complex contracts that are spread across several files, there is a standardized way for describing the relations between those files. Furthermore, the compiler can generate a json file while compiling that includes -the source, natspec comments and other metadata whose hash is included in the +the (hash of the) source, natspec comments and other metadata whose hash is included in the actual bytecode. Specifically, the creation data for a contract has to begin with `push32 pop`. -There is some overlap between the input description and the metadata output -and due to the fact that some fields are optional, the metadata can be used as -input to the compiler. In order to verify the metadata, you actually take it, -re-run the compiler on the metadata and check that it again produces the same -metadata. - -If the compiler is invoked in a different way, not using the input -description (for example by using a file content retrieval callback), -the compiler can still generate the metadata alongside the bytecode of each -contract. - The metadata standard is versioned. Future versions are only required to provide the "version" field, the "language" field and the two keys inside the "compiler" field. The field compiler.keccak should be the keccak hash of a binary of the compiler with the given version. @@ -68,34 +57,128 @@ Comments are of course not permitted and used here only for explanatory purposes Input Description ----------------- +QUESTION: How to specific file-reading callback? - probably not as part of json input + The input description is language-specific and could change with each compiler version, but it should be backwards compatible if possible. { sources: { - "abc": "contract b{}", + // the keys here are the "global" names of the source files, imports can use other files via remappings (see below) + "abc": "contract b{}", // specify source directly + // (axic) I think 'keccak' on its on is not enough. I would go perhaps with swarm: "0x12.." and ipfs: "Qma..." for simplicity + // (chriseth) Where the content is stored is a second component, but yes, we could give an indication there. "def": {keccak: "0x123..."}, // source has to be retrieved by its hash + "ghi": {file: "/tmp/path/to/file.sol"}, // file on filesystem + // (axic) I'm inclined to think the source _must_ be provided in the JSON, + "dir/file.sol": "contract a {}" }, settings: { - remappings: [":g/dir"], + remappings: [":g/dir"], // just as it used to be + // (axic) what is remapping doing exactly? optimizer: {enabled: true, runs: 500}, - compilationTarget: "myFile.sol:MyContract", // Can also be an array - // To be backwards compatible, use the full name of the contract in the output - // only if there are name clashes. - // If the full name was given as "compilationTargets", use the full name. + // if given, only compiles this contract, can also be an array. If only a contract name is given, tries to find it if unique. + compilationTarget: "myFile.sol:MyContract", + // addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different libraries: { "def:MyLib": "0x123123..." }, // The following can be used to restrict the fields the compiler will output. + // (axic) + outputSelection: [ + "abi", "evm.assembly", "evm.bytecode", ..., "why3", "ewasm.wasm" + ] outputSelection: { + abi,asm,ast,bin,bin-runtime,clone-bin,devdoc,interface,opcodes,srcmap,srcmap-runtime,userdoc + + --ast AST of all source files. + --ast-json AST of all source files in JSON format. + --asm EVM assembly of the contracts. + --asm-json EVM assembly of the contracts in JSON format. + --opcodes Opcodes of the contracts. + --bin Binary of the contracts in hex. + --bin-runtime Binary of the runtime part of the contracts in hex. + --clone-bin Binary of the clone contracts in hex. + --abi ABI specification of the contracts. + --interface Solidity interface of the contracts. + --hashes Function signature hashes of the contracts. + --userdoc Natspec user documentation of all contracts. + --devdoc Natspec developer documentation of all contracts. + --formal Translated source suitable for formal analysis. + // to be defined } } } + +Regular Output +-------------- + + + { + errors: ["error1", "error2"], // we might structure them + errors: [ + { + // (axic) + file: "sourceFile.sol", // optional? + contract: "contractName", // optional + line: 100, // optional - currently, we always have a byte range in the source file + // Errors/warnings originate in several components, most of them are not + // backend-specific. Currently, why3 errors are part of the why3 output. + // I think it is better to put code-generator-specific errors into the code-generator output + // area, and warnings and errors that are code-generator-agnostic into this general area, + // so that it is easier to determine whether some source code is invalid or only + // triggers errors/warnings in some backend that might only implement some part of solidity. + type: "evm" or "why3" or "ewasm" // maybe a better field name would be needed + severity: "warning" or "error" // mandatory + message: "Invalid keyword" // mandatory + } + ] + contracts: { + "sourceFile.sol:ContractName": { + abi: + evm: { + assembly: + bytecode: + runtimeBytecode: + opcodes: + annotatedOpcodes: // (axic) see https://github.com/ethereum/solidity/issues/1178 + gasEstimates: + sourceMap: + runtimeSourceMap: + // If given, this is an unlinked object (cannot be filtered out explicitly, might be + // filtered if both bytecode, runtimeBytecode, opcodes and others are filtered out) + linkReferences: { + "sourceFile.sol:Library1": [1, 200, 80] // byte offsets into bytecode. Linking replaces the 20 bytes there. + } + // the same for runtimeBytecode - I'm not sure it is a good idea to allow to link libraries differently for the runtime bytecode. + // furthermore, runtime bytecode is always a substring of the bytecode anyway. + runtimeLinkReferences: { + } + }, + functionHashes: + metadata: // see below + ewasm: { + wast: // S-expression format + wasm: // + } + } + }, + formal: { + "why3": "..." + }, + sourceList: ["source1.sol", "source2.sol"], // this is important for source references both in the ast as well as in the srcmap in the contract + sources: { + "source1.sol": { + "AST": { ... } + } + } + } + Metadata Output --------------- @@ -118,18 +201,15 @@ that can be used to query a location of the binary (and whether the version is }, sources: { - "abc": "contract b{}", - "def": {keccak: "0x123..."}, // source has to be retrieved by its hash - "dir/file.sol": "contract a {}" + "abc": {keccak: "0x456..."}, // here, sources are always given by hash + "def": {keccak: "0x123..."}, + "dir/file.sol": {keccax: "0xabc..."} }, settings: { remappings: [":g/dir"], optimizer: {enabled: true, runs: 500}, compilationTarget: "myFile.sol:MyContract", - // To be backwards compatible, use the full name of the contract in the output - // only if there are name clashes. - // If the full name was given as "compilationTargets", use the full name. libraries: { "def:MyLib": "0x123123..." } @@ -137,8 +217,22 @@ that can be used to query a location of the binary (and whether the version is output: { abi: [ /* abi definition */ ], - userDocumentation: [ /* user documentation comments */ ], - developerDocumentation: [ /* developer documentation comments */ ], - natspec: [ /* natspec comments */ ] + natspec: [ /* user documentation comments */ ] } } + +This is used in the following way: A component that wants to interact +with a contract (e.g. mist) retrieves the creation transaction of the contract +and from that the first 33 bytes. If the first byte decodes into a PUSH32 +instruction, the other 32 bytes are interpreted as the keccak-hash of +a file which is retrieved via a content-addressable storage like swarm. +That file is JSON-decoded into a structure like above. Sources are +retrieved in the same way and combined with the structure into a proper +compiler input description, which selects only the bytecode as output. + +The compiler of the correct version (which is checked to be part of the "official" compilers) +is invoked on that input. The resulting +bytecode is compared (excess bytecode in the creation transaction +is constructor input data) which automatically verifies the metadata since +its hash is part of the bytecode. The constructor input data is decoded +according to the interface and presented to the user. From 0b3f1a5378daa4075820a6be94afb2d9106c3ebd Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 16 Nov 2016 13:20:10 +0000 Subject: [PATCH 205/414] Describe the ABI output field --- docs/using-the-compiler.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 5b8a4b5c2..72166f181 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -140,7 +140,9 @@ Regular Output ] contracts: { "sourceFile.sol:ContractName": { - abi: + // The Ethereum Contract ABI. If empty, it is represented as an empty array. + // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + abi: [], evm: { assembly: bytecode: From 04089edc4e651493f7596bf2372c7c0487fc2dad Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 16 Nov 2016 13:20:34 +0000 Subject: [PATCH 206/414] Add missing fields --- docs/using-the-compiler.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 72166f181..a49bfb8e5 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -167,7 +167,10 @@ Regular Output ewasm: { wast: // S-expression format wasm: // - } + }, + userdoc: // Obsolete + devdoc: // Obsolete + natspec: // Combined dev+userdoc } }, formal: { From 073871c248b097b654fb354fd756559009d7c3e9 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 16 Nov 2016 13:25:40 +0000 Subject: [PATCH 207/414] Update the metadata JSON spec --- docs/using-the-compiler.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index a49bfb8e5..4fc5c71d1 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -204,12 +204,15 @@ that can be used to query a location of the binary (and whether the version is version: "soljson-2313-2016-12-12", keccak: "0x123..." }, + // This is a subset of the regular compiler input sources: { "abc": {keccak: "0x456..."}, // here, sources are always given by hash "def": {keccak: "0x123..."}, - "dir/file.sol": {keccax: "0xabc..."} + "dir/file.sol": {keccax: "0xabc..."}, + "xkcd": {swarm: "0x456..."} }, + // This is a subset of the regular compiler input settings: { remappings: [":g/dir"], @@ -219,9 +222,12 @@ that can be used to query a location of the binary (and whether the version is "def:MyLib": "0x123123..." } }, + // This is a subset of the regular compiler output output: { abi: [ /* abi definition */ ], + userdoc: [], + devdoc: [], natspec: [ /* user documentation comments */ ] } } From 559c4c7a451bbe0518441bd0442b8325f40b279a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 17 Nov 2016 10:31:46 +0000 Subject: [PATCH 208/414] Update the metadata JSON spec --- docs/using-the-compiler.rst | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 4fc5c71d1..de9e104ac 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -197,29 +197,43 @@ that can be used to query a location of the binary (and whether the version is "official") at a registry contract. { + // The version of the metadata format (required field) version: "1", + // Required field language: "Solidity", + // Required field, the contents are specific to the language compiler: { - commit: "55db20e32c97098d13230ab7500758e8e3b31d64", - version: "soljson-2313-2016-12-12", - keccak: "0x123..." + name: "solc", + version: "0.4.5-nightly.2016.11.15+commit.c1b1efaf.Emscripten.clang", + // Optional hash of the compiler binary which produced this output + keccak256: "0x123..." }, // This is a subset of the regular compiler input sources: { - "abc": {keccak: "0x456..."}, // here, sources are always given by hash - "def": {keccak: "0x123..."}, - "dir/file.sol": {keccax: "0xabc..."}, - "xkcd": {swarm: "0x456..."} + "myFile.sol": { + "keccak256": "0x123...", + "url": "bzzr://0x56..." + }, + "Token": { + "keccak256": "0x456...", + "url": "https://raw.githubusercontent.com/ethereum/solidity/develop/std/Token.sol" + }, + "mortal": { + "content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }" + } }, // This is a subset of the regular compiler input + // Its content is specific to the compiler (determined by the language and compiler fields) settings: { remappings: [":g/dir"], optimizer: {enabled: true, runs: 500}, - compilationTarget: "myFile.sol:MyContract", + compilationTarget: { + "myFile.sol": MyContract" + }, libraries: { - "def:MyLib": "0x123123..." + "MyLib": "0x123123..." } }, // This is a subset of the regular compiler output @@ -228,7 +242,6 @@ that can be used to query a location of the binary (and whether the version is abi: [ /* abi definition */ ], userdoc: [], devdoc: [], - natspec: [ /* user documentation comments */ ] } } From d9f14e77371b09b419e6561cf847cd4d0e72bde0 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 2 Dec 2016 10:36:11 +0000 Subject: [PATCH 209/414] The metadata section has been moved, make only a reference to it --- docs/using-the-compiler.rst | 100 ++---------------------------------- 1 file changed, 5 insertions(+), 95 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index de9e104ac..0fec6c8c2 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -33,24 +33,11 @@ Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case. -************************************************** -Standardized Input Description and Metadata Output -************************************************** +***************************************** +Standardized Input and Output Description +***************************************** -In order to ease source code verification of complex contracts that are spread across several files, -there is a standardized way for describing the relations between those files. -Furthermore, the compiler can generate a json file while compiling that includes -the (hash of the) source, natspec comments and other metadata whose hash is included in the -actual bytecode. Specifically, the creation data for a contract has to begin with -`push32 pop`. - -The metadata standard is versioned. Future versions are only required to provide the "version" field, -the "language" field and the two keys inside the "compiler" field. -The field compiler.keccak should be the keccak hash of a binary of the compiler with the given version. - -The example below is presented in a human-readable way. Properly formatted metadata -should use quotes correctly, reduce whitespace to a minimum and sort the keys of all objects -to arrive at a unique formatting. +The compiler API expects a JSON formatted input and outputs the compilations result in a JSON formatted output. Comments are of course not permitted and used here only for explanatory purposes. @@ -163,7 +150,7 @@ Regular Output } }, functionHashes: - metadata: // see below + metadata: // see the Metadata Output documentation ewasm: { wast: // S-expression format wasm: // @@ -183,80 +170,3 @@ Regular Output } } } - -Metadata Output ---------------- - -Note that the actual bytecode is not part of the metadata because the hash -of the metadata structure will be included in the bytecode itself. - -This requires the compiler to be able to compute the hash of its own binary, -which requires it to be statically linked. The hash of the binary is not -too important. It is much more important to have the commit hash because -that can be used to query a location of the binary (and whether the version is -"official") at a registry contract. - - { - // The version of the metadata format (required field) - version: "1", - // Required field - language: "Solidity", - // Required field, the contents are specific to the language - compiler: { - name: "solc", - version: "0.4.5-nightly.2016.11.15+commit.c1b1efaf.Emscripten.clang", - // Optional hash of the compiler binary which produced this output - keccak256: "0x123..." - }, - // This is a subset of the regular compiler input - sources: - { - "myFile.sol": { - "keccak256": "0x123...", - "url": "bzzr://0x56..." - }, - "Token": { - "keccak256": "0x456...", - "url": "https://raw.githubusercontent.com/ethereum/solidity/develop/std/Token.sol" - }, - "mortal": { - "content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }" - } - }, - // This is a subset of the regular compiler input - // Its content is specific to the compiler (determined by the language and compiler fields) - settings: - { - remappings: [":g/dir"], - optimizer: {enabled: true, runs: 500}, - compilationTarget: { - "myFile.sol": MyContract" - }, - libraries: { - "MyLib": "0x123123..." - } - }, - // This is a subset of the regular compiler output - output: - { - abi: [ /* abi definition */ ], - userdoc: [], - devdoc: [], - } - } - -This is used in the following way: A component that wants to interact -with a contract (e.g. mist) retrieves the creation transaction of the contract -and from that the first 33 bytes. If the first byte decodes into a PUSH32 -instruction, the other 32 bytes are interpreted as the keccak-hash of -a file which is retrieved via a content-addressable storage like swarm. -That file is JSON-decoded into a structure like above. Sources are -retrieved in the same way and combined with the structure into a proper -compiler input description, which selects only the bytecode as output. - -The compiler of the correct version (which is checked to be part of the "official" compilers) -is invoked on that input. The resulting -bytecode is compared (excess bytecode in the creation transaction -is constructor input data) which automatically verifies the metadata since -its hash is part of the bytecode. The constructor input data is decoded -according to the interface and presented to the user. From 720cf20855b7695f3c2059c2e1c52e2cb2501995 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 2 Dec 2016 13:46:56 +0000 Subject: [PATCH 210/414] Place into a code block --- docs/using-the-compiler.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 0fec6c8c2..dcd5e5892 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -49,6 +49,8 @@ QUESTION: How to specific file-reading callback? - probably not as part of json The input description is language-specific and could change with each compiler version, but it should be backwards compatible if possible. +.. code-block:: none + { sources: { @@ -105,6 +107,7 @@ should be backwards compatible if possible. Regular Output -------------- +.. code-block:: none { errors: ["error1", "error2"], // we might structure them From 6e2cc081ecec5c5c20945b70159d36b9e8286d0a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 13:54:21 +0000 Subject: [PATCH 211/414] Update sources definition based on the metadata --- docs/using-the-compiler.rst | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index dcd5e5892..57135df88 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -52,17 +52,33 @@ should be backwards compatible if possible. .. code-block:: none { + // Required sources: { - // the keys here are the "global" names of the source files, imports can use other files via remappings (see below) - "abc": "contract b{}", // specify source directly - // (axic) I think 'keccak' on its on is not enough. I would go perhaps with swarm: "0x12.." and ipfs: "Qma..." for simplicity - // (chriseth) Where the content is stored is a second component, but yes, we could give an indication there. - "def": {keccak: "0x123..."}, // source has to be retrieved by its hash - "ghi": {file: "/tmp/path/to/file.sol"}, // file on filesystem - // (axic) I'm inclined to think the source _must_ be provided in the JSON, - - "dir/file.sol": "contract a {}" + // The keys here are the "global" names of the source files, + // imports can use other files via remappings (see below). + "myFile.sol": + { + // Optional: keccak256 hash of the source file + "keccak256": "0x123...", + // Required (unless "content" is used, see below): URL(s) to the source file. + // URL(s) should be imported in this order and the result checked against the + // keccak256 hash (if available). If the hash doesn't match or none of the + // URL(s) result in success, an error should be raised. + "urls": + [ + "bzzr://56ab...", + "ipfs://Qma...", + "file:///tmp/path/to/file.sol" + ] + }, + "mortal": + { + // Optional: keccak256 hash of the source file + "keccak256": "0x234...", + // Required (unless "urls" is used): literal contents of the source file + "content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }" + } }, settings: { From 82c0e4de1df0d4b17e01e7fd35ebc312156b85fd Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 13:58:30 +0000 Subject: [PATCH 212/414] Update settings section --- docs/using-the-compiler.rst | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 57135df88..2238a773b 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -80,17 +80,29 @@ should be backwards compatible if possible. "content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }" } }, + // Optional settings: { - remappings: [":g/dir"], // just as it used to be - // (axic) what is remapping doing exactly? - optimizer: {enabled: true, runs: 500}, - // if given, only compiles this contract, can also be an array. If only a contract name is given, tries to find it if unique. - compilationTarget: "myFile.sol:MyContract", - // addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different - libraries: { - "def:MyLib": "0x123123..." + // Optional: Sorted list of remappings + remappings: [ ":g/dir" ], + // Optional: Optimizer settings (enabled defaults to false) + optimizer: { + enabled: true, + runs: 500 }, + // If given, only compiles the specified contracts. + compilationTarget: { + "myFile.sol": "MyContract" + }, + // Addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different. + libraries: { + // The top level key is the the name of the source file where the library is used. + // If remappings are used, this source file should match the global path after remappings were applied. + // If this key is an empty string, that refers to a global level. + "myFile.sol": { + "MyLib": "0x123123..." + } + } // The following can be used to restrict the fields the compiler will output. // (axic) outputSelection: [ @@ -144,6 +156,7 @@ Regular Output message: "Invalid keyword" // mandatory } ] + // This contains all the compiled outputs. It can be limited/filtered by the compilationTarget setting. contracts: { "sourceFile.sol:ContractName": { // The Ethereum Contract ABI. If empty, it is represented as an empty array. From 4b5639bf63f2c86604cb55387324e00759a0b1f2 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 14:57:07 +0000 Subject: [PATCH 213/414] Update output selection --- docs/using-the-compiler.rst | 67 ++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 2238a773b..ebb03832a 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -90,10 +90,6 @@ should be backwards compatible if possible. enabled: true, runs: 500 }, - // If given, only compiles the specified contracts. - compilationTarget: { - "myFile.sol": "MyContract" - }, // Addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different. libraries: { // The top level key is the the name of the source file where the library is used. @@ -103,30 +99,47 @@ should be backwards compatible if possible. "MyLib": "0x123123..." } } - // The following can be used to restrict the fields the compiler will output. - // (axic) - outputSelection: [ - "abi", "evm.assembly", "evm.bytecode", ..., "why3", "ewasm.wasm" - ] + // The following can be used to select desired outputs. + // If this field is omitted, then the compiler loads and does type checking, but will not generate any outputs apart from errors. + // The first level key is the file name and the second is the contract name, where empty contract name refers to the file itself, + // while the star refers to all of the contracts. + // + // The available output types are as follows: + // abi - ABI + // ast - AST of all source files + // why3 - Why3 translated output + // devdoc - Developer documentation (natspec) + // userdoc - User documentation (natspec) + // metadata - Metadata + // evm.ir - New assembly format before desugaring (not supported atm) + // evm.assembly - New assembly format after desugaring (not supported atm) + // evm.asm - Current assembly format (--asm) + // evm.asmJSON - Current assembly format in JSON (--asm-json) + // evm.opcodes - Opcodes list + // evm.methodIdentifiers - The list of function hashes + // evm.gasEstimates - Function gas estimates + // evm.bytecode - Bytecode (--bin) + // evm.deployedBytecode - Deployed bytecode (--bin-runtime) + // evm.sourceMap - Source mapping (useful for debugging) + // ewasm.wast - eWASM S-expressions format (not supported atm) + // ewasm.wasm - eWASM binary format (not supported atm) outputSelection: { - abi,asm,ast,bin,bin-runtime,clone-bin,devdoc,interface,opcodes,srcmap,srcmap-runtime,userdoc - - --ast AST of all source files. - --ast-json AST of all source files in JSON format. - --asm EVM assembly of the contracts. - --asm-json EVM assembly of the contracts in JSON format. - --opcodes Opcodes of the contracts. - --bin Binary of the contracts in hex. - --bin-runtime Binary of the runtime part of the contracts in hex. - --clone-bin Binary of the clone contracts in hex. - --abi ABI specification of the contracts. - --interface Solidity interface of the contracts. - --hashes Function signature hashes of the contracts. - --userdoc Natspec user documentation of all contracts. - --devdoc Natspec developer documentation of all contracts. - --formal Translated source suitable for formal analysis. - - // to be defined + // Enable the metadata and bytecode outputs of every single contract. + "*": { + "*": [ "metadata", "evm.bytecode" ] + }, + // Enable the abi and opcodes output of MyContract defined in file def. + "def": { + "MyContract": [ "abi", "evm.opcodes" ] + }, + // Enable the source map output of every single contract. + "*": { + "*": [ "evm.sourceMap" ] + }, + // Enable the AST and Why3 output of every single file. + "*": { + "": [ "ast", "why3" ] + } } } } From cbb668672fe14f30ae51a022e48f27ddcbe9b324 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 15:00:01 +0000 Subject: [PATCH 214/414] Add metadata.useLiteralContent option --- docs/using-the-compiler.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index ebb03832a..2518310b6 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -90,6 +90,11 @@ should be backwards compatible if possible. enabled: true, runs: 500 }, + // Metadata settings (optional) + metadata: { + // Use only literal content and not URLs (false by default) + useLiteralContent: true + }, // Addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different. libraries: { // The top level key is the the name of the source file where the library is used. From d46ec20f88feac1326381e5ac67a02086c5f4516 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 20:52:59 +0000 Subject: [PATCH 215/414] Change layout and include API, Input, Output sections --- docs/using-the-compiler.rst | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 2518310b6..b89af912d 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -1,8 +1,11 @@ +****************** +Using the compiler +****************** + .. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker .. _commandline-compiler: -****************************** Using the Commandline Compiler ****************************** @@ -32,23 +35,26 @@ Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case. +.. _compiler-api: -***************************************** -Standardized Input and Output Description -***************************************** +Using the Compiler API +********************** The compiler API expects a JSON formatted input and outputs the compilations result in a JSON formatted output. +TBD + +Compiler Input and Output JSON Description +****************************************** + +These JSON formats are used by the compiler API as well as are available through ``solc``. These are subject to change, +some fields are optional (as noted), but it is aimed at to only make backwards compatible changes. + Comments are of course not permitted and used here only for explanatory purposes. Input Description ----------------- -QUESTION: How to specific file-reading callback? - probably not as part of json input - -The input description is language-specific and could change with each compiler version, but it -should be backwards compatible if possible. - .. code-block:: none { @@ -150,8 +156,8 @@ should be backwards compatible if possible. } -Regular Output --------------- +Output Description +------------------ .. code-block:: none From 21a022848540dc488b312f94b62d62fce66aafc7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 21:10:19 +0000 Subject: [PATCH 216/414] Include pseudo-code of compiler API --- docs/using-the-compiler.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index b89af912d..703a1c577 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -42,7 +42,18 @@ Using the Compiler API The compiler API expects a JSON formatted input and outputs the compilations result in a JSON formatted output. -TBD +See the following pseudo-code: + +.. code-block:: none + + // defined by the consumer of the API + importCallback(url:string) -> content:string + + // invoking the compiler + solc.compile(inputJSON:string, importCallback:function) -> outputJSON:string + +The compiler will ask the ``importCallback`` for each URL defined for a source file and will stop when it succeeds. +If all URLs failed, the compilation results in a failure. Compiler Input and Output JSON Description ****************************************** @@ -66,6 +77,7 @@ Input Description "myFile.sol": { // Optional: keccak256 hash of the source file + // It is used to verify the retrieved content if imported via URLs. "keccak256": "0x123...", // Required (unless "content" is used, see below): URL(s) to the source file. // URL(s) should be imported in this order and the result checked against the From 627a2cec4d058edc6d2e07a37cf0b464ee868b6b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 21:18:09 +0000 Subject: [PATCH 217/414] Update errors output --- docs/using-the-compiler.rst | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 703a1c577..2e0cba1f9 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -174,24 +174,27 @@ Output Description .. code-block:: none { - errors: ["error1", "error2"], // we might structure them + // Optional: not present if no errors/warnings were encountered errors: [ - { - // (axic) - file: "sourceFile.sol", // optional? - contract: "contractName", // optional - line: 100, // optional - currently, we always have a byte range in the source file - // Errors/warnings originate in several components, most of them are not - // backend-specific. Currently, why3 errors are part of the why3 output. - // I think it is better to put code-generator-specific errors into the code-generator output - // area, and warnings and errors that are code-generator-agnostic into this general area, - // so that it is easier to determine whether some source code is invalid or only - // triggers errors/warnings in some backend that might only implement some part of solidity. - type: "evm" or "why3" or "ewasm" // maybe a better field name would be needed - severity: "warning" or "error" // mandatory - message: "Invalid keyword" // mandatory - } - ] + { + // Optional + file: "sourceFile.sol", + // Optional + contract: "contractName", + // Optional + line: 100, + // Optional + column: 0, + // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc + type: "TypeError", + // Mandatory: Component where the error originated, such as "general", "why3", "ewasm", etc. + component: "general", + // Mandatory ("error" or "warning") + severity: "error", + // Mandatory + message: "Invalid keyword" + } + ], // This contains all the compiled outputs. It can be limited/filtered by the compilationTarget setting. contracts: { "sourceFile.sol:ContractName": { From 4b19f560b8ac5fd20d46db93a3aa2d1c41adb0db Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 21:27:12 +0000 Subject: [PATCH 218/414] Make contracts output two-level --- docs/using-the-compiler.rst | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 2e0cba1f9..3282b85a0 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -195,13 +195,14 @@ Output Description message: "Invalid keyword" } ], - // This contains all the compiled outputs. It can be limited/filtered by the compilationTarget setting. + // This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings. contracts: { - "sourceFile.sol:ContractName": { - // The Ethereum Contract ABI. If empty, it is represented as an empty array. - // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI - abi: [], - evm: { + "sourceFile.sol": { + "ContractName": { + // The Ethereum Contract ABI. If empty, it is represented as an empty array. + // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + abi: [], + evm: { assembly: bytecode: runtimeBytecode: @@ -219,16 +220,17 @@ Output Description // furthermore, runtime bytecode is always a substring of the bytecode anyway. runtimeLinkReferences: { } - }, - functionHashes: - metadata: // see the Metadata Output documentation - ewasm: { + }, + functionHashes: + metadata: // see the Metadata Output documentation + ewasm: { wast: // S-expression format wasm: // - }, - userdoc: // Obsolete - devdoc: // Obsolete - natspec: // Combined dev+userdoc + }, + userdoc: // Obsolete + devdoc: // Obsolete + natspec: // Combined dev+userdoc + } } }, formal: { From c217bc2dcaf9deec38a54cc094b09288e95ba58f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 21:33:46 +0000 Subject: [PATCH 219/414] Updated EVM output --- docs/using-the-compiler.rst | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 3282b85a0..2d78d58a0 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -204,22 +204,25 @@ Output Description abi: [], evm: { assembly: - bytecode: - runtimeBytecode: + // Bytecode and related details. + bytecode: { + // The bytecode as a hex string. + object: "00fe", + // The source mapping as a string. See the source mapping definition. + sourceMap: "", + // If given, this is an unlinked object. + linkReferences: { + "libraryFile.sol": { + // Byte offsets into the bytecode. Linking replaces the 20 bytes located there. + "Library1": [1, 200, 80] + } + } + } + // The same layout as above. + deployedBytecode: { }, opcodes: annotatedOpcodes: // (axic) see https://github.com/ethereum/solidity/issues/1178 gasEstimates: - sourceMap: - runtimeSourceMap: - // If given, this is an unlinked object (cannot be filtered out explicitly, might be - // filtered if both bytecode, runtimeBytecode, opcodes and others are filtered out) - linkReferences: { - "sourceFile.sol:Library1": [1, 200, 80] // byte offsets into bytecode. Linking replaces the 20 bytes there. - } - // the same for runtimeBytecode - I'm not sure it is a good idea to allow to link libraries differently for the runtime bytecode. - // furthermore, runtime bytecode is always a substring of the bytecode anyway. - runtimeLinkReferences: { - } }, functionHashes: metadata: // see the Metadata Output documentation From a3340e210e94c3e31cf26b65cc49e03f8ef444de Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 21:35:07 +0000 Subject: [PATCH 220/414] Error list should have sourceLocation --- docs/using-the-compiler.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 2d78d58a0..67b3f32ca 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -179,12 +179,10 @@ Output Description { // Optional file: "sourceFile.sol", - // Optional - contract: "contractName", - // Optional - line: 100, - // Optional - column: 0, + // Optional: Location within the source file. + sourceLocation: [ + { start: 0, end: 100 }, + ], // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc type: "TypeError", // Mandatory: Component where the error originated, such as "general", "why3", "ewasm", etc. From 9fc017d10b3da56f4e94b73bf9f9f6ec7d5345cf Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 21:35:57 +0000 Subject: [PATCH 221/414] Support linkReferences with length specified --- docs/using-the-compiler.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 67b3f32ca..ed7f08565 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -212,7 +212,10 @@ Output Description linkReferences: { "libraryFile.sol": { // Byte offsets into the bytecode. Linking replaces the 20 bytes located there. - "Library1": [1, 200, 80] + "Library1": [ + { start: 0, length: 20 }, + { start: 200, length: 20 } + ] } } } From 9fa54db7bd0003da3ef4f7daa6df860e71f7af75 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 21:52:35 +0000 Subject: [PATCH 222/414] Explain every contract output field --- docs/using-the-compiler.rst | 49 ++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index ed7f08565..6a50ca685 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -201,7 +201,14 @@ Output Description // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI abi: [], evm: { - assembly: + // Intermediate representation (string) + ir: "", + // Assembly (string) + assembly: "", + // Old-style assembly (string) + asm: "", + // Old-style assembly (JSON object) + asmJSON: [], // Bytecode and related details. bytecode: { // The bytecode as a hex string. @@ -221,19 +228,39 @@ Output Description } // The same layout as above. deployedBytecode: { }, - opcodes: - annotatedOpcodes: // (axic) see https://github.com/ethereum/solidity/issues/1178 - gasEstimates: + // Opcodes list (string) + opcodes: "", + // The list of function hashes + methodIdentifiers: { + "5c19a95c": "delegate(address)", + }, + // Function gas estimates + gasEstimates: { + creation: { + dataCost: 420000, + // -1 means infinite (aka. unknown) + executionCost: -1 + }, + external: { + "delegate(address)": 25000 + }, + internal: { + "heavyLifting()": -1 + } + } }, - functionHashes: - metadata: // see the Metadata Output documentation + // See the Metadata Output documentation + metadata: {}, ewasm: { - wast: // S-expression format - wasm: // + // S-expressions format + wast: "", + // Binary format (hex string) + wasm: "" }, - userdoc: // Obsolete - devdoc: // Obsolete - natspec: // Combined dev+userdoc + // User documentation (natspec) + userdoc: {}, + // Developer documentation (natspec) + devdoc: {} } } }, From 96677cd1788ab7eb8c493f4de7030200b0c39108 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 2 Feb 2017 22:06:10 +0000 Subject: [PATCH 223/414] Update the AST output --- docs/using-the-compiler.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 6a50ca685..f2d6b858a 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -193,6 +193,15 @@ Output Description message: "Invalid keyword" } ], + // This contains the file-level outputs. In can be limited/filtered by the outputSelection settings. + sources: { + "sourceFile.sol": { + // Identifier (used in source maps) + id: 1, + // The AST object + ast: {} + } + }, // This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings. contracts: { "sourceFile.sol": { @@ -264,13 +273,6 @@ Output Description } } }, - formal: { - "why3": "..." - }, - sourceList: ["source1.sol", "source2.sol"], // this is important for source references both in the ast as well as in the srcmap in the contract - sources: { - "source1.sol": { - "AST": { ... } - } - } + // Why3 output (string) + why3: "" } From 10d3a591d42952980203c6f9395f81065f06575b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 16:55:56 +0000 Subject: [PATCH 224/414] Move file into sourceLocation --- docs/using-the-compiler.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index f2d6b858a..ab8f3d826 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -177,11 +177,11 @@ Output Description // Optional: not present if no errors/warnings were encountered errors: [ { - // Optional - file: "sourceFile.sol", // Optional: Location within the source file. - sourceLocation: [ - { start: 0, end: 100 }, + sourceLocation: { + file: "sourceFile.sol", + start: 0, + end: 100 ], // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc type: "TypeError", From 749db7608b269b4b2dde7649d63f32c87184f2b7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 19:00:07 +0000 Subject: [PATCH 225/414] Include language field in the JSON --- docs/using-the-compiler.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index ab8f3d826..9ea1a6abc 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -69,6 +69,8 @@ Input Description .. code-block:: none { + // Required: Source code language, such as "Solidity", "serpent", "lll", "assembly", etc. + language: "Solidity", // Required sources: { @@ -205,6 +207,7 @@ Output Description // This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings. contracts: { "sourceFile.sol": { + // If the language used has no contract names, this field should equal to an empty string. "ContractName": { // The Ethereum Contract ABI. If empty, it is represented as an empty array. // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI From cd81e58e3be5dd52a138f3ccac4ab2a305979acc Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 19:16:23 +0000 Subject: [PATCH 226/414] Drop the legacy assembly output --- docs/using-the-compiler.rst | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 9ea1a6abc..f197c663b 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -136,15 +136,14 @@ Input Description // devdoc - Developer documentation (natspec) // userdoc - User documentation (natspec) // metadata - Metadata - // evm.ir - New assembly format before desugaring (not supported atm) - // evm.assembly - New assembly format after desugaring (not supported atm) - // evm.asm - Current assembly format (--asm) - // evm.asmJSON - Current assembly format in JSON (--asm-json) + // evm.ir - New assembly format before desugaring + // evm.assembly - New assembly format after desugaring + // evm.legacyAssemblyJSON - Old-style assembly format in JSON // evm.opcodes - Opcodes list // evm.methodIdentifiers - The list of function hashes // evm.gasEstimates - Function gas estimates - // evm.bytecode - Bytecode (--bin) - // evm.deployedBytecode - Deployed bytecode (--bin-runtime) + // evm.bytecode - Bytecode + // evm.deployedBytecode - Deployed bytecode // evm.sourceMap - Source mapping (useful for debugging) // ewasm.wast - eWASM S-expressions format (not supported atm) // ewasm.wasm - eWASM binary format (not supported atm) @@ -218,9 +217,7 @@ Output Description // Assembly (string) assembly: "", // Old-style assembly (string) - asm: "", - // Old-style assembly (JSON object) - asmJSON: [], + legacyAssemblyJSON: [], // Bytecode and related details. bytecode: { // The bytecode as a hex string. From dc431fe1f6e0ff0ddcf1561b7ef136cc654cf976 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 19:22:40 +0000 Subject: [PATCH 227/414] Simplify the compiler API section (and remove pseudo code) --- docs/using-the-compiler.rst | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index f197c663b..08f181329 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -37,30 +37,18 @@ If ``solc`` is called with the option ``--link``, all input files are interprete .. _compiler-api: -Using the Compiler API -********************** - -The compiler API expects a JSON formatted input and outputs the compilations result in a JSON formatted output. - -See the following pseudo-code: - -.. code-block:: none - - // defined by the consumer of the API - importCallback(url:string) -> content:string - - // invoking the compiler - solc.compile(inputJSON:string, importCallback:function) -> outputJSON:string - -The compiler will ask the ``importCallback`` for each URL defined for a source file and will stop when it succeeds. -If all URLs failed, the compilation results in a failure. - Compiler Input and Output JSON Description ****************************************** +.. warning:: + + This JSON interface is not yet supported by the Solidity compiler, but will be released in a future version. + These JSON formats are used by the compiler API as well as are available through ``solc``. These are subject to change, some fields are optional (as noted), but it is aimed at to only make backwards compatible changes. +The compiler API expects a JSON formatted input and outputs the compilation result in a JSON formatted output. + Comments are of course not permitted and used here only for explanatory purposes. Input Description From 753e104cbcb0627300d1891f87fcd00b83950f46 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 9 Feb 2017 10:21:08 +0100 Subject: [PATCH 228/414] Use nodejs 6 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c31d9b2b2..b642d947f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,7 @@ matrix: dist: trusty sudo: required compiler: gcc - node_js: "5" + node_js: "6" services: - docker before_install: From b508aac64a1c189367a34e79906a7b2553d0ad15 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 13:34:00 +0000 Subject: [PATCH 229/414] Use only send/recv in IPC --- test/RPCSession.cpp | 9 +++------ test/RPCSession.h | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 968d77b1d..791d2cfe9 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -73,10 +73,6 @@ IPCSocket::IPCSocket(string const& _path): m_path(_path) if (connect(m_socket, reinterpret_cast(&saun), sizeof(struct sockaddr_un)) < 0) BOOST_FAIL("Error connecting to IPC socket: " << _path); - - m_fp = fdopen(m_socket, "r"); - if (!m_fp) - BOOST_FAIL("Error opening IPC socket: " << _path); #endif } @@ -113,11 +109,12 @@ string IPCSocket::sendRequest(string const& _req) return returnStr; #else - send(m_socket, _req.c_str(), _req.length(), 0); + if (send(m_socket, _req.c_str(), _req.length(), 0) != (ssize_t)_req.length()) + BOOST_FAIL("Writing on IPC failed"); char c; string response; - while ((c = fgetc(m_fp)) != EOF) + while (recv(m_socket, &c, 1, 0) == 1) { if (c != '\n') response += c; diff --git a/test/RPCSession.h b/test/RPCSession.h index fc166b99b..5369e1e2e 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -55,12 +55,11 @@ class IPCSocket: public boost::noncopyable public: IPCSocket(std::string const& _path); std::string sendRequest(std::string const& _req); - ~IPCSocket() { close(m_socket); fclose(m_fp); } + ~IPCSocket() { close(m_socket); } std::string const& path() const { return m_path; } private: - FILE *m_fp; std::string m_path; int m_socket; }; From 9cffa9a92edf564d85dfbaeee6cbabac3cd42c98 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 14:56:18 +0000 Subject: [PATCH 230/414] Do not use -Og for debug mode (won't work on Mac/clang) --- cmake/EthCompilerSettings.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index c734423b7..97db9168c 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -71,7 +71,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-fPIC) # Configuration-specific compiler settings. - set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DETH_DEBUG") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") From 5396c7692b87587dc35c07b2c6239782efd20739 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 14:10:43 +0000 Subject: [PATCH 231/414] Do not expect a new line, rather buffer up the response in IPC --- test/RPCSession.cpp | 32 ++++++++++++++------------------ test/RPCSession.h | 5 +++-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 791d2cfe9..8d18d118d 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -18,6 +18,7 @@ */ /** @file RPCSession.cpp * @author Dimtiry Khokhlov + * @author Alex Beregszaszi * @date 2016 */ @@ -91,18 +92,17 @@ string IPCSocket::sendRequest(string const& _req) if (!fSuccess) BOOST_FAIL("WriteFile to pipe failed"); - DWORD cbRead; - TCHAR chBuf[c_buffsize]; + DWORD cbRead; // Read from the pipe. fSuccess = ReadFile( - m_socket, // pipe handle - chBuf, // buffer to receive reply - c_buffsize,// size of buffer - &cbRead, // number of bytes read - NULL); // not overlapped + m_socket, // pipe handle + m_readBuf, // buffer to receive reply + sizeof(m_readBuf), // size of buffer + &cbRead, // number of bytes read + NULL); // not overlapped - returnStr += chBuf; + returnStr += m_readBuf; if (!fSuccess) BOOST_FAIL("ReadFile from pipe failed"); @@ -112,16 +112,12 @@ string IPCSocket::sendRequest(string const& _req) if (send(m_socket, _req.c_str(), _req.length(), 0) != (ssize_t)_req.length()) BOOST_FAIL("Writing on IPC failed"); - char c; - string response; - while (recv(m_socket, &c, 1, 0) == 1) - { - if (c != '\n') - response += c; - else - break; - } - return response; + ssize_t ret = recv(m_socket, m_readBuf, sizeof(m_readBuf), 0); + + if (ret < 0) + BOOST_FAIL("Reading on IPC failed"); + + return string(m_readBuf, m_readBuf + ret); #endif } diff --git a/test/RPCSession.h b/test/RPCSession.h index 5369e1e2e..f1aee6a81 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -35,7 +35,6 @@ #include #if defined(_WIN32) -const int c_buffsize = 5120000; //because windows pipe is broken and wont work as in examples. use larger buffer limit to receive whole package in one call class IPCSocket : public boost::noncopyable { public: @@ -47,7 +46,8 @@ public: private: std::string m_path; - HANDLE m_socket; + HANDLE m_socket; + TCHAR m_readBuf[512000]; }; #else class IPCSocket: public boost::noncopyable @@ -62,6 +62,7 @@ public: private: std::string m_path; int m_socket; + char m_readBuf[512000]; }; #endif From f9a818eaf8713f14f80576ea706d484d2bc92c7b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 12:24:23 +0000 Subject: [PATCH 232/414] Detect closed sockets in IPC --- test/RPCSession.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 8d18d118d..9d046c4fb 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -114,7 +114,8 @@ string IPCSocket::sendRequest(string const& _req) ssize_t ret = recv(m_socket, m_readBuf, sizeof(m_readBuf), 0); - if (ret < 0) + // Also consider closed socket an error. + if (ret <= 0) BOOST_FAIL("Reading on IPC failed"); return string(m_readBuf, m_readBuf + ret); From f2cafd497459d7ed0ea7f6efdeeb77a51cacecc7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 13:06:48 +0000 Subject: [PATCH 233/414] Simplify the Windows IPC code --- test/RPCSession.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 9d046c4fb..caaf9a8cb 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -80,7 +80,7 @@ IPCSocket::IPCSocket(string const& _path): m_path(_path) string IPCSocket::sendRequest(string const& _req) { #if defined(_WIN32) - string returnStr; + // Write to the pipe. DWORD cbWritten; BOOL fSuccess = WriteFile( m_socket, // pipe handle @@ -92,9 +92,8 @@ string IPCSocket::sendRequest(string const& _req) if (!fSuccess) BOOST_FAIL("WriteFile to pipe failed"); - DWORD cbRead; - // Read from the pipe. + DWORD cbRead; fSuccess = ReadFile( m_socket, // pipe handle m_readBuf, // buffer to receive reply @@ -102,12 +101,10 @@ string IPCSocket::sendRequest(string const& _req) &cbRead, // number of bytes read NULL); // not overlapped - returnStr += m_readBuf; - if (!fSuccess) BOOST_FAIL("ReadFile from pipe failed"); - return returnStr; + return string(m_readBuf, m_readBuf + cbRead); #else if (send(m_socket, _req.c_str(), _req.length(), 0) != (ssize_t)_req.length()) BOOST_FAIL("Writing on IPC failed"); From 95f8c5bcdb69ab4cba835f8bc06ab89da4768db8 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 13:08:52 +0000 Subject: [PATCH 234/414] Ensure that the whole message was written on Windows IPC --- test/RPCSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index caaf9a8cb..b3451528f 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -89,7 +89,7 @@ string IPCSocket::sendRequest(string const& _req) &cbWritten, // bytes written NULL); // not overlapped - if (!fSuccess) + if (!fSuccess || (_req.size() != cbWritten)) BOOST_FAIL("WriteFile to pipe failed"); // Read from the pipe. From 503cf4eaebabf03b8d541bd8f9c9c833daceec63 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 2 Feb 2017 18:52:41 -0600 Subject: [PATCH 235/414] reorganize travis Signed-off-by: RJ Catalano --- .travis.yml | 85 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/.travis.yml b/.travis.yml index b642d947f..eafba71f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,18 @@ branches: - develop - release - /^v[0-9]/ + +env: + global: + - ENCRYPTION_LABEL="6d4541b72666" + - SOLC_BUILD_TYPE=RelWithDebInfo + - SOLC_DOCS=Off + - SOLC_EMSCRIPTEN=Off + - SOLC_INSTALL_DEPS_TRAVIS=On + - SOLC_RELEASE=On + - SOLC_TESTS=On + - SOLC_DOCKER=Off + matrix: include: # Ubuntu 14.04 LTS "Trusty Tahr" @@ -61,10 +73,23 @@ matrix: dist: trusty sudo: required compiler: gcc + before_install: + - sudo apt-get -y install python-sphinx env: - - TRAVIS_DOCS=On - - TRAVIS_RELEASE=Off - - TRAVIS_TESTS=Off + - SOLC_DOCS=On + - SOLC_RELEASE=Off + - SOLC_TESTS=Off + # Docker target, which generates a statically linked alpine image + - os: linux + dist: trusty + sudo: required + services: + - docker + env: + - SOLC_DOCKER=On + - SOLC_INSTALL_DEPS_TRAVIS=Off + - SOLC_RELEASE=Off + - SOLC_TESTS=Off # Emscripten target, which compiles 'solc' to javascript and uploads the resulting .js # files to https://github.com/ethereum/solc-bin. These binaries are used in Browser-Solidity @@ -79,10 +104,10 @@ matrix: before_install: - docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit env: - - TRAVIS_EMSCRIPTEN=On - - TRAVIS_INSTALL_DEPS=Off - - TRAVIS_RELEASE=Off - - TRAVIS_TESTS=Off + - SOLC_EMSCRIPTEN=On + - SOLC_INSTALL_DEPS_TRAVIS=Off + - SOLC_RELEASE=Off + - SOLC_TESTS=Off # OS X Mavericks (10.9) # https://en.wikipedia.org/wiki/OS_X_Mavericks @@ -143,37 +168,18 @@ cache: - $HOME/.local install: - - test $TRAVIS_INSTALL_DEPS != On || ./scripts/install_deps.sh + - test $SOLC_INSTALL_DEPS_TRAVIS != On || ./scripts/install_deps.sh - test "$TRAVIS_OS_NAME" != "linux" || ./scripts/install_cmake.sh - echo -n "$TRAVIS_COMMIT" > commit_hash.txt + - test $SOLC_DOCKER != On || docker build -t ethereum/solc:build -f ./scripts/Dockerfile . before_script: - - test $TRAVIS_EMSCRIPTEN != On || ./scripts/build_emscripten.sh - - test $TRAVIS_RELEASE != On || (./scripts/build.sh $TRAVIS_BUILD_TYPE + - test $SOLC_EMSCRIPTEN != On || ./scripts/build_emscripten.sh + - test $SOLC_RELEASE != On || (./scripts/build.sh $SOLC_BUILD_TYPE && ./scripts/release.sh $ZIP_SUFFIX && ./scripts/create_source_tarball.sh ) script: - - test $TRAVIS_DOCS != On || ./scripts/docs.sh - - # There are a variety of reliability issues with the Solidity unit-tests at the time of - # writing (especially on macOS), so within TravisCI we will try to run the unit-tests - # up to 3 times before giving up and declaring the tests as broken. - # - # We should aim to remove this "retry logic" as soon as we can, because it is a - # band-aid for issues which need solving at their root. Some of those issues will be - # in Solidity's RPC setup and some will be in 'eth'. It seems unlikely that Solidity - # itself is broken from the failure messages which we are seeing. - # - # More details on known issues at https://github.com/ethereum/solidity/issues/769 - - test $TRAVIS_TESTS != On || (cd $TRAVIS_BUILD_DIR && (./scripts/tests.sh || ./scripts/tests.sh || ./scripts/tests.sh) ) -env: - global: - - ENCRYPTION_LABEL="6d4541b72666" - - TRAVIS_BUILD_TYPE=RelWithDebInfo - - TRAVIS_DOCS=Off - - TRAVIS_EMSCRIPTEN=Off - - TRAVIS_INSTALL_DEPS=On - - TRAVIS_RELEASE=On - - TRAVIS_TESTS=On + - test $SOLC_DOCS != On || ./scripts/docs.sh + - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && ./scripts/tests.sh ) deploy: # This is the deploy target for the Emscripten build. @@ -182,14 +188,23 @@ deploy: # Both the build and deploy steps for Emscripten are only run within the Ubuntu # configurations (not for macOS). That is controlled by conditionals within the bash # scripts because TravisCI doesn't provide much in the way of conditional logic. + # This is also the deploy target for the dockerfile. If we are pushing into a develop branch, it will be tagged + # as a nightly and appended the commit of the branch it was pushed in. If we are pushing to master it will + # be tagged as "stable" and given the version tag as well. - provider: script - script: test $TRAVIS_EMSCRIPTEN != On || scripts/release_emscripten.sh + script: test $SOLC_EMSCRIPTEN == On || scripts/release_emscripten.sh + skip_cleanup: true + on: + branch: + - develop + - release + - provider: script + script: test $SOLC_DOCKER != On || ./scripts/docker_deploy.sh skip_cleanup: true on: branch: - develop - release - # This is the deploy target for the native build (Linux and macOS) # which generates ZIPs per commit and the source tarball. # @@ -207,4 +222,4 @@ deploy: on: all_branches: true tags: true - condition: $TRAVIS_RELEASE == On + condition: $SOLC_RELEASE == On \ No newline at end of file From 00feec567afbedc0b09805d7226c94c1d96c0c2a Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 2 Feb 2017 18:52:59 -0600 Subject: [PATCH 236/414] reorganize deps installation Signed-off-by: RJ Catalano --- scripts/install_deps.sh | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index f21c48d03..869bce35a 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -57,7 +57,7 @@ detect_linux_distro() { # extract 'foo' from NAME=foo, only on the line with NAME=foo DISTRO=$(sed -n -e 's/^NAME="\(.*\)\"/\1/p' /etc/os-release) elif [ -f /etc/centos-release ]; then - DISTRO=CentOS + DISTRO=CentOS else DISTRO='' fi @@ -93,19 +93,17 @@ case $(uname -s) in # Check for Homebrew install and abort if it is not installed. brew -v > /dev/null 2>&1 || { echo >&2 "ERROR - solidity requires a Homebrew install. See http://brew.sh."; exit 1; } - brew update - brew upgrade - brew install boost brew install cmake - - # We should really 'brew install' our eth client here, but at the time of writing - # the bottle is known broken, so we will just cheat and use a hardcoded ZIP for - # the time being, which is good enough. The cause of the breaks will go away - # when we commit the repository reorg changes anyway. - curl -L -O https://github.com/bobsummerwill/cpp-ethereum/releases/download/v1.3.0/cpp-ethereum-osx-mavericks-v1.3.0.zip - unzip cpp-ethereum-osx-mavericks-v1.3.0.zip + if ["$CI" = true]; then + brew upgrade cmake + brew tap ethereum/ethereum + brew install cpp-ethereum + brew linkapps cpp-ethereum + else + brew upgrade + fi ;; @@ -207,7 +205,6 @@ case $(uname -s) in # Install "normal packages" sudo apt-get -y update sudo apt-get -y install \ - python-sphinx \ build-essential \ cmake \ g++ \ @@ -311,17 +308,17 @@ case $(uname -s) in sudo apt-get -y update sudo apt-get -y install \ - python-sphinx \ build-essential \ cmake \ git \ libboost-all-dev - - # Install 'eth', for use in the Solidity Tests-over-IPC. - sudo add-apt-repository -y ppa:ethereum/ethereum - sudo add-apt-repository -y ppa:ethereum/ethereum-dev - sudo apt-get -y update - sudo apt-get -y install eth + if [ "$CI" = true ]; then + # Install 'eth', for use in the Solidity Tests-over-IPC. + sudo add-apt-repository -y ppa:ethereum/ethereum + sudo add-apt-repository -y ppa:ethereum/ethereum-dev + sudo apt-get -y update + sudo apt-get -y install eth + fi ;; @@ -397,4 +394,4 @@ case $(uname -s) in echo "If you would like to get your operating system working, that would be fantastic." echo "Drop us a message at https://gitter.im/ethereum/solidity." ;; -esac +esac \ No newline at end of file From d76d9d41690b69622b703b7a3f567e830d095f9e Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 2 Feb 2017 18:53:30 -0600 Subject: [PATCH 237/414] create automated docker deployment Signed-off-by: RJ Catalano --- scripts/Dockerfile | 22 +++++++++++++--------- scripts/docker_deploy.sh | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) create mode 100755 scripts/docker_deploy.sh diff --git a/scripts/Dockerfile b/scripts/Dockerfile index e34436ed2..8be19783e 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -1,12 +1,16 @@ FROM alpine MAINTAINER chriseth +#Official solidity docker image -RUN \ - apk --no-cache --update add build-base cmake boost-dev git && \ - sed -i -E -e 's/include /include /' /usr/include/boost/asio/detail/socket_types.hpp && \ - git clone --depth 1 --recursive -b develop https://github.com/ethereum/solidity && \ - cd /solidity && cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSTATIC_LINKING=1 && \ - cd /solidity && make solc && install -s solc/solc /usr/bin && \ - cd / && rm -rf solidity && \ - apk del sed build-base git make cmake gcc g++ musl-dev curl-dev boost-dev && \ - rm -rf /var/cache/apk/* +#Establish working directory as solidity +WORKDIR /solidity +#Copy working directory on travis to the image +COPY / $WORKDIR + +#Install dependencies, eliminate annoying warnings, and build release, delete all remaining points and statically link. +RUN ./scripts/install_deps.sh && sed -i -E -e 's/include /include /' /usr/include/boost/asio/detail/socket_types.hpp &&\ +cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSTATIC_LINKING=1 &&\ +make solc && install -s solc/solc /usr/bin &&\ +cd / && rm -rf solidity &&\ +apk del sed build-base git make cmake gcc g++ musl-dev curl-dev boost-dev &&\ +rm -rf /var/cache/apk/* \ No newline at end of file diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh new file mode 100755 index 000000000..f4c61601b --- /dev/null +++ b/scripts/docker_deploy.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; +version=version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/CMakeLists.txt) +if [ "$TRAVIS_BRANCH" == "develop" ]; then + docker tag ethereum/solc:build ethereum/solc:nightly; + docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT" + docker push ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT"; + docker push ethereum/solc:nightly; +elif [ "$TRAVIS_BRANCH" == "release"]; then + docker tag ethereum/solc:build ethereum/solc:stable; + docker tag ethereum/solc:build ethereum/solc:"$version"; + docker tag ethereum/solc:build ethereum/solc: + docker push ethereum/solc; + docker push ethereum/solc:"$version"; + docker push ethereum/solc:stable; +fi \ No newline at end of file From d9e7af939ca202975a6dff7537d73614d75f1470 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 2 Feb 2017 18:53:42 -0600 Subject: [PATCH 238/414] defeat race condition Signed-off-by: RJ Catalano --- scripts/tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/tests.sh b/scripts/tests.sh index 8edfda0bc..88815f5f6 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -58,7 +58,7 @@ $ETH_PATH --test -d /tmp/test & # is available and is ready for the unit-tests to start talking to it. while [ ! -S /tmp/test/geth.ipc ]; do sleep 2; done echo "--> IPC available." - +sleep 2 # And then run the Solidity unit-tests (once without optimization, once with), # pointing to that IPC endpoint. echo "--> Running tests without optimizer..." @@ -69,4 +69,4 @@ ERROR_CODE=$? pkill eth || true sleep 4 pgrep eth && pkill -9 eth || true -exit $ERROR_CODE +exit $ERROR_CODE \ No newline at end of file From 7ffc6863fb33775fb95b6ca2994dd769d3db33c3 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 2 Feb 2017 20:57:07 -0600 Subject: [PATCH 239/414] edit the documentation for the travis file Signed-off-by: RJ Catalano --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index eafba71f6..df8b4ebba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -188,9 +188,7 @@ deploy: # Both the build and deploy steps for Emscripten are only run within the Ubuntu # configurations (not for macOS). That is controlled by conditionals within the bash # scripts because TravisCI doesn't provide much in the way of conditional logic. - # This is also the deploy target for the dockerfile. If we are pushing into a develop branch, it will be tagged - # as a nightly and appended the commit of the branch it was pushed in. If we are pushing to master it will - # be tagged as "stable" and given the version tag as well. + - provider: script script: test $SOLC_EMSCRIPTEN == On || scripts/release_emscripten.sh skip_cleanup: true @@ -198,6 +196,9 @@ deploy: branch: - develop - release + # This is the deploy target for the dockerfile. If we are pushing into a develop branch, it will be tagged + # as a nightly and appended the commit of the branch it was pushed in. If we are pushing to master it will + # be tagged as "stable" and given the version tag as well. - provider: script script: test $SOLC_DOCKER != On || ./scripts/docker_deploy.sh skip_cleanup: true From e884f7a4790c498e4b406e42aec5a7f7b4102368 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Tue, 7 Feb 2017 16:20:48 -0600 Subject: [PATCH 240/414] minor fixups Signed-off-by: RJ Catalano --- scripts/docker_deploy.sh | 2 +- scripts/install_deps.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh index f4c61601b..870cca442 100755 --- a/scripts/docker_deploy.sh +++ b/scripts/docker_deploy.sh @@ -1,7 +1,7 @@ #!/usr/bin/env sh docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; -version=version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/CMakeLists.txt) +version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/CMakeLists.txt) if [ "$TRAVIS_BRANCH" == "develop" ]; then docker tag ethereum/solc:build ethereum/solc:nightly; docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT" diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 869bce35a..7cfc92f2f 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -57,7 +57,7 @@ detect_linux_distro() { # extract 'foo' from NAME=foo, only on the line with NAME=foo DISTRO=$(sed -n -e 's/^NAME="\(.*\)\"/\1/p' /etc/os-release) elif [ -f /etc/centos-release ]; then - DISTRO=CentOS + DISTRO=CentOS else DISTRO='' fi From e9ae50dc5994a02119655eff4ed488fb3918d41f Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Thu, 9 Feb 2017 11:36:08 -0600 Subject: [PATCH 241/414] clarify branches for docker to push on and clarify where to find cmakelists.txt Signed-off-by: RJ Catalano --- scripts/docker_deploy.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh index 870cca442..dcd03f9ef 100755 --- a/scripts/docker_deploy.sh +++ b/scripts/docker_deploy.sh @@ -1,7 +1,7 @@ #!/usr/bin/env sh docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; -version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/CMakeLists.txt) +version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/../CMakeLists.txt) if [ "$TRAVIS_BRANCH" == "develop" ]; then docker tag ethereum/solc:build ethereum/solc:nightly; docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT" @@ -9,9 +9,8 @@ if [ "$TRAVIS_BRANCH" == "develop" ]; then docker push ethereum/solc:nightly; elif [ "$TRAVIS_BRANCH" == "release"]; then docker tag ethereum/solc:build ethereum/solc:stable; - docker tag ethereum/solc:build ethereum/solc:"$version"; - docker tag ethereum/solc:build ethereum/solc: - docker push ethereum/solc; - docker push ethereum/solc:"$version"; docker push ethereum/solc:stable; +elif [ "$TRAVIS_BRANCH" == v"$version"]; then + docker tag ethereum/solc:build ethereum/solc:"$version"; + docker push ethereum/solc:"$version"; fi \ No newline at end of file From 4f6ebae36c03bea6882c77a1c7841095e7c99049 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 10 Feb 2017 14:07:46 +0100 Subject: [PATCH 242/414] Check for tag in travis. --- scripts/docker_deploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh index dcd03f9ef..c9682c166 100755 --- a/scripts/docker_deploy.sh +++ b/scripts/docker_deploy.sh @@ -10,7 +10,7 @@ if [ "$TRAVIS_BRANCH" == "develop" ]; then elif [ "$TRAVIS_BRANCH" == "release"]; then docker tag ethereum/solc:build ethereum/solc:stable; docker push ethereum/solc:stable; -elif [ "$TRAVIS_BRANCH" == v"$version"]; then +elif [ "$TRAVIS_TAG" == v"$version"]; then docker tag ethereum/solc:build ethereum/solc:"$version"; docker push ethereum/solc:"$version"; -fi \ No newline at end of file +fi From 3128ec2ca5d30aaca78aae457642214f3f52e31f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 16:20:05 +0000 Subject: [PATCH 243/414] Add blockNumber and blockTimestamp to ExecutionFramework --- test/ExecutionFramework.cpp | 8 ++++++++ test/ExecutionFramework.h | 2 ++ test/RPCSession.cpp | 1 + test/RPCSession.h | 1 + 4 files changed, 12 insertions(+) diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index ddcd9cb68..701396428 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -82,6 +82,8 @@ void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256 m_rpc.test_mineBlocks(1); RPCSession::TransactionReceipt receipt(m_rpc.eth_getTransactionReceipt(txHash)); + m_blockNumber = u256(receipt.blockNumber); + if (_isCreation) { m_contractAddress = Address(receipt.contractAddress); @@ -129,6 +131,12 @@ size_t ExecutionFramework::currentTimestamp() return size_t(u256(latestBlock.get("timestamp", "invalid").asString())); } +size_t ExecutionFramework::blockTimestamp(u256 _number) +{ + auto latestBlock = m_rpc.rpcCall("eth_getBlockByNumber", {toString(_number), "false"}); + return size_t(u256(latestBlock.get("timestamp", "invalid").asString())); +} + Address ExecutionFramework::account(size_t _i) { return Address(m_rpc.accountCreateIfNotExists(_i)); diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 733fd56dc..76d0fd8cf 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -262,6 +262,7 @@ protected: void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0); void sendEther(Address const& _to, u256 const& _value); size_t currentTimestamp(); + size_t blockTimestamp(u256 number); /// @returns the (potentially newly created) _ith address. Address account(size_t _i); @@ -284,6 +285,7 @@ protected: bool m_showMessages = false; Address m_sender; Address m_contractAddress; + u256 m_blockNumber; u256 const m_gasPrice = 100 * szabo; u256 const m_gas = 100000000; bytes m_output; diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index b3451528f..a58355d0b 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -138,6 +138,7 @@ RPCSession::TransactionReceipt RPCSession::eth_getTransactionReceipt(string cons BOOST_REQUIRE(!result.isNull()); receipt.gasUsed = result["gasUsed"].asString(); receipt.contractAddress = result["contractAddress"].asString(); + receipt.blockNumber = result["blockNumber"].asString(); for (auto const& log: result["logs"]) { LogEntry entry; diff --git a/test/RPCSession.h b/test/RPCSession.h index f1aee6a81..b2e8a3092 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -92,6 +92,7 @@ public: std::string gasUsed; std::string contractAddress; std::vector logEntries; + std::string blockNumber; }; static RPCSession& instance(std::string const& _path); From 4cf44f1b41fe021653a6f45c72c19253dd352459 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 7 Feb 2017 16:20:25 +0000 Subject: [PATCH 244/414] Do not use modifyTimestamp where not needed --- test/libsolidity/SolidityEndToEndTest.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 4924b55d0..3c2e939cf 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1482,9 +1482,12 @@ BOOST_AUTO_TEST_CASE(now) } } )"; - m_rpc.test_modifyTimestamp(0x776347e2); compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("someInfo()") == encodeArgs(true, 0x776347e3)); + u256 startBlock = m_blockNumber; + auto ret = callContractFunction("someInfo()"); + u256 endBlock = m_blockNumber; + BOOST_CHECK(startBlock != endBlock); + BOOST_CHECK(ret == encodeArgs(true, blockTimestamp(endBlock))); } BOOST_AUTO_TEST_CASE(type_conversions_cleanup) From 702ee20a0179afe607383554ee89b0f863e14f63 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 18:41:51 +0000 Subject: [PATCH 245/414] Create getBlockByNumber RPC method --- test/ExecutionFramework.cpp | 4 ++-- test/RPCSession.cpp | 10 ++++++++-- test/RPCSession.h | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index 701396428..f4e5fcef5 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -127,13 +127,13 @@ void ExecutionFramework::sendEther(Address const& _to, u256 const& _value) size_t ExecutionFramework::currentTimestamp() { - auto latestBlock = m_rpc.rpcCall("eth_getBlockByNumber", {"\"latest\"", "false"}); + auto latestBlock = m_rpc.eth_getBlockByNumber("latest", false); return size_t(u256(latestBlock.get("timestamp", "invalid").asString())); } size_t ExecutionFramework::blockTimestamp(u256 _number) { - auto latestBlock = m_rpc.rpcCall("eth_getBlockByNumber", {toString(_number), "false"}); + auto latestBlock = m_rpc.eth_getBlockByNumber(toString(_number), false); return size_t(u256(latestBlock.get("timestamp", "invalid").asString())); } diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index a58355d0b..c27e73d42 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -131,6 +131,12 @@ string RPCSession::eth_getCode(string const& _address, string const& _blockNumbe return rpcCall("eth_getCode", { quote(_address), quote(_blockNumber) }).asString(); } +Json::Value RPCSession::eth_getBlockByNumber(string const& _blockNumber, bool _fullObjects) +{ + // NOTE: to_string() converts bool to 0 or 1 + return rpcCall("eth_getBlockByNumber", { quote(_blockNumber), _fullObjects ? "true" : "false" }); +} + RPCSession::TransactionReceipt RPCSession::eth_getTransactionReceipt(string const& _transactionHash) { TransactionReceipt receipt; @@ -290,9 +296,9 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector const& request += "],\"id\":" + to_string(m_rpcSequence) + "}"; ++m_rpcSequence; - //cout << "Request: " << request << endl; + // cout << "Request: " << request << endl; string reply = m_ipcSocket.sendRequest(request); - //cout << "Reply: " << reply << endl; + // cout << "Reply: " << reply << endl; Json::Value result; BOOST_REQUIRE(Json::Reader().parse(reply, result, false)); diff --git a/test/RPCSession.h b/test/RPCSession.h index b2e8a3092..105ba3788 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -98,6 +98,7 @@ public: static RPCSession& instance(std::string const& _path); std::string eth_getCode(std::string const& _address, std::string const& _blockNumber); + Json::Value eth_getBlockByNumber(std::string const& _blockNumber, bool _fullObjects); std::string eth_call(TransactionData const& _td, std::string const& _blockNumber); TransactionReceipt eth_getTransactionReceipt(std::string const& _transactionHash); std::string eth_sendTransaction(TransactionData const& _transactionData); From a82acba49ae68a960f33a2938f3d3560bf65441c Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 13:26:11 +0000 Subject: [PATCH 246/414] Compare start/end timestamp --- test/libsolidity/SolidityEndToEndTest.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 3c2e939cf..53b61450d 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1484,10 +1484,13 @@ BOOST_AUTO_TEST_CASE(now) )"; compileAndRun(sourceCode); u256 startBlock = m_blockNumber; + size_t startTime = blockTimestamp(startBlock); auto ret = callContractFunction("someInfo()"); u256 endBlock = m_blockNumber; + size_t endTime = blockTimestamp(endBlock); BOOST_CHECK(startBlock != endBlock); - BOOST_CHECK(ret == encodeArgs(true, blockTimestamp(endBlock))); + BOOST_CHECK(startTime != endTime); + BOOST_CHECK(ret == encodeArgs(true, endTime)); } BOOST_AUTO_TEST_CASE(type_conversions_cleanup) From f8461e9e31b96e6656b8dcabd73de2e5176e6fe3 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 01:37:53 +0000 Subject: [PATCH 247/414] Implement assert as a global function --- Changelog.md | 1 + libsolidity/analysis/GlobalContext.cpp | 4 +++- libsolidity/ast/Types.h | 7 ++++--- libsolidity/codegen/ExpressionCompiler.cpp | 8 ++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 0c4e83294..e8656ac87 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.10 (unreleased) Features: + * Add ``assert(condition)`` to abort execution. * Type system: Support explicit conversion of external function to address. Bugfixes: diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index e46868be2..cc418c5e0 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -65,7 +65,9 @@ m_magicVariables(vector>{make_shared< make_shared("ecrecover", make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared("ripemd160", - make_shared(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))}) + make_shared(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), + make_shared("assert", + make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert))}) { } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index e280b32c2..83d840e06 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -819,8 +819,8 @@ public: { Internal, ///< stack-call using plain JUMP External, ///< external call using CALL - CallCode, ///< extercnal call using CALLCODE, i.e. not exchanging the storage - DelegateCall, ///< extercnal call using DELEGATECALL, i.e. not exchanging the storage + CallCode, ///< external call using CALLCODE, i.e. not exchanging the storage + DelegateCall, ///< external call using DELEGATECALL, i.e. not exchanging the storage Bare, ///< CALL without function hash BareCallCode, ///< CALLCODE without function hash BareDelegateCall, ///< DELEGATECALL without function hash @@ -844,7 +844,8 @@ public: MulMod, ///< MULMOD ArrayPush, ///< .push() to a dynamically sized array in storage ByteArrayPush, ///< .push() to a dynamically sized byte array in storage - ObjectCreation ///< array creation using new + ObjectCreation, ///< array creation using new + Assert ///< assert() }; virtual Category category() const override { return Category::Function; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index d74d9dd3d..a99e3d406 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -863,6 +863,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::POP; break; } + case Location::Assert: + { + arguments.front()->accept(*this); + utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); + m_context << Instruction::ISZERO; + m_context.appendConditionalJumpTo(m_context.errorTag()); + break; + } default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type.")); } From 8429c03f2ab81ad5fe077df6f69f6dbe88609779 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 15:27:23 +0000 Subject: [PATCH 248/414] Add tests for assert() --- test/libsolidity/SolidityEndToEndTest.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 4924b55d0..e49db34e9 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9077,6 +9077,25 @@ BOOST_AUTO_TEST_CASE(invalid_instruction) BOOST_CHECK(callContractFunction("f()") == encodeArgs()); } +BOOST_AUTO_TEST_CASE(assert) +{ + char const* sourceCode = R"( + contract C { + function f() { + assert(false); + } + function g(bool val) returns (bool) { + assert(val == true); + return true; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("g(bool)", false) == encodeArgs()); + BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } From 7f726de1cb861f4c7cb4e89e4ec56cd5712d0855 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 15:31:03 +0000 Subject: [PATCH 249/414] Document assert() --- docs/miscellaneous.rst | 3 ++- docs/units-and-global-variables.rst | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 476500676..27289daf1 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -435,7 +435,7 @@ The following is the order of precedence for operators, listed in order of evalu | *16* | Comma operator | ``,`` | +------------+-------------------------------------+--------------------------------------------+ -.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, assert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Global Variables ================ @@ -460,6 +460,7 @@ Global Variables - ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error - ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256`` - ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256`` +- ``assert(bool condition)``: throws if the condition is not met - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index dd3d4be8d..a6f6613f1 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -79,11 +79,13 @@ Block and Transaction Properties You can only access the hashes of the most recent 256 blocks, all other values will be zero. -.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: assert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Mathematical and Cryptographic Functions ---------------------------------------- +``assert(bool condition)``: + throws if the condition is not met. ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``. ``mulmod(uint x, uint y, uint k) returns (uint)``: From 39cd2214f2161d2e09ab0de21a64f6dcdb851b13 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 13:31:40 +0000 Subject: [PATCH 250/414] Document user provided exceptions --- docs/control-structures.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 1c7d71f26..ff0a48ec1 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -396,6 +396,10 @@ Currently, Solidity automatically generates a runtime exception in the following #. If your contract receives Ether via a public getter function. #. If you call a zero-initialized variable of internal function type. +While a user-provided exception is generated in the following situations: +#. Calling ``throw``. +#. The condition of ``assert(condition)`` is not met. + Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation (instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect From 8a3d4a0500e98dbca83c0217d141fcb4860986e3 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 13:32:36 +0000 Subject: [PATCH 251/414] Cleanup is not needed for assert() --- libsolidity/codegen/ExpressionCompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a99e3d406..f69d61db9 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -866,7 +866,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case Location::Assert: { arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); + utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); m_context << Instruction::ISZERO; m_context.appendConditionalJumpTo(m_context.errorTag()); break; From fd7ffedead51f77135250dca29326c890f3632a2 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 21:41:40 +0000 Subject: [PATCH 252/414] Use different wording for assert --- Changelog.md | 2 +- docs/miscellaneous.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index e8656ac87..d383ba421 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,7 +1,7 @@ ### 0.4.10 (unreleased) Features: - * Add ``assert(condition)`` to abort execution. + * Add ``assert(condition)``, which throws if condition is false. * Type system: Support explicit conversion of external function to address. Bugfixes: diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 27289daf1..a64ceeb2b 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -460,7 +460,7 @@ Global Variables - ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error - ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256`` - ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256`` -- ``assert(bool condition)``: throws if the condition is not met +- ``assert(bool condition)``: throws if the condition is false - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address From 148f9233516ff5ad94d81aba9bc9c0440d3afc7b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 20:21:27 +0000 Subject: [PATCH 253/414] Add REVERT to libevmasm --- libevmasm/GasMeter.cpp | 1 + libevmasm/Instruction.cpp | 2 ++ libevmasm/Instruction.h | 1 + libevmasm/PeepholeOptimiser.cpp | 3 ++- libevmasm/SemanticInformation.cpp | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 462c09dd0..a0adc35d5 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -80,6 +80,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas += GasCosts::sloadGas; break; case Instruction::RETURN: + case Instruction::REVERT: gas += memoryGas(0, -1); break; case Instruction::MLOAD: diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index f9ee9be1f..de6630f30 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -159,6 +159,7 @@ const std::map dev::solidity::c_instructions = { "CALLCODE", Instruction::CALLCODE }, { "RETURN", Instruction::RETURN }, { "DELEGATECALL", Instruction::DELEGATECALL }, + { "REVERT", Instruction::REVERT }, { "INVALID", Instruction::INVALID }, { "SELFDESTRUCT", Instruction::SELFDESTRUCT } }; @@ -294,6 +295,7 @@ static const std::map c_instructionInfo = { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, + { Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } }, { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } } }; diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 7f56ad3a9..d79ec969e 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -177,6 +177,7 @@ enum class Instruction: uint8_t RETURN, ///< halt execution returning output data DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender + REVERT = 0xfd, ///< halt execution, revert state and return output data INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion }; diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 9a8341ab9..6c92d76bc 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -200,7 +200,8 @@ struct UnreachableCode it[0] != Instruction::RETURN && it[0] != Instruction::STOP && it[0] != Instruction::INVALID && - it[0] != Instruction::SELFDESTRUCT + it[0] != Instruction::SELFDESTRUCT && + it[0] != Instruction::REVERT ) return false; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 3a0843b8f..61586e7b7 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -119,6 +119,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) case Instruction::SELFDESTRUCT: case Instruction::STOP: case Instruction::INVALID: + case Instruction::REVERT: return true; default: return false; From f3158f92d6070b6088c6a1b32f2934b9cd7dde1b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 22:11:19 +0000 Subject: [PATCH 254/414] Support revert() --- libsolidity/analysis/GlobalContext.cpp | 4 +++- libsolidity/ast/Types.cpp | 1 + libsolidity/ast/Types.h | 1 + libsolidity/codegen/ExpressionCompiler.cpp | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index cc418c5e0..4f100cd0e 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -67,7 +67,9 @@ m_magicVariables(vector>{make_shared< make_shared("ripemd160", make_shared(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), make_shared("assert", - make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert))}) + make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert)), + make_shared("revert", + make_shared(strings(), strings(), FunctionType::Location::Revert))}) { } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 4a64b4c81..5b7b4a2cd 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2095,6 +2095,7 @@ string FunctionType::identifier() const case Location::Send: id += "send"; break; case Location::SHA3: id += "sha3"; break; case Location::Selfdestruct: id += "selfdestruct"; break; + case Location::Revert: id += "revert"; break; case Location::ECRecover: id += "ecrecover"; break; case Location::SHA256: id += "sha256"; break; case Location::RIPEMD160: id += "ripemd160"; break; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 83d840e06..3546e5220 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -828,6 +828,7 @@ public: Send, ///< CALL, but without data and gas SHA3, ///< SHA3 Selfdestruct, ///< SELFDESTRUCT + Revert, ///< REVERT ECRecover, ///< CALL to special contract for ecrecover SHA256, ///< CALL to special contract for sha256 RIPEMD160, ///< CALL to special contract for ripemd160 diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index f69d61db9..316ae888d 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -650,6 +650,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); m_context << Instruction::SELFDESTRUCT; break; + case Location::Revert: + // memory offset returned - zero length + m_context << u256(0) << u256(0); + m_context << Instruction::REVERT; + break; case Location::SHA3: { TypePointers argumentTypes; From 1fcad8b4ab48f63504c69c24372fb34f34a79436 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 22:47:05 +0000 Subject: [PATCH 255/414] Document revert() --- Changelog.md | 2 ++ docs/assembly.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index d383ba421..bd514cfe6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ Features: * Add ``assert(condition)``, which throws if condition is false. + * Code generator: Support ``revert()`` to abort with rolling back, but not consuming all gas. + * Inline assembly: Support ``revert`` (EIP140) as an opcode. * Type system: Support explicit conversion of external function to address. Bugfixes: diff --git a/docs/assembly.rst b/docs/assembly.rst index 79137b7e9..23ccfcbe2 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -248,6 +248,8 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+------+-----------------------------------------------------------------+ | return(p, s) | `-` | end execution, return data mem[p..(p+s)) | +-------------------------+------+-----------------------------------------------------------------+ +| revert(p, s) | `-` | end execution, revert state changes, return data mem[p..(p+s)) | ++-------------------------+------+-----------------------------------------------------------------+ | selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a | +-------------------------+------+-----------------------------------------------------------------+ | invalid | `-` | end execution with invalid instruction | From 586d156f33c78469f3272b4e8b8eae4f6229d04e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 23:16:47 +0000 Subject: [PATCH 256/414] Use the REVERT opcode for throw; --- libsolidity/codegen/ContractCompiler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 9d6129a36..6524bd039 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -762,7 +762,9 @@ bool ContractCompiler::visit(Return const& _return) bool ContractCompiler::visit(Throw const& _throw) { CompilerContext::LocationSetter locationSetter(m_context, _throw); - m_context.appendJumpTo(m_context.errorTag()); + // Do not send back an error detail. + m_context << u256(0) << u256(0); + m_context << Instruction::REVERT; return false; } From 28a7b1e019dc6f694d0615d7ef1220f19c10e861 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 16:32:48 +0000 Subject: [PATCH 257/414] Document revert() --- docs/control-structures.rst | 2 +- docs/miscellaneous.rst | 3 ++- docs/units-and-global-variables.rst | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index ff0a48ec1..df8ac729c 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -400,7 +400,7 @@ While a user-provided exception is generated in the following situations: #. Calling ``throw``. #. The condition of ``assert(condition)`` is not met. -Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation +Internally, Solidity performs a revert operation (instruction ``0xfd``) when a user-provided exception is thrown. In contrast, it performs an invalid operation (instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index a64ceeb2b..3c57507ef 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -435,7 +435,7 @@ The following is the order of precedence for operators, listed in order of evalu | *16* | Comma operator | ``,`` | +------------+-------------------------------------+--------------------------------------------+ -.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, assert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Global Variables ================ @@ -453,6 +453,7 @@ Global Variables - ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``) - ``tx.gasprice`` (``uint``): gas price of the transaction - ``tx.origin`` (``address``): sender of the transaction (full call chain) +- ``revert()``: abort execution and revert state changes - ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments - ``sha3(...) returns (bytes32)``: an alias to `keccak256()` - ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index a6f6613f1..72741b672 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -79,7 +79,7 @@ Block and Transaction Properties You can only access the hashes of the most recent 256 blocks, all other values will be zero. -.. index:: assert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Mathematical and Cryptographic Functions ---------------------------------------- @@ -100,6 +100,8 @@ Mathematical and Cryptographic Functions compute RIPEMD-160 hash of the (tightly packed) arguments ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover the address associated with the public key from elliptic curve signature or return zero on error +``revert()``: + abort execution and revert state changes In the above, "tightly packed" means that the arguments are concatenated without padding. This means that the following are all identical:: From f26fe5bc1c835302af27981e96d0ebbf31c80389 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 16:34:48 +0000 Subject: [PATCH 258/414] Add tests for revert() --- test/libsolidity/InlineAssembly.cpp | 5 +++++ test/libsolidity/SolidityEndToEndTest.cpp | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index cf0343a92..37d17495a 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -205,6 +205,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment) BOOST_CHECK(!successAssemble("{ gas := 2 }")); } +BOOST_AUTO_TEST_CASE(revert) +{ + BOOST_CHECK(successAssemble("{ revert(0, 0) }")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index e49db34e9..8c0e29fd2 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9096,6 +9096,25 @@ BOOST_AUTO_TEST_CASE(assert) BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); } +BOOST_AUTO_TEST_CASE(revert) +{ + char const* sourceCode = R"( + contract C { + function f() { + revert(); + } + function g() { + assembly { + revert(0, 0) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("g()") == encodeArgs()); +} + BOOST_AUTO_TEST_SUITE_END() } From 30cfad35484e9b903bf90bb723382aebc832d560 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 13:37:06 +0000 Subject: [PATCH 259/414] Check for state changes in revert() tests --- test/libsolidity/SolidityEndToEndTest.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8c0e29fd2..92c1a9a1f 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9100,10 +9100,13 @@ BOOST_AUTO_TEST_CASE(revert) { char const* sourceCode = R"( contract C { + uint public a = 42; function f() { + a = 1; revert(); } function g() { + a = 1; assembly { revert(0, 0) } @@ -9112,7 +9115,9 @@ BOOST_AUTO_TEST_CASE(revert) )"; compileAndRun(sourceCode, 0, "C"); BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(42))); BOOST_CHECK(callContractFunction("g()") == encodeArgs()); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(42))); } BOOST_AUTO_TEST_SUITE_END() From c8ec79548b8f8825735ee96f1768e7fc5313d19e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 22:53:32 +0000 Subject: [PATCH 260/414] Use the revert opcode in assert() --- libsolidity/codegen/ExpressionCompiler.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 316ae888d..2ed19a833 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -872,8 +872,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); - m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + // jump if condition was met + m_context << Instruction::ISZERO << Instruction::ISZERO; + auto success = m_context.appendConditionalJump(); + // condition was not met, abort + m_context << u256(0) << u256(0); + m_context << Instruction::REVERT; + // the success branch + m_context << success; break; } default: From a2bcb0008b2278ef469111081708a2274413c5ee Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 11 Feb 2017 18:03:47 +0000 Subject: [PATCH 261/414] Run every travis script in a subshell --- .travis.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index df8b4ebba..cfbadb85d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -168,18 +168,18 @@ cache: - $HOME/.local install: - - test $SOLC_INSTALL_DEPS_TRAVIS != On || ./scripts/install_deps.sh - - test "$TRAVIS_OS_NAME" != "linux" || ./scripts/install_cmake.sh + - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh) + - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh) - echo -n "$TRAVIS_COMMIT" > commit_hash.txt - - test $SOLC_DOCKER != On || docker build -t ethereum/solc:build -f ./scripts/Dockerfile . + - test $SOLC_DOCKER != On || (docker build -t ethereum/solc:build -f scripts/Dockerfile .) before_script: - - test $SOLC_EMSCRIPTEN != On || ./scripts/build_emscripten.sh - - test $SOLC_RELEASE != On || (./scripts/build.sh $SOLC_BUILD_TYPE - && ./scripts/release.sh $ZIP_SUFFIX - && ./scripts/create_source_tarball.sh ) + - test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh) + - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE + && scripts/release.sh $ZIP_SUFFIX + && scripts/create_source_tarball.sh) script: - - test $SOLC_DOCS != On || ./scripts/docs.sh - - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && ./scripts/tests.sh ) + - test $SOLC_DOCS != On || (scripts/docs.sh) + - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) deploy: # This is the deploy target for the Emscripten build. @@ -190,7 +190,7 @@ deploy: # scripts because TravisCI doesn't provide much in the way of conditional logic. - provider: script - script: test $SOLC_EMSCRIPTEN == On || scripts/release_emscripten.sh + script: test $SOLC_EMSCRIPTEN == On || (scripts/release_emscripten.sh) skip_cleanup: true on: branch: @@ -200,7 +200,7 @@ deploy: # as a nightly and appended the commit of the branch it was pushed in. If we are pushing to master it will # be tagged as "stable" and given the version tag as well. - provider: script - script: test $SOLC_DOCKER != On || ./scripts/docker_deploy.sh + script: test $SOLC_DOCKER != On || (scripts/docker_deploy.sh) skip_cleanup: true on: branch: @@ -223,4 +223,4 @@ deploy: on: all_branches: true tags: true - condition: $SOLC_RELEASE == On \ No newline at end of file + condition: $SOLC_RELEASE == On From 108b79d3bfba648e6e16d8d74c8073eb465f4d6f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 11 Feb 2017 18:07:41 +0000 Subject: [PATCH 262/414] Fix macOS builds --- .travis.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index cfbadb85d..0a0aa139a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ #------------------------------------------------------------------------------ language: cpp + branches: # We need to whitelist the branches which we want to have "push" automation, # this includes tags (which are treated as branches by travis). @@ -79,6 +80,7 @@ matrix: - SOLC_DOCS=On - SOLC_RELEASE=Off - SOLC_TESTS=Off + # Docker target, which generates a statically linked alpine image - os: linux dist: trusty @@ -126,7 +128,7 @@ matrix: # env: # # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?" # # https://github.com/ethereum/solidity/issues/894 -# - TRAVIS_TESTS=Off +# - SOLC_TESTS=Off # - ZIP_SUFFIX=osx-yosemite # OS X El Capitan (10.11) @@ -137,10 +139,10 @@ matrix: # env: # # The use of Debug config here ONLY for El Capitan is a workaround for "The Heisenbug" # # See https://github.com/ethereum/webthree-umbrella/issues/565 -# - TRAVIS_BUILD_TYPE=Debug +# - SOLC_BUILD_TYPE=Debug # # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?" # # https://github.com/ethereum/solidity/issues/894 -# - TRAVIS_TESTS=Off +# - SOLC_TESTS=Off # - ZIP_SUFFIX=osx-elcapitan # macOS Sierra (10.12) @@ -151,10 +153,10 @@ matrix: # env: # # Look like "The Heisenbug" is occurring here too, so we'll do the same workaround. # # See https://travis-ci.org/ethereum/solidity/jobs/150240930 -# - TRAVIS_BUILD_TYPE=Debug +# - SOLC_BUILD_TYPE=Debug # # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?" # # https://github.com/ethereum/solidity/issues/894 -# - TRAVIS_TESTS=Off +# - SOLC_TESTS=Off # - ZIP_SUFFIX=macos-sierra git: @@ -172,11 +174,13 @@ install: - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh) - echo -n "$TRAVIS_COMMIT" > commit_hash.txt - test $SOLC_DOCKER != On || (docker build -t ethereum/solc:build -f scripts/Dockerfile .) + before_script: - test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh) - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE && scripts/release.sh $ZIP_SUFFIX && scripts/create_source_tarball.sh) + script: - test $SOLC_DOCS != On || (scripts/docs.sh) - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) From 31baabaf61c3d9e306ae6f79ebc1eb89bcee6d18 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Sat, 11 Feb 2017 14:06:30 -0600 Subject: [PATCH 263/414] should fix all the emscripten errors and problems associated with it Signed-off-by: RJ Catalano --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a0aa139a..77fb06cd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -194,7 +194,7 @@ deploy: # scripts because TravisCI doesn't provide much in the way of conditional logic. - provider: script - script: test $SOLC_EMSCRIPTEN == On || (scripts/release_emscripten.sh) + script: test $SOLC_EMSCRIPTEN != On || (scripts/release_emscripten.sh) skip_cleanup: true on: branch: From 11c4a7b6426a0bd29b7b17ab21e7415b651a98ee Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 13 Feb 2017 11:22:07 +0100 Subject: [PATCH 264/414] Fail if docker deploy failed. --- scripts/docker_deploy.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh index c9682c166..fe157044e 100755 --- a/scripts/docker_deploy.sh +++ b/scripts/docker_deploy.sh @@ -1,5 +1,7 @@ #!/usr/bin/env sh +set -e + docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/../CMakeLists.txt) if [ "$TRAVIS_BRANCH" == "develop" ]; then From bc3e3fd7090def8f4da81c44b31a86c1fda19414 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 13 Feb 2017 12:28:39 +0100 Subject: [PATCH 265/414] Fix test expressions. --- scripts/docker_deploy.sh | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh index fe157044e..d2810a3e5 100755 --- a/scripts/docker_deploy.sh +++ b/scripts/docker_deploy.sh @@ -4,15 +4,20 @@ set -e docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/../CMakeLists.txt) -if [ "$TRAVIS_BRANCH" == "develop" ]; then - docker tag ethereum/solc:build ethereum/solc:nightly; - docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT" - docker push ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT"; - docker push ethereum/solc:nightly; -elif [ "$TRAVIS_BRANCH" == "release"]; then - docker tag ethereum/solc:build ethereum/solc:stable; - docker push ethereum/solc:stable; -elif [ "$TRAVIS_TAG" == v"$version"]; then - docker tag ethereum/solc:build ethereum/solc:"$version"; - docker push ethereum/solc:"$version"; +if [ "$TRAVIS_BRANCH" = "develop" ] +then + docker tag ethereum/solc:build ethereum/solc:nightly; + docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT" + docker push ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT"; + docker push ethereum/solc:nightly; +elif [ "$TRAVIS_BRANCH" = "release" ] +then + docker tag ethereum/solc:build ethereum/solc:stable; + docker push ethereum/solc:stable; +elif [ "$TRAVIS_TAG" = v"$version" ] +then + docker tag ethereum/solc:build ethereum/solc:"$version"; + docker push ethereum/solc:"$version"; +else + echo "Not publishing docker image from branch $TRAVIS_BRANCH or tag $TRAVIS_TAG" fi From 9ee0d6fb900c037b6143c3b48e688008b558ba12 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 13 Feb 2017 14:28:07 +0100 Subject: [PATCH 266/414] Try different nodejs version request formatting. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 77fb06cd7..26e6b214a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,7 +100,8 @@ matrix: dist: trusty sudo: required compiler: gcc - node_js: "6" + node_js: + - "6" services: - docker before_install: From 1d4ef87bb11e447334d2dea99b5a70c7201d6b97 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 13 Feb 2017 13:59:29 +0000 Subject: [PATCH 267/414] Use maxMiningTime in mining as opposed to poll counter --- test/RPCSession.cpp | 12 ++++++++---- test/RPCSession.h | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index c27e73d42..613d042af 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -248,22 +248,26 @@ void RPCSession::test_mineBlocks(int _number) // We auto-calibrate the time it takes to mine the transaction. // It would be better to go without polling, but that would probably need a change to the test client + unsigned startTime = boost::posix_time::microsec_clock::local_time(); unsigned sleepTime = m_sleepTime; - size_t polls = 0; - for (; polls < 14 && !mined; ++polls) + size_t tries = 0; + for (; !mined; ++tries) { std::this_thread::sleep_for(chrono::milliseconds(sleepTime)); + boost::posix_time::time_duration timeSpent = boost::posix_time::microsec_clock::local_time() - startTime; + if (timeSpent > m_maxMiningTime) + break; if (fromBigEndian(fromHex(rpcCall("eth_blockNumber").asString())) >= startBlock + _number) mined = true; else sleepTime *= 2; } - if (polls > 1) + if (tries > 1) { m_successfulMineRuns = 0; m_sleepTime += 2; } - else if (polls == 1) + else if (tries == 1) { m_successfulMineRuns++; if (m_successfulMineRuns > 5) diff --git a/test/RPCSession.h b/test/RPCSession.h index 105ba3788..a0b1e9ef4 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -126,6 +126,7 @@ private: IPCSocket m_ipcSocket; size_t m_rpcSequence = 1; + unsigned m_maxMiningTime = 15000; // 15 seconds unsigned m_sleepTime = 10; unsigned m_successfulMineRuns = 0; From e9dd9d2c7220594e2731b081990396429c5288d7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 13 Feb 2017 14:10:02 +0000 Subject: [PATCH 268/414] Simplify mining loop --- test/RPCSession.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 613d042af..bf639e195 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -243,22 +243,20 @@ void RPCSession::test_mineBlocks(int _number) u256 startBlock = fromBigEndian(fromHex(rpcCall("eth_blockNumber").asString())); BOOST_REQUIRE(rpcCall("test_mineBlocks", { to_string(_number) }, true) == true); - bool mined = false; - // We auto-calibrate the time it takes to mine the transaction. // It would be better to go without polling, but that would probably need a change to the test client unsigned startTime = boost::posix_time::microsec_clock::local_time(); unsigned sleepTime = m_sleepTime; size_t tries = 0; - for (; !mined; ++tries) + for (; ; ++tries) { std::this_thread::sleep_for(chrono::milliseconds(sleepTime)); boost::posix_time::time_duration timeSpent = boost::posix_time::microsec_clock::local_time() - startTime; if (timeSpent > m_maxMiningTime) - break; + BOOST_FAIL("Error in test_mineBlocks: block mining timeout!"); if (fromBigEndian(fromHex(rpcCall("eth_blockNumber").asString())) >= startBlock + _number) - mined = true; + break; else sleepTime *= 2; } @@ -277,9 +275,6 @@ void RPCSession::test_mineBlocks(int _number) m_sleepTime--; } } - - if (!mined) - BOOST_FAIL("Error in test_mineBlocks: block mining timeout!"); } void RPCSession::test_modifyTimestamp(size_t _timestamp) From 0fe788aad6d55c440e04f6f64f1927613bd6b16b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 13 Feb 2017 15:01:15 +0000 Subject: [PATCH 269/414] Use std::chrono and not boost::posix_Time --- test/RPCSession.cpp | 5 +++-- test/RPCSession.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index bf639e195..ff00d7833 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -246,13 +246,14 @@ void RPCSession::test_mineBlocks(int _number) // We auto-calibrate the time it takes to mine the transaction. // It would be better to go without polling, but that would probably need a change to the test client - unsigned startTime = boost::posix_time::microsec_clock::local_time(); + auto startTime = std::chrono::steady_clock::now(); unsigned sleepTime = m_sleepTime; size_t tries = 0; for (; ; ++tries) { std::this_thread::sleep_for(chrono::milliseconds(sleepTime)); - boost::posix_time::time_duration timeSpent = boost::posix_time::microsec_clock::local_time() - startTime; + auto endTime = std::chrono::steady_clock::now(); + unsigned timeSpent = std::chrono::duration_cast(endTime - startTime).count(); if (timeSpent > m_maxMiningTime) BOOST_FAIL("Error in test_mineBlocks: block mining timeout!"); if (fromBigEndian(fromHex(rpcCall("eth_blockNumber").asString())) >= startBlock + _number) diff --git a/test/RPCSession.h b/test/RPCSession.h index a0b1e9ef4..414db3232 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -127,7 +127,7 @@ private: IPCSocket m_ipcSocket; size_t m_rpcSequence = 1; unsigned m_maxMiningTime = 15000; // 15 seconds - unsigned m_sleepTime = 10; + unsigned m_sleepTime = 10; // 10 milliseconds unsigned m_successfulMineRuns = 0; std::vector m_accounts; From 2ba09206dec5956ef436455c391499dc1c22e841 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 13 Feb 2017 17:41:12 +0100 Subject: [PATCH 270/414] Install nvm --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 26e6b214a..b5329243a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,6 +105,8 @@ matrix: services: - docker before_install: + - nvm install + - nvm use - docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit env: - SOLC_EMSCRIPTEN=On From 99b00c75cc4f241aea6e4b6587b68baaba4ee4a5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 13 Feb 2017 18:02:10 +0100 Subject: [PATCH 271/414] Use version 6 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5329243a..b722dacd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,8 +105,8 @@ matrix: services: - docker before_install: - - nvm install - - nvm use + - nvm install 6 + - nvm use 6 - docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit env: - SOLC_EMSCRIPTEN=On From 75d59b1adbbf20bf89a06955157b9a89bb2c67ae Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Sat, 11 Feb 2017 18:21:57 -0500 Subject: [PATCH 272/414] Update the sphinx highlighting rules --- docs/utils/SolidityLexer.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py index a7da59aaf..f5220c8bf 100644 --- a/docs/utils/SolidityLexer.py +++ b/docs/utils/SolidityLexer.py @@ -54,11 +54,10 @@ class SolidityLexer(RegexLexer): r'(<<|>>>?|==?|!=?|[-<>+*%&\|\^/])=?', Operator, 'slashstartsregex'), (r'[{(\[;,]', Punctuation, 'slashstartsregex'), (r'[})\].]', Punctuation), - (r'(for|in|while|do|break|return|continue|switch|case|default|if|else|' - r'throw|try|catch|finally|new|delete|typeof|instanceof|void|' - r'this|import|mapping|returns|private|public|external|internal|' - r'constant|memory|storage|payable)\b', Keyword, 'slashstartsregex'), - (r'(var|let|with|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'), + (r'(anonymous|as|assembly|break|constant|continue|do|else|external|hex|if|' + r'indexed|internal|import|is|mapping|memory|new|payable|public|pragma|' + r'private|return|returns|storage|super|this|throw|using|while)\b', Keyword, 'slashstartsregex'), + (r'(var|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'), (r'(bytes|string|address|uint|int|bool|byte|' + '|'.join( ['uint%d' % (i + 8) for i in range(0, 256, 8)] + @@ -68,16 +67,11 @@ class SolidityLexer(RegexLexer): ['fixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)] ) + r')\b', Keyword.Type, 'slashstartsregex'), (r'(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b', Keyword.Type, 'slashstartsregex'), - (r'(abstract|boolean|byte|char|class|const|debugger|double|enum|export|' - r'extends|final|float|goto|implements|int|interface|long|native|' - r'package|private|protected|public|short|static|super|synchronized|throws|' - r'transient|volatile)\b', Keyword.Reserved), - (r'(true|false|null|NaN|Infinity|undefined)\b', Keyword.Constant), - (r'(Array|Boolean|Date|Error|Function|Math|netscape|' - r'Number|Object|Packages|RegExp|String|sun|decodeURI|' - r'decodeURIComponent|encodeURI|encodeURIComponent|' - r'Error|eval|isFinite|isNaN|parseFloat|parseInt|document|this|' - r'window)\b', Name.Builtin), + (r'(abstract|after|case|catch|default|final|in|inline|interface|let|match|' + r'null|of|pure|relocatable|static|switch|try|type|typeof|view)\b', Keyword.Reserved), + (r'(true|false)\b', Keyword.Constant), + (r'(block|msg|tx|now|suicide|selfdestruct|addmod|mulmod|sha3|keccak256|log[0-4]|' + r'sha256|ecrecover|ripemd160|assert|revert)', Name.Builtin), (r'[$a-zA-Z_][a-zA-Z0-9_]*', Name.Other), (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float), (r'0x[0-9a-fA-F]+', Number.Hex), From fc8e50f688bad9c13259fc926142b838d7aba5d3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 27 Jan 2017 18:27:59 +0100 Subject: [PATCH 273/414] Refactor NameAndTypeResolver and SyntaxChecker to allow other entry points. --- libsolidity/analysis/NameAndTypeResolver.cpp | 106 +++++++++--------- libsolidity/analysis/NameAndTypeResolver.h | 9 +- libsolidity/analysis/ReferencesResolver.cpp | 27 ++++- libsolidity/analysis/ReferencesResolver.h | 9 +- libsolidity/analysis/SyntaxChecker.cpp | 4 +- libsolidity/analysis/SyntaxChecker.h | 2 +- .../SolidityNameAndTypeResolution.cpp | 2 +- 7 files changed, 92 insertions(+), 67 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index b0a82715d..2791fed7e 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -132,68 +132,64 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map const& baseContract: _contract.baseContracts()) - if (!resolver.resolve(*baseContract)) - success = false; - - m_currentScope = m_scopes[&_contract].get(); - - if (success) + if (ContractDefinition* contract = dynamic_cast(&_node)) { - linearizeBaseContracts(_contract); - vector properBases( - ++_contract.annotation().linearizedBaseContracts.begin(), - _contract.annotation().linearizedBaseContracts.end() - ); + m_currentScope = m_scopes[contract->scope()].get(); + solAssert(!!m_currentScope, ""); - for (ContractDefinition const* base: properBases) - importInheritedScope(*base); + for (ASTPointer const& baseContract: contract->baseContracts()) + if (!resolveNamesAndTypes(*baseContract, false)) + success = false; + + m_currentScope = m_scopes[contract].get(); + + if (success) + { + linearizeBaseContracts(*contract); + vector properBases( + ++contract->annotation().linearizedBaseContracts.begin(), + contract->annotation().linearizedBaseContracts.end() + ); + + for (ContractDefinition const* base: properBases) + importInheritedScope(*base); + } + + // these can contain code, only resolve parameters for now + for (ASTPointer const& node: contract->subNodes()) + { + m_currentScope = m_scopes[contract].get(); + if (!resolveNamesAndTypes(*node, false)) + success = false; + } + + if (!success) + return false; + + if (!_resolveInsideCode) + return success; + + m_currentScope = m_scopes[contract].get(); + + // now resolve references inside the code + for (ASTPointer const& node: contract->subNodes()) + { + m_currentScope = m_scopes[contract].get(); + if (!resolveNamesAndTypes(*node, true)) + success = false; + } } - - // these can contain code, only resolve parameters for now - for (ASTPointer const& node: _contract.subNodes()) + else { - m_currentScope = m_scopes[m_scopes.count(node.get()) ? node.get() : &_contract].get(); - if (!resolver.resolve(*node)) - success = false; + if (m_scopes.count(&_node)) + m_currentScope = m_scopes[&_node].get(); + return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node); } - - if (!success) - return false; - - m_currentScope = m_scopes[&_contract].get(); - - // now resolve references inside the code - for (ModifierDefinition const* modifier: _contract.functionModifiers()) - { - m_currentScope = m_scopes[modifier].get(); - ReferencesResolver resolver(m_errors, *this, nullptr, true); - if (!resolver.resolve(*modifier)) - success = false; - } - - for (FunctionDefinition const* function: _contract.definedFunctions()) - { - m_currentScope = m_scopes[function].get(); - if (!ReferencesResolver( - m_errors, - *this, - function->returnParameterList().get(), - true - ).resolve(*function)) - success = false; - } - if (!success) - return false; } catch (FatalError const&) { @@ -201,7 +197,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) throw; // Something is weird here, rather throw again. return false; } - return true; + return success; } bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 68c3ffa1d..fb018d749 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -48,9 +48,12 @@ public: bool registerDeclarations(SourceUnit& _sourceUnit); /// Applies the effect of import directives. bool performImports(SourceUnit& _sourceUnit, std::map const& _sourceUnits); - /// Resolves all names and types referenced from the given contract. + /// Resolves all names and types referenced from the given AST Node. + /// This is usually only called at the contract level, but with a bit of care, it can also + /// be called at deeper levels. + /// @param _avoidCode if false, does not descend into nodes that contain code. /// @returns false in case of error. - bool resolveNamesAndTypes(ContractDefinition& _contract); + bool resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode = true); /// Updates the given global declaration (used for "this"). Not to be used with declarations /// that create their own scope. /// @returns false in case of error. @@ -77,8 +80,6 @@ public: ); private: - void reset(); - /// Imports all members declared directly in the given contract (i.e. does not import inherited members) /// into the current scope if they are not present already. void importInheritedScope(ContractDefinition const& _base); diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index d589f4a0a..c06181d89 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -65,6 +65,30 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName) return true; } +bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition) +{ + m_returnParameters.push_back(_functionDefinition.returnParameterList().get()); + return true; +} + +void ReferencesResolver::endVisit(FunctionDefinition const&) +{ + solAssert(!m_returnParameters.empty(), ""); + m_returnParameters.pop_back(); +} + +bool ReferencesResolver::visit(ModifierDefinition const&) +{ + m_returnParameters.push_back(nullptr); + return true; +} + +void ReferencesResolver::endVisit(ModifierDefinition const&) +{ + solAssert(!m_returnParameters.empty(), ""); + m_returnParameters.pop_back(); +} + void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) { Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath()); @@ -161,7 +185,8 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) bool ReferencesResolver::visit(Return const& _return) { - _return.annotation().functionReturnParameters = m_returnParameters; + solAssert(!m_returnParameters.empty(), ""); + _return.annotation().functionReturnParameters = m_returnParameters.back(); return true; } diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index caa3a78fc..23ac6b07c 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -45,12 +45,10 @@ public: ReferencesResolver( ErrorList& _errors, NameAndTypeResolver& _resolver, - ParameterList const* _returnParameters, bool _resolveInsideCode = false ): m_errors(_errors), m_resolver(_resolver), - m_returnParameters(_returnParameters), m_resolveInsideCode(_resolveInsideCode) {} @@ -61,6 +59,10 @@ private: virtual bool visit(Block const&) override { return m_resolveInsideCode; } virtual bool visit(Identifier const& _identifier) override; virtual bool visit(ElementaryTypeName const& _typeName) override; + virtual bool visit(FunctionDefinition const& _functionDefinition) override; + virtual void endVisit(FunctionDefinition const& _functionDefinition) override; + virtual bool visit(ModifierDefinition const& _modifierDefinition) override; + virtual void endVisit(ModifierDefinition const& _modifierDefinition) override; virtual void endVisit(UserDefinedTypeName const& _typeName) override; virtual void endVisit(FunctionTypeName const& _typeName) override; virtual void endVisit(Mapping const& _typeName) override; @@ -83,7 +85,8 @@ private: ErrorList& m_errors; NameAndTypeResolver& m_resolver; - ParameterList const* m_returnParameters; + /// Stack of return parameters. + std::vector m_returnParameters; bool const m_resolveInsideCode; bool m_errorOccurred = false; }; diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 0a4943fe4..890141335 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -26,9 +26,9 @@ using namespace dev; using namespace dev::solidity; -bool SyntaxChecker::checkSyntax(SourceUnit const& _sourceUnit) +bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot) { - _sourceUnit.accept(*this); + _astRoot.accept(*this); return Error::containsOnlyWarnings(m_errors); } diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index c24bae092..308e128b5 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -39,7 +39,7 @@ public: /// @param _errors the reference to the list of errors and warnings to add them found during type checking. SyntaxChecker(ErrorList& _errors): m_errors(_errors) {} - bool checkSyntax(SourceUnit const& _sourceUnit); + bool checkSyntax(ASTNode const& _astRoot); private: /// Adds a new error to the list of errors. diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 0151d244a..29856d317 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1080,7 +1080,7 @@ BOOST_AUTO_TEST_CASE(modifier_returns_value) modifier mod(uint a) { _; return 7; } } )"; - CHECK_ERROR(text, TypeError, ""); + CHECK_ERROR(text, TypeError, "Return arguments not allowed."); } BOOST_AUTO_TEST_CASE(state_variable_accessors) From c87bafd2ede044361ff7ca849a14298e97bd8318 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 27 Jan 2017 23:29:03 +0100 Subject: [PATCH 274/414] Refactor type system to allow multiple entry points. --- libsolidity/analysis/NameAndTypeResolver.cpp | 30 +++++++++++++------- libsolidity/analysis/NameAndTypeResolver.h | 6 ++-- libsolidity/analysis/TypeChecker.cpp | 4 +-- libsolidity/analysis/TypeChecker.h | 2 +- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 2791fed7e..5fea91d6e 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -44,18 +44,12 @@ NameAndTypeResolver::NameAndTypeResolver( m_scopes[nullptr]->registerDeclaration(*declaration); } -bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit) +bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit) { - if (!m_scopes[&_sourceUnit]) - // By importing, it is possible that the container already exists. - m_scopes[&_sourceUnit].reset(new DeclarationContainer(nullptr, m_scopes[nullptr].get())); - m_currentScope = m_scopes[&_sourceUnit].get(); - // The helper registers all declarations in m_scopes as a side-effect of its construction. try { DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors); - _sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations(); } catch (FatalError const&) { @@ -458,11 +452,26 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper( ErrorList& _errors ): m_scopes(_scopes), - m_currentScope(&_astRoot), + m_currentScope(nullptr), m_errors(_errors) { - solAssert(!!m_scopes.at(m_currentScope), ""); _astRoot.accept(*this); + solAssert(m_currentScope == nullptr, "Scopes not correctly closed."); +} + +bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit) +{ + if (!m_scopes[&_sourceUnit]) + // By importing, it is possible that the container already exists. + m_scopes[&_sourceUnit].reset(new DeclarationContainer(nullptr, m_scopes[nullptr].get())); + m_currentScope = &_sourceUnit; + return true; +} + +void DeclarationRegistrationHelper::endVisit(SourceUnit& _sourceUnit) +{ + _sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations(); + closeCurrentScope(); } bool DeclarationRegistrationHelper::visit(ImportDirective& _import) @@ -583,12 +592,13 @@ void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declara void DeclarationRegistrationHelper::closeCurrentScope() { - solAssert(m_currentScope, "Closed non-existing scope."); + solAssert(m_currentScope && m_scopes.count(m_currentScope), "Closed non-existing scope."); m_currentScope = m_scopes[m_currentScope]->enclosingNode(); } void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) { + solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope."); if (!m_scopes[m_currentScope]->registerDeclaration(_declaration, nullptr, !_declaration.isVisibleInContract())) { SourceLocation firstDeclarationLocation; diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index fb018d749..67a6c0e4b 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -43,9 +43,9 @@ class NameAndTypeResolver: private boost::noncopyable { public: NameAndTypeResolver(std::vector const& _globals, ErrorList& _errors); - /// Registers all declarations found in the source unit. + /// Registers all declarations found in the AST node, usually a source unit. /// @returns false in case of error. - bool registerDeclarations(SourceUnit& _sourceUnit); + bool registerDeclarations(ASTNode& _sourceUnit); /// Applies the effect of import directives. bool performImports(SourceUnit& _sourceUnit, std::map const& _sourceUnits); /// Resolves all names and types referenced from the given AST Node. @@ -133,6 +133,8 @@ public: ); private: + bool visit(SourceUnit& _sourceUnit) override; + void endVisit(SourceUnit& _sourceUnit) override; bool visit(ImportDirective& _declaration) override; bool visit(ContractDefinition& _contract) override; void endVisit(ContractDefinition& _contract) override; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 533c787b7..be59d3d26 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -32,11 +32,11 @@ using namespace dev; using namespace dev::solidity; -bool TypeChecker::checkTypeRequirements(ContractDefinition const& _contract) +bool TypeChecker::checkTypeRequirements(ASTNode const& _contract) { try { - visit(_contract); + _contract.accept(*this); } catch (FatalError const&) { diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 143b15b26..46d8230ab 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -47,7 +47,7 @@ public: /// Performs type checking on the given contract and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings - bool checkTypeRequirements(ContractDefinition const& _contract); + bool checkTypeRequirements(ASTNode const& _contract); /// @returns the type of an expression and asserts that it is present. TypePointer const& type(Expression const& _expression) const; From e67faa9839ebd0dadef2adf3ed1ef69fac6f65e1 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 31 Jan 2017 22:59:56 +0100 Subject: [PATCH 275/414] Extract scopes into compiler stack. --- libsolidity/analysis/NameAndTypeResolver.cpp | 2 ++ libsolidity/analysis/NameAndTypeResolver.h | 8 ++++++-- libsolidity/interface/CompilerStack.cpp | 3 ++- libsolidity/interface/CompilerStack.h | 3 +++ test/libsolidity/Assembly.cpp | 3 ++- test/libsolidity/SolidityExpressionCompiler.cpp | 3 ++- test/libsolidity/SolidityNameAndTypeResolution.cpp | 3 ++- 7 files changed, 19 insertions(+), 6 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 5fea91d6e..ed1bd1d30 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -34,8 +34,10 @@ namespace solidity NameAndTypeResolver::NameAndTypeResolver( vector const& _globals, + map>& _scopes, ErrorList& _errors ) : + m_scopes(_scopes), m_errors(_errors) { if (!m_scopes[nullptr]) diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 67a6c0e4b..1c7af0c92 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -42,7 +42,11 @@ namespace solidity class NameAndTypeResolver: private boost::noncopyable { public: - NameAndTypeResolver(std::vector const& _globals, ErrorList& _errors); + NameAndTypeResolver( + std::vector const& _globals, + std::map>& _scopes, + ErrorList& _errors + ); /// Registers all declarations found in the AST node, usually a source unit. /// @returns false in case of error. bool registerDeclarations(ASTNode& _sourceUnit); @@ -113,7 +117,7 @@ private: /// where nullptr denotes the global scope. Note that structs are not scope since they do /// not contain code. /// Aliases (for example `import "x" as y;`) create multiple pointers to the same scope. - std::map> m_scopes; + std::map>& m_scopes; DeclarationContainer* m_currentScope = nullptr; ErrorList& m_errors; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 3335c40e4..9d8d872f4 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -88,6 +88,7 @@ void CompilerStack::reset(bool _keepSources) m_optimize = false; m_optimizeRuns = 200; m_globalContext.reset(); + m_scopes.clear(); m_sourceOrder.clear(); m_contracts.clear(); m_errors.clear(); @@ -165,7 +166,7 @@ bool CompilerStack::parse() noErrors = false; m_globalContext = make_shared(); - NameAndTypeResolver resolver(m_globalContext->declarations(), m_errors); + NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errors); for (Source const* source: m_sourceOrder) if (!resolver.registerDeclarations(*source->ast)) return false; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 9ee70215e..eddfea685 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -52,6 +52,7 @@ namespace solidity // forward declarations class Scanner; +class ASTNode; class ContractDefinition; class FunctionDefinition; class SourceUnit; @@ -59,6 +60,7 @@ class Compiler; class GlobalContext; class InterfaceHandler; class Error; +class DeclarationContainer; enum class DocumentationType: uint8_t { @@ -271,6 +273,7 @@ private: bool m_parseSuccessful; std::map m_sources; std::shared_ptr m_globalContext; + std::map> m_scopes; std::vector m_sourceOrder; std::map m_contracts; std::string m_formalTranslation; diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 497bfc777..c4ec0d202 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -53,7 +53,8 @@ eth::AssemblyItems compileContract(const string& _sourceCode) BOOST_REQUIRE_NO_THROW(sourceUnit = parser.parse(make_shared(CharStream(_sourceCode)))); BOOST_CHECK(!!sourceUnit); - NameAndTypeResolver resolver({}, errors); + map> scopes; + NameAndTypeResolver resolver({}, scopes, errors); solAssert(Error::containsOnlyWarnings(errors), ""); resolver.registerDeclarations(*sourceUnit); for (ASTPointer const& node: sourceUnit->nodes()) diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index a769776e9..3116aea8c 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -114,7 +114,8 @@ bytes compileFirstExpression( declarations.push_back(variable.get()); ErrorList errors; - NameAndTypeResolver resolver(declarations, errors); + map> scopes; + NameAndTypeResolver resolver(declarations, scopes, errors); resolver.registerDeclarations(*sourceUnit); vector inheritanceHierarchy; diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 29856d317..1a4f3cdc9 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -66,7 +66,8 @@ parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false, return make_pair(sourceUnit, errors.at(0)); std::shared_ptr globalContext = make_shared(); - NameAndTypeResolver resolver(globalContext->declarations(), errors); + map> scopes; + NameAndTypeResolver resolver(globalContext->declarations(), scopes, errors); solAssert(Error::containsOnlyWarnings(errors), ""); resolver.registerDeclarations(*sourceUnit); From b1bb228ab3a8ca0548ed5f08fdc5fda1fcb71b1a Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 31 Jan 2017 23:12:40 +0100 Subject: [PATCH 276/414] Allow different entry scope for registerDeclarations. --- libsolidity/analysis/NameAndTypeResolver.cpp | 13 +++++++------ libsolidity/analysis/NameAndTypeResolver.h | 11 +++++++++-- libsolidity/analysis/TypeChecker.cpp | 7 ++++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index ed1bd1d30..fd6fc0587 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -46,12 +46,12 @@ NameAndTypeResolver::NameAndTypeResolver( m_scopes[nullptr]->registerDeclaration(*declaration); } -bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit) +bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope) { // The helper registers all declarations in m_scopes as a side-effect of its construction. try { - DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors); + DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors, _currentScope); } catch (FatalError const&) { @@ -451,21 +451,22 @@ void NameAndTypeResolver::reportFatalTypeError(Error const& _e) DeclarationRegistrationHelper::DeclarationRegistrationHelper( map>& _scopes, ASTNode& _astRoot, - ErrorList& _errors + ErrorList& _errors, + ASTNode const* _currentScope ): m_scopes(_scopes), - m_currentScope(nullptr), + m_currentScope(_currentScope), m_errors(_errors) { _astRoot.accept(*this); - solAssert(m_currentScope == nullptr, "Scopes not correctly closed."); + solAssert(m_currentScope == _currentScope, "Scopes not correctly closed."); } bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit) { if (!m_scopes[&_sourceUnit]) // By importing, it is possible that the container already exists. - m_scopes[&_sourceUnit].reset(new DeclarationContainer(nullptr, m_scopes[nullptr].get())); + m_scopes[&_sourceUnit].reset(new DeclarationContainer(m_currentScope, m_scopes[m_currentScope].get())); m_currentScope = &_sourceUnit; return true; } diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 1c7af0c92..4de40e87a 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -49,7 +49,9 @@ public: ); /// Registers all declarations found in the AST node, usually a source unit. /// @returns false in case of error. - bool registerDeclarations(ASTNode& _sourceUnit); + /// @param _currentScope should be nullptr but can be used to inject new declarations into + /// existing scopes, used by the snippets feature. + bool registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope = nullptr); /// Applies the effect of import directives. bool performImports(SourceUnit& _sourceUnit, std::map const& _sourceUnits); /// Resolves all names and types referenced from the given AST Node. @@ -130,10 +132,15 @@ private: class DeclarationRegistrationHelper: private ASTVisitor { public: + /// Registers declarations in their scopes and creates new scopes as a side-effect + /// of construction. + /// @param _currentScope should be nullptr if we start at SourceUnit, but can be different + /// to inject new declarations into an existing scope, used by snippets. DeclarationRegistrationHelper( std::map>& _scopes, ASTNode& _astRoot, - ErrorList& _errors + ErrorList& _errors, + ASTNode const* _currentScope = nullptr ); private: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index be59d3d26..06a9e1cea 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -427,7 +427,12 @@ bool TypeChecker::visit(StructDefinition const& _struct) bool TypeChecker::visit(FunctionDefinition const& _function) { - bool isLibraryFunction = dynamic_cast(*_function.scope()).isLibrary(); + bool isLibraryFunction = false; + if ( + dynamic_cast(_function.scope()) && + dynamic_cast(_function.scope())->isLibrary() + ) + isLibraryFunction = true; if (_function.isPayable()) { if (isLibraryFunction) From a791ec75e2e73130afee391958651453acc8d781 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Feb 2017 13:32:48 +0100 Subject: [PATCH 277/414] Review comments. --- libsolidity/analysis/NameAndTypeResolver.cpp | 2 +- libsolidity/analysis/NameAndTypeResolver.h | 5 ++++- libsolidity/analysis/TypeChecker.cpp | 7 ++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index fd6fc0587..013842602 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -139,7 +139,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi solAssert(!!m_currentScope, ""); for (ASTPointer const& baseContract: contract->baseContracts()) - if (!resolveNamesAndTypes(*baseContract, false)) + if (!resolveNamesAndTypes(*baseContract, true)) success = false; m_currentScope = m_scopes[contract].get(); diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 4de40e87a..828b566fc 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -42,6 +42,9 @@ namespace solidity class NameAndTypeResolver: private boost::noncopyable { public: + /// Creates the resolver with the given declarations added to the global scope. + /// @param _scopes mapping of scopes to be used (usually default constructed), these + /// are filled during the lifetime of this object. NameAndTypeResolver( std::vector const& _globals, std::map>& _scopes, @@ -57,7 +60,7 @@ public: /// Resolves all names and types referenced from the given AST Node. /// This is usually only called at the contract level, but with a bit of care, it can also /// be called at deeper levels. - /// @param _avoidCode if false, does not descend into nodes that contain code. + /// @param _resolveInsideCode if false, does not descend into nodes that contain code. /// @returns false in case of error. bool resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode = true); /// Updates the given global declaration (used for "this"). Not to be used with declarations diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 06a9e1cea..28cb9acc3 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -427,12 +427,9 @@ bool TypeChecker::visit(StructDefinition const& _struct) bool TypeChecker::visit(FunctionDefinition const& _function) { - bool isLibraryFunction = false; - if ( + bool isLibraryFunction = dynamic_cast(_function.scope()) && - dynamic_cast(_function.scope())->isLibrary() - ) - isLibraryFunction = true; + dynamic_cast(_function.scope())->isLibrary(); if (_function.isPayable()) { if (isLibraryFunction) From eeaa2bad955a86436a8bfcf8cf33ff9dc5495ca6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 8 Feb 2017 13:56:23 +0100 Subject: [PATCH 278/414] Kill the right eth process in tests --- scripts/tests.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/tests.sh b/scripts/tests.sh index 88815f5f6..d47edd28d 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -52,6 +52,7 @@ fi # true and continue as normal, either processing further commands in a script # or returning the cursor focus back to the user in a Linux terminal. $ETH_PATH --test -d /tmp/test & +ETH_PID=$! # Wait until the IPC endpoint is available. That won't be available instantly. # The node needs to get a little way into its startup sequence before the IPC @@ -66,7 +67,7 @@ echo "--> Running tests without optimizer..." echo "--> Running tests WITH optimizer..." && \ "$REPO_ROOT"/build/test/soltest --show-progress -- --optimize --ipcpath /tmp/test/geth.ipc ERROR_CODE=$? -pkill eth || true +pkill "$ETH_PID" || true sleep 4 -pgrep eth && pkill -9 eth || true -exit $ERROR_CODE \ No newline at end of file +pgrep "$ETH_PID" && pkill -9 "$ETH_PID" || true +exit $ERROR_CODE From 80f72437864301562b485cb380eddcea5e6e575f Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 9 Feb 2017 14:04:23 +0100 Subject: [PATCH 279/414] Assembly printer. --- libsolidity/inlineasm/AsmPrinter.cpp | 125 +++++++++++++++++++++++++++ libsolidity/inlineasm/AsmPrinter.h | 61 +++++++++++++ libsolidity/inlineasm/AsmStack.cpp | 18 +++- libsolidity/inlineasm/AsmStack.h | 4 + 4 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 libsolidity/inlineasm/AsmPrinter.cpp create mode 100644 libsolidity/inlineasm/AsmPrinter.h diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp new file mode 100644 index 000000000..d829a4162 --- /dev/null +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -0,0 +1,125 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Christian + * @date 2017 + * Converts a parsed assembly into its textual form. + */ + +#include + +#include + +#include +#include +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::assembly; + +//@TODO source locations + +string AsmPrinter::operator()(assembly::Instruction const& _instruction) +{ + return boost::to_lower_copy(instructionInfo(_instruction.instruction).name); +} + +string AsmPrinter::operator()(assembly::Literal const& _literal) +{ + if (_literal.isNumber) + return _literal.value; + string out; + for (char c: _literal.value) + if (c == '\\') + out += "\\\\"; + else if (c == '"') + out += "\\"; + else if (c == '\'') + out += "\\'"; + else if (c == '\b') + out += "\\b"; + else if (c == '\f') + out += "\\f"; + else if (c == '\n') + out += "\\n"; + else if (c == '\r') + out += "\\r"; + else if (c == '\t') + out += "\\t"; + else if (c == '\v') + out += "\\v"; + else if (!isprint(c, locale::classic())) + { + ostringstream o; + o << std::hex << setfill('0') << setw(2) << unsigned(c); + out += "0x" + o.str(); + } + else + out += c; + return out; +} + +string AsmPrinter::operator()(assembly::Identifier const& _identifier) +{ + return _identifier.name; +} + +string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functionalInstruction) +{ + return + (*this)(_functionalInstruction.instruction); + + "(" + + boost::algorithm::join( + _functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), + ", " ) + + ")"; +} + +string AsmPrinter::operator()(assembly::Label const& _label) +{ + return _label.name + ":"; +} + +string AsmPrinter::operator()(assembly::Assignment const& _assignment) +{ + return "=: " + (*this)(_assignment.variableName); +} + +string AsmPrinter::operator()(assembly::FunctionalAssignment const& _functionalAssignment) +{ + return (*this)(_functionalAssignment.variableName) + " := " + boost::apply_visitor(*this, *_functionalAssignment.value); +} + +string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration) +{ + return "let " + _variableDeclaration.name + " := " + boost::apply_visitor(*this, *_variableDeclaration.value); +} + +string AsmPrinter::operator()(Block const& _block) +{ + string body = boost::algorithm::join( + _block.statements | boost::adaptors::transformed(boost::apply_visitor(*this)), + "\n" + ); + boost::replace_all(body, "\n", "\n "); + return "{\n " + body + "\n}"; +} diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h new file mode 100644 index 000000000..39069d021 --- /dev/null +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -0,0 +1,61 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Christian + * @date 2017 + * Converts a parsed assembly into its textual form. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ +namespace assembly +{ +struct Instruction; +struct Literal; +struct Identifier; +struct FunctionalInstruction; +struct Label; +struct Assignment; +struct FunctionalAssignment; +struct VariableDeclaration; +struct FunctionDefinition; +struct FunctionCall; +struct Block; + +class AsmPrinter: public boost::static_visitor +{ +public: + std::string operator()(assembly::Instruction const& _instruction); + 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::Label const& _label); + std::string operator()(assembly::Assignment const& _assignment); + std::string operator()(assembly::FunctionalAssignment const& _functionalAssignment); + std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); + std::string operator()(assembly::Block const& _block); +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp index b8e0e8572..6539e9bce 100644 --- a/libsolidity/inlineasm/AsmStack.cpp +++ b/libsolidity/inlineasm/AsmStack.cpp @@ -21,12 +21,17 @@ */ #include -#include -#include -#include -#include + #include #include +#include + +#include + +#include +#include + +#include using namespace std; using namespace dev; @@ -44,6 +49,11 @@ bool InlineAssemblyStack::parse(shared_ptr const& _scanner) return true; } +string InlineAssemblyStack::print() +{ + return AsmPrinter()(*m_parserResult); +} + eth::Assembly InlineAssemblyStack::assemble() { CodeGenerator codeGen(*m_parserResult, m_errors); diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h index 1543cb2a5..71d6c7717 100644 --- a/libsolidity/inlineasm/AsmStack.h +++ b/libsolidity/inlineasm/AsmStack.h @@ -46,6 +46,10 @@ public: /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`. /// @return false or error. bool parse(std::shared_ptr const& _scanner); + /// Converts the parser result back into a string form (not necessarily the same form + /// as the source form, but it should parse into the same parsed form again). + std::string print(); + eth::Assembly assemble(); /// Parse and assemble a string in one run - for use in Solidity code generation itself. From ca71b7624db6c731670ddf69873dd9f0c8c6ccd2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Feb 2017 13:59:15 +0100 Subject: [PATCH 280/414] Review changes. --- libsolidity/inlineasm/AsmPrinter.cpp | 4 ++-- libsolidity/inlineasm/AsmStack.cpp | 2 +- libsolidity/inlineasm/AsmStack.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index d829a4162..66cf39c01 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -71,11 +71,11 @@ string AsmPrinter::operator()(assembly::Literal const& _literal) { ostringstream o; o << std::hex << setfill('0') << setw(2) << unsigned(c); - out += "0x" + o.str(); + out += "\\x" + o.str(); } else out += c; - return out; + return "\"" + out + "\""; } string AsmPrinter::operator()(assembly::Identifier const& _identifier) diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp index 6539e9bce..38d688c13 100644 --- a/libsolidity/inlineasm/AsmStack.cpp +++ b/libsolidity/inlineasm/AsmStack.cpp @@ -49,7 +49,7 @@ bool InlineAssemblyStack::parse(shared_ptr const& _scanner) return true; } -string InlineAssemblyStack::print() +string InlineAssemblyStack::toString() { return AsmPrinter()(*m_parserResult); } diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h index 71d6c7717..4d5a99a4b 100644 --- a/libsolidity/inlineasm/AsmStack.h +++ b/libsolidity/inlineasm/AsmStack.h @@ -48,7 +48,7 @@ public: bool parse(std::shared_ptr const& _scanner); /// Converts the parser result back into a string form (not necessarily the same form /// as the source form, but it should parse into the same parsed form again). - std::string print(); + std::string toString(); eth::Assembly assemble(); From 58849cb1d52c88daeb15ec34d19ef0dc143c0a33 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Feb 2017 15:40:58 +0100 Subject: [PATCH 281/414] Tests for printing assembly. --- test/libsolidity/InlineAssembly.cpp | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 37d17495a..437c5866b 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -73,11 +73,22 @@ bool successAssemble(string const& _source, bool _allowWarnings = true) return successParse(_source, true, _allowWarnings); } +void parsePrintCompare(string const& _source) +{ + assembly::InlineAssemblyStack stack; + BOOST_REQUIRE(stack.parse(std::make_shared(CharStream(_source)))); + BOOST_REQUIRE(stack.errors().empty()); + BOOST_CHECK_EQUAL(stack.toString(), _source); +} + } BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly) + +BOOST_AUTO_TEST_SUITE(Parsing) + BOOST_AUTO_TEST_CASE(smoke_test) { BOOST_CHECK(successParse("{ }")); @@ -148,6 +159,49 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); } +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(Printing) + +BOOST_AUTO_TEST_CASE(print_smoke) +{ + parsePrintCompare("{\n}"); +} + +BOOST_AUTO_TEST_CASE(print_instructions) +{ + parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n}"); +} + +BOOST_AUTO_TEST_CASE(print_subblock) +{ + parsePrintCompare("{\n {\n dup4\n add\n }\n}"); +} + +BOOST_AUTO_TEST_CASE(print_functional) +{ + parsePrintCompare("{\n mul(sload(0x12), 7)\n}"); +} + +BOOST_AUTO_TEST_CASE(print_label) +{ + parsePrintCompare("{\n loop:\n jump(loop)\n}"); +} + +BOOST_AUTO_TEST_CASE(print_assignments) +{ + parsePrintCompare("{\n let x := mul(2, 3)\n 7\n =: x\n x := add(1, 2)\n}"); +} + +BOOST_AUTO_TEST_CASE(print_string_literals) +{ + parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n}"); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(Analysis) + BOOST_AUTO_TEST_CASE(string_literals) { BOOST_CHECK(successAssemble("{ let x := \"12345678901234567890123456789012\" }")); @@ -212,6 +266,8 @@ BOOST_AUTO_TEST_CASE(revert) BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() + } } } // end namespaces From 24197a2b3f7f94dac03b6ac30802069011cf036c Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Feb 2017 15:41:07 +0100 Subject: [PATCH 282/414] Assembly printing fixes. --- libsolidity/inlineasm/AsmPrinter.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index 66cf39c01..ab2a03ff0 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -52,9 +52,7 @@ string AsmPrinter::operator()(assembly::Literal const& _literal) if (c == '\\') out += "\\\\"; else if (c == '"') - out += "\\"; - else if (c == '\'') - out += "\\'"; + out += "\\\""; else if (c == '\b') out += "\\b"; else if (c == '\f') @@ -70,7 +68,7 @@ string AsmPrinter::operator()(assembly::Literal const& _literal) else if (!isprint(c, locale::classic())) { ostringstream o; - o << std::hex << setfill('0') << setw(2) << unsigned(c); + o << std::hex << setfill('0') << setw(2) << (unsigned)(unsigned char)(c); out += "\\x" + o.str(); } else @@ -86,7 +84,7 @@ string AsmPrinter::operator()(assembly::Identifier const& _identifier) string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functionalInstruction) { return - (*this)(_functionalInstruction.instruction); + + (*this)(_functionalInstruction.instruction) + "(" + boost::algorithm::join( _functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), @@ -116,6 +114,8 @@ string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDecl string AsmPrinter::operator()(Block const& _block) { + if (_block.statements.empty()) + return "{\n}"; string body = boost::algorithm::join( _block.statements | boost::adaptors::transformed(boost::apply_visitor(*this)), "\n" From 5e8a1e0ae6dcd269258fe4239060bcc890abacb5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 15 Feb 2017 15:21:11 +0100 Subject: [PATCH 283/414] Test for unicode string literals. --- test/libsolidity/InlineAssembly.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 437c5866b..8744d96f8 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -198,6 +198,17 @@ BOOST_AUTO_TEST_CASE(print_string_literals) parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n}"); } +BOOST_AUTO_TEST_CASE(print_string_literal_unicode) +{ + string source = "{ \"\\u1bac\" }"; + string parsed = "{\n \"\\xe1\\xae\\xac\"\n}"; + assembly::InlineAssemblyStack stack; + BOOST_REQUIRE(stack.parse(std::make_shared(CharStream(source)))); + BOOST_REQUIRE(stack.errors().empty()); + BOOST_CHECK_EQUAL(stack.toString(), parsed); + parsePrintCompare(parsed); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(Analysis) From 5d584aded8bcabaafddf2f61692f8663913be603 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 31 Jan 2017 23:59:41 +0100 Subject: [PATCH 284/414] Parsing function definitions. --- libsolidity/inlineasm/AsmCodeGen.cpp | 5 ++- libsolidity/inlineasm/AsmData.h | 5 ++- libsolidity/inlineasm/AsmParser.cpp | 49 +++++++++++++++++++++++++--- libsolidity/inlineasm/AsmParser.h | 2 ++ test/libsolidity/InlineAssembly.cpp | 10 ++++++ 5 files changed, 65 insertions(+), 6 deletions(-) diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 43c3b27a9..1eab5df0f 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -249,7 +249,10 @@ public: _block.location ); } - + } + void operator()(assembly::FunctionDefinition const&) + { + solAssert(false, "Function definition not removed during desugaring phase."); } private: diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index d622ff54c..64dd7b8ec 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -48,8 +48,9 @@ struct Label { SourceLocation location; std::string name; }; struct Assignment { SourceLocation location; Identifier variableName; }; struct FunctionalAssignment; struct VariableDeclaration; +struct FunctionDefinition; struct Block; -using Statement = boost::variant; +using Statement = boost::variant; /// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand /// side and requires x to occupy exactly one stack slot. struct FunctionalAssignment { SourceLocation location; Identifier variableName; std::shared_ptr value; }; @@ -59,6 +60,8 @@ struct FunctionalInstruction { SourceLocation location; Instruction instruction; struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr value; }; /// Block that creates a scope (frees declared stack variables) struct Block { SourceLocation location; std::vector statements; }; +/// Function definition ("function f(a, b) -> (d, e) { ... }") +struct FunctionDefinition { SourceLocation location; std::string name; std::vector arguments; std::vector returns; Block body; }; struct LocationExtractor: boost::static_visitor { diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 46a2730d9..019ec1a38 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -62,6 +62,8 @@ assembly::Statement Parser::parseStatement() { case Token::Let: return parseVariableDeclaration(); + case Token::Function: + return parseFunctionDefinition(); case Token::LBrace: return parseBlock(); case Token::Assign: @@ -214,10 +216,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() { VariableDeclaration varDecl = createWithLocation(); expectToken(Token::Let); - varDecl.name = m_scanner->currentLiteral(); - if (instructions().count(varDecl.name)) - fatalParserError("Cannot use instruction names for identifier names."); - expectToken(Token::Identifier); + varDecl.name = expectAsmIdentifier(); expectToken(Token::Colon); expectToken(Token::Assign); varDecl.value.reset(new Statement(parseExpression())); @@ -225,6 +224,39 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() return varDecl; } +assembly::FunctionDefinition Parser::parseFunctionDefinition() +{ + FunctionDefinition funDef = createWithLocation(); + expectToken(Token::Function); + funDef.name = expectAsmIdentifier(); + expectToken(Token::LParen); + while (m_scanner->currentToken() != Token::RParen) + { + funDef.arguments.push_back(expectAsmIdentifier()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); + } + expectToken(Token::RParen); + if (m_scanner->currentToken() == Token::Sub) + { + expectToken(Token::Sub); + expectToken(Token::GreaterThan); + expectToken(Token::LParen); + while (true) + { + funDef.returns.push_back(expectAsmIdentifier()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); + } + expectToken(Token::RParen); + } + funDef.body = parseBlock(); + funDef.location.end = funDef.body.location.end; + return funDef; +} + FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction) { if (_instruction.type() != typeid(Instruction)) @@ -266,3 +298,12 @@ FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _ expectToken(Token::RParen); return ret; } + +string Parser::expectAsmIdentifier() +{ + string name = m_scanner->currentLiteral(); + if (instructions().count(name)) + fatalParserError("Cannot use instruction names for identifier names."); + expectToken(Token::Identifier); + return name; +} diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 643548dd0..1da049f6d 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -67,7 +67,9 @@ protected: std::map const& instructions(); Statement parseElementaryOperation(bool _onlySinglePusher = false); VariableDeclaration parseVariableDeclaration(); + FunctionDefinition parseFunctionDefinition(); FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction); + std::string expectAsmIdentifier(); }; } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 8744d96f8..5216ac060 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -159,6 +159,16 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); } +BOOST_AUTO_TEST_CASE(function_definitions) +{ + BOOST_CHECK(successParse("{ function f() { } function g(a) -> (x) { } }")); +} + +BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) +{ + BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> (x, y) { } }")); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(Printing) From 49a919b3e012cfa0f7e0c93c29b93289740e9e44 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Feb 2017 21:20:21 +0100 Subject: [PATCH 285/414] Parsing function calls. --- libsolidity/inlineasm/AsmCodeGen.cpp | 4 ++ libsolidity/inlineasm/AsmData.h | 4 +- libsolidity/inlineasm/AsmParser.cpp | 91 +++++++++++++++++----------- libsolidity/inlineasm/AsmParser.h | 2 +- test/libsolidity/InlineAssembly.cpp | 5 ++ 5 files changed, 69 insertions(+), 37 deletions(-) diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 1eab5df0f..faa7dabdf 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -190,6 +190,10 @@ public: } (*this)(_instr.instruction); } + void operator()(assembly::FunctionCall const&) + { + solAssert(false, "Function call not removed during desugaring phase."); + } void operator()(Label const& _label) { m_state.assembly.setSourceLocation(_label.location); diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index 64dd7b8ec..d61b5803e 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -49,13 +49,15 @@ struct Assignment { SourceLocation location; Identifier variableName; }; struct FunctionalAssignment; struct VariableDeclaration; struct FunctionDefinition; +struct FunctionCall; struct Block; -using Statement = boost::variant; +using Statement = boost::variant; /// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand /// side and requires x to occupy exactly one stack slot. struct FunctionalAssignment { SourceLocation location; Identifier variableName; std::shared_ptr value; }; /// Functional instruction, e.g. "mul(mload(20), add(2, x))" struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector arguments; }; +struct FunctionCall { SourceLocation location; Identifier functionName; std::vector arguments; }; /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr value; }; /// Block that creates a scope (frees declared stack variables) diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 019ec1a38..0fc0a34f8 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -257,46 +257,67 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() return funDef; } -FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction) +assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _instruction) { - if (_instruction.type() != typeid(Instruction)) - fatalParserError("Assembly instruction required in front of \"(\")"); - FunctionalInstruction ret; - ret.instruction = std::move(boost::get(_instruction)); - ret.location = ret.instruction.location; - solidity::Instruction instr = ret.instruction.instruction; - InstructionInfo instrInfo = instructionInfo(instr); - if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16) - fatalParserError("DUPi instructions not allowed for functional notation"); - if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16) - fatalParserError("SWAPi instructions not allowed for functional notation"); - - expectToken(Token::LParen); - unsigned args = unsigned(instrInfo.args); - for (unsigned i = 0; i < args; ++i) + if (_instruction.type() == typeid(Instruction)) { - ret.arguments.emplace_back(parseExpression()); - if (i != args - 1) + FunctionalInstruction ret; + ret.instruction = std::move(boost::get(_instruction)); + ret.location = ret.instruction.location; + solidity::Instruction instr = ret.instruction.instruction; + InstructionInfo instrInfo = instructionInfo(instr); + if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16) + fatalParserError("DUPi instructions not allowed for functional notation"); + if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16) + fatalParserError("SWAPi instructions not allowed for functional notation"); + expectToken(Token::LParen); + unsigned args = unsigned(instrInfo.args); + for (unsigned i = 0; i < args; ++i) { - if (m_scanner->currentToken() != Token::Comma) - fatalParserError(string( - "Expected comma (" + - instrInfo.name + - " expects " + - boost::lexical_cast(args) + - " arguments)" - )); - else - m_scanner->next(); + ret.arguments.emplace_back(parseExpression()); + if (i != args - 1) + { + if (m_scanner->currentToken() != Token::Comma) + fatalParserError(string( + "Expected comma (" + + instrInfo.name + + " expects " + + boost::lexical_cast(args) + + " arguments)" + )); + else + m_scanner->next(); + } } + ret.location.end = endPosition(); + if (m_scanner->currentToken() == Token::Comma) + fatalParserError( + string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast(args) + " arguments)") + ); + expectToken(Token::RParen); + return ret; } - ret.location.end = endPosition(); - if (m_scanner->currentToken() == Token::Comma) - fatalParserError( - string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast(args) + " arguments)") - ); - expectToken(Token::RParen); - return ret; + else if (_instruction.type() == typeid(Identifier)) + { + FunctionCall ret; + ret.functionName = std::move(boost::get(_instruction)); + ret.location = ret.functionName.location; + expectToken(Token::LParen); + while (m_scanner->currentToken() != Token::RParen) + { + ret.arguments.emplace_back(parseExpression()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); + } + ret.location.end = endPosition(); + expectToken(Token::RParen); + return ret; + } + else + fatalParserError("Assembly instruction or function name required in front of \"(\")"); + + return {}; } string Parser::expectAsmIdentifier() diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 1da049f6d..4b4a24ae5 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -68,7 +68,7 @@ protected: Statement parseElementaryOperation(bool _onlySinglePusher = false); VariableDeclaration parseVariableDeclaration(); FunctionDefinition parseFunctionDefinition(); - FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction); + Statement parseFunctionalInstruction(Statement&& _instruction); std::string expectAsmIdentifier(); }; diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 5216ac060..40135a06f 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -169,6 +169,11 @@ BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> (x, y) { } }")); } +BOOST_AUTO_TEST_CASE(function_calls) +{ + BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }")); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(Printing) From a5ce81100ef32654e351329fff5cd0e19622798c Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 9 Feb 2017 14:04:23 +0100 Subject: [PATCH 286/414] Assembly printer for functions. --- libsolidity/inlineasm/AsmPrinter.cpp | 18 ++++++++++++++++++ libsolidity/inlineasm/AsmPrinter.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index ab2a03ff0..a70b0b786 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -112,6 +112,24 @@ string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDecl return "let " + _variableDeclaration.name + " := " + boost::apply_visitor(*this, *_variableDeclaration.value); } +string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefinition) +{ + string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")"; + if (!_functionDefinition.returns.empty()) + out += " -> (" + boost::algorithm::join(_functionDefinition.returns, ", ") + ")"; + return out + "\n" + (*this)(_functionDefinition.body); +} + +string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall) +{ + return + (*this)(_functionCall.functionName) + "(" + + boost::algorithm::join( + _functionCall.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), + ", " ) + + ")"; +} + string AsmPrinter::operator()(Block const& _block) { if (_block.statements.empty()) diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index 39069d021..a7a1de0a5 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -53,6 +53,8 @@ public: std::string operator()(assembly::Assignment const& _assignment); std::string operator()(assembly::FunctionalAssignment const& _functionalAssignment); std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); + std::string operator()(assembly::FunctionDefinition const& _functionDefinition); + std::string operator()(assembly::FunctionCall const& _functionCall); std::string operator()(assembly::Block const& _block); }; From 01fcd989b57d31fa8fc63720401ffc77698ee57b Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Feb 2017 16:08:33 +0100 Subject: [PATCH 287/414] More tests. --- test/libsolidity/InlineAssembly.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 40135a06f..ddbc3c632 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -224,6 +224,16 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) parsePrintCompare(parsed); } +BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) +{ + parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> (x, y)\n {\n }\n}"); +} + +BOOST_AUTO_TEST_CASE(function_calls) +{ + parsePrintCompare("{\n g(1, mul(2, x), f(mul(2, 3)))\n x()\n}"); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(Analysis) From dcc16c81e26f31141ae766096873b5fd7741cdf5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 11:45:06 +0100 Subject: [PATCH 288/414] Some checks for the existence of mobile type. --- Changelog.md | 1 + libsolidity/ast/Types.cpp | 7 +++++-- libsolidity/codegen/ExpressionCompiler.cpp | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index bd514cfe6..5684adbdf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Bugfixes: * Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``). * Commandline interface: Do not try creating paths ``.`` and ``..``. * Type system: Disallow arrays with negative length. + * Type system: Fix a crash related to invalid binary operators. ### 0.4.9 (2017-01-31) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 5b7b4a2cd..75dee6dbb 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -252,9 +252,9 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) { if (!_a || !_b) return TypePointer(); - else if (_b->isImplicitlyConvertibleTo(*_a->mobileType())) + else if (_a->mobileType() && _b->isImplicitlyConvertibleTo(*_a->mobileType())) return _a->mobileType(); - else if (_a->isImplicitlyConvertibleTo(*_b->mobileType())) + else if (_b->mobileType() && _a->isImplicitlyConvertibleTo(*_b->mobileType())) return _b->mobileType(); else return TypePointer(); @@ -1895,7 +1895,10 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons size_t si = fillRight ? i : components().size() - i - 1; size_t ti = fillRight ? i : targetComponents.size() - i - 1; if (components()[si] && targetComponents[ti]) + { tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[ti]); + solAssert(tempComponents[ti], ""); + } } return make_shared(tempComponents); } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 2ed19a833..1c2883cd7 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -220,6 +220,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType( _assignment.leftHandSide().annotation().type ); + solAssert(rightIntermediateType, ""); utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded); _assignment.leftHandSide().accept(*this); @@ -395,6 +396,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) TypePointer leftTargetType = commonType; TypePointer rightTargetType = Token::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType; + solAssert(rightTargetType, ""); // for commutative operators, push the literal as late as possible to allow improved optimization auto isLiteral = [](Expression const& _e) @@ -808,6 +810,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments[0]->accept(*this); // stack: newLength storageSlot slotOffset argValue TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); + solAssert(type, ""); utils().convertType(*arguments[0]->annotation().type, *type); utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack()); From e629cf5bc3dc093a302b461273d89c8dd5999cb6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 14:54:17 +0100 Subject: [PATCH 289/414] Test case. --- .../SolidityNameAndTypeResolution.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 1a4f3cdc9..507d90575 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -5079,6 +5079,22 @@ BOOST_AUTO_TEST_CASE(invalid_address_length) CHECK_WARNING(text, "checksum"); } +BOOST_AUTO_TEST_CASE(early_exit_on_fatal_errors) +{ + // This tests a crash that occured because we did not stop for fatal errors. + char const* text = R"( + contract C { + struct S { + ftring a; + } + S public s; + function s() s { + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique"); +} + BOOST_AUTO_TEST_SUITE_END() } From c3c3cccbec058f7f220994da7d272ce41d49d3e8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 12:36:43 +0100 Subject: [PATCH 290/414] Fix early exist for fatal errors. --- Changelog.md | 1 + libsolidity/analysis/NameAndTypeResolver.cpp | 141 ++++++++++--------- libsolidity/analysis/NameAndTypeResolver.h | 3 + libsolidity/analysis/ReferencesResolver.cpp | 9 +- libsolidity/analysis/ReferencesResolver.h | 2 +- libsolidity/ast/Types.cpp | 4 + 6 files changed, 87 insertions(+), 73 deletions(-) diff --git a/Changelog.md b/Changelog.md index bd514cfe6..72947dab0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ Features: Bugfixes: * Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``). * Commandline interface: Do not try creating paths ``.`` and ``..``. + * Type system: Fix a crash caused by continuing on fatal errors in the code. * Type system: Disallow arrays with negative length. ### 0.4.9 (2017-01-31) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 013842602..336dc894d 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -21,6 +21,7 @@ */ #include + #include #include #include @@ -130,62 +131,9 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map(&_node)) - { - m_currentScope = m_scopes[contract->scope()].get(); - solAssert(!!m_currentScope, ""); - - for (ASTPointer const& baseContract: contract->baseContracts()) - if (!resolveNamesAndTypes(*baseContract, true)) - success = false; - - m_currentScope = m_scopes[contract].get(); - - if (success) - { - linearizeBaseContracts(*contract); - vector properBases( - ++contract->annotation().linearizedBaseContracts.begin(), - contract->annotation().linearizedBaseContracts.end() - ); - - for (ContractDefinition const* base: properBases) - importInheritedScope(*base); - } - - // these can contain code, only resolve parameters for now - for (ASTPointer const& node: contract->subNodes()) - { - m_currentScope = m_scopes[contract].get(); - if (!resolveNamesAndTypes(*node, false)) - success = false; - } - - if (!success) - return false; - - if (!_resolveInsideCode) - return success; - - m_currentScope = m_scopes[contract].get(); - - // now resolve references inside the code - for (ASTPointer const& node: contract->subNodes()) - { - m_currentScope = m_scopes[contract].get(); - if (!resolveNamesAndTypes(*node, true)) - success = false; - } - } - else - { - if (m_scopes.count(&_node)) - m_currentScope = m_scopes[&_node].get(); - return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node); - } + return resolveNamesAndTypesInternal(_node, _resolveInsideCode); } catch (FatalError const&) { @@ -193,7 +141,6 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi throw; // Something is weird here, rather throw again. return false; } - return success; } bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) @@ -249,21 +196,25 @@ vector NameAndTypeResolver::cleanedDeclarations( solAssert(_declarations.size() > 1, ""); vector uniqueFunctions; - for (auto it = _declarations.begin(); it != _declarations.end(); ++it) + for (Declaration const* declaration: _declarations) { - solAssert(*it, ""); + solAssert(declaration, ""); // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1 - solAssert(dynamic_cast(*it) || dynamic_cast(*it) || dynamic_cast(*it), - "Found overloading involving something not a function or a variable"); + solAssert( + dynamic_cast(declaration) || + dynamic_cast(declaration) || + dynamic_cast(declaration), + "Found overloading involving something not a function or a variable." + ); - shared_ptr functionType { (*it)->functionType(false) }; + FunctionTypePointer functionType { declaration->functionType(false) }; if (!functionType) - functionType = (*it)->functionType(true); - solAssert(functionType, "failed to determine the function type of the overloaded"); + functionType = declaration->functionType(true); + solAssert(functionType, "Failed to determine the function type of the overloaded."); for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) if (!parameter) - reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); + reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context."); if (uniqueFunctions.end() == find_if( uniqueFunctions.begin(), @@ -276,11 +227,73 @@ vector NameAndTypeResolver::cleanedDeclarations( return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType); } )) - uniqueFunctions.push_back(*it); + uniqueFunctions.push_back(declaration); } return uniqueFunctions; } +bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode) +{ + if (ContractDefinition* contract = dynamic_cast(&_node)) + { + bool success = true; + m_currentScope = m_scopes[contract->scope()].get(); + solAssert(!!m_currentScope, ""); + + for (ASTPointer const& baseContract: contract->baseContracts()) + if (!resolveNamesAndTypes(*baseContract, true)) + success = false; + + m_currentScope = m_scopes[contract].get(); + + if (success) + { + linearizeBaseContracts(*contract); + vector properBases( + ++contract->annotation().linearizedBaseContracts.begin(), + contract->annotation().linearizedBaseContracts.end() + ); + + for (ContractDefinition const* base: properBases) + importInheritedScope(*base); + } + + // these can contain code, only resolve parameters for now + for (ASTPointer const& node: contract->subNodes()) + { + m_currentScope = m_scopes[contract].get(); + if (!resolveNamesAndTypes(*node, false)) + { + success = false; + break; + } + } + + if (!success) + return false; + + if (!_resolveInsideCode) + return success; + + m_currentScope = m_scopes[contract].get(); + + // now resolve references inside the code + for (ASTPointer const& node: contract->subNodes()) + { + m_currentScope = m_scopes[contract].get(); + if (!resolveNamesAndTypes(*node, true)) + success = false; + } + return success; + } + else + { + if (m_scopes.count(&_node)) + m_currentScope = m_scopes[&_node].get(); + return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node); + } +} + void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) { auto iterator = m_scopes.find(&_base); diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 828b566fc..038a887b4 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -89,6 +89,9 @@ public: ); private: + /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors. + bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true); + /// Imports all members declared directly in the given contract (i.e. does not import inherited members) /// into the current scope if they are not present already. void importInheritedScope(ContractDefinition const& _base); diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index c06181d89..37bcb2d9a 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -35,14 +35,7 @@ using namespace dev::solidity; bool ReferencesResolver::resolve(ASTNode const& _root) { - try - { - _root.accept(*this); - } - catch (FatalError const&) - { - solAssert(m_errorOccurred, ""); - } + _root.accept(*this); return !m_errorOccurred; } diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 23ac6b07c..dce343d33 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -52,7 +52,7 @@ public: m_resolveInsideCode(_resolveInsideCode) {} - /// @returns true if no errors during resolving + /// @returns true if no errors during resolving and throws exceptions on fatal errors. bool resolve(ASTNode const& _root); private: diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 5b7b4a2cd..96b3ed170 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1652,6 +1652,7 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const for (ASTPointer const& variable: m_struct.members()) { TypePointer type = variable->annotation().type; + solAssert(type, ""); // Skip all mapping members if we are not in storage. if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) continue; @@ -1964,6 +1965,8 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): if (auto structType = dynamic_cast(returnType.get())) { for (auto const& member: structType->members(nullptr)) + { + solAssert(member.type, ""); if (member.type->category() != Category::Mapping) { if (auto arrayType = dynamic_cast(member.type.get())) @@ -1972,6 +1975,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): retParams.push_back(member.type); retParamNames.push_back(member.name); } + } } else { From 811bb770c51bc63f9ccb2bff014482ba9c760132 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 15:49:59 +0100 Subject: [PATCH 291/414] Change effect of assert to invalid opcode. --- docs/control-structures.rst | 9 ++++++--- docs/miscellaneous.rst | 2 +- libsolidity/codegen/ExpressionCompiler.cpp | 5 ++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index df8ac729c..f1b2e6dac 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -398,10 +398,13 @@ Currently, Solidity automatically generates a runtime exception in the following While a user-provided exception is generated in the following situations: #. Calling ``throw``. -#. The condition of ``assert(condition)`` is not met. Internally, Solidity performs a revert operation (instruction ``0xfd``) when a user-provided exception is thrown. In contrast, it performs an invalid operation -(instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes +(instruction ``0xfe``) if a runtime exception is encountered or the condition of an ``assert`` call is not met. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction -(or at least call) without effect. \ No newline at end of file +(or at least call) without effect. + +If contracts are written so that ``assert`` is only used to test internal conditions and ``throw`` or +``revert`` is used in case of malformed input, a formal analysis tool that verifies that the invalid +opcode can never be reached can be used to check for the absence of errors assuming valid inputs. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 3c57507ef..80326bab2 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -461,7 +461,7 @@ Global Variables - ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error - ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256`` - ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256`` -- ``assert(bool condition)``: throws if the condition is false +- ``assert(bool condition)``: throws if the condition is false (using an invalid opcode) - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 2ed19a833..41cfcb69a 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -875,9 +875,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // jump if condition was met m_context << Instruction::ISZERO << Instruction::ISZERO; auto success = m_context.appendConditionalJump(); - // condition was not met, abort - m_context << u256(0) << u256(0); - m_context << Instruction::REVERT; + // condition was not met, flag an error + m_context << Instruction::INVALID; // the success branch m_context << success; break; From f93f9fa3a0dc1fada5688c0433a85cad1231a881 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 16:59:19 +0100 Subject: [PATCH 292/414] Add executable for use with AFL. --- test/CMakeLists.txt | 22 ++--------- test/fuzzer.cpp | 92 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 test/fuzzer.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 609aaab36..4d56ec9d6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,23 +7,9 @@ aux_source_directory(libsolidity SRC_LIST) aux_source_directory(contracts SRC_LIST) aux_source_directory(liblll SRC_LIST) -get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) +list(REMOVE_ITEM SRC_LIST "./fuzzer.cpp") -# search for test names and create ctest tests -enable_testing() -foreach(file ${SRC_LIST}) - file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/${file} test_list_raw REGEX "BOOST_.*TEST_(SUITE|CASE)") - set(TestSuite "DEFAULT") - foreach(test_raw ${test_list_raw}) - string(REGEX REPLACE ".*TEST_(SUITE|CASE)\\(([^ ,\\)]*).*" "\\1 \\2" test ${test_raw}) - if(test MATCHES "^SUITE .*") - string(SUBSTRING ${test} 6 -1 TestSuite) - elseif(test MATCHES "^CASE .*") - string(SUBSTRING ${test} 5 -1 TestCase) - add_test(NAME ${TestSuite}/${TestCase} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND test -t ${TestSuite}/${TestCase}) - endif(test MATCHES "^SUITE .*") - endforeach(test_raw) -endforeach(file) +get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) file(GLOB HEADERS "*.h" "*/*.h") set(EXECUTABLE soltest) @@ -34,5 +20,5 @@ eth_use(${EXECUTABLE} REQUIRED Solidity::solidity Solidity::lll) include_directories(BEFORE ..) target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) -enable_testing() -set(CTEST_OUTPUT_ON_FAILURE TRUE) +add_executable(solfuzzer fuzzer.cpp) +target_link_libraries(solfuzzer soljson) diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp new file mode 100644 index 000000000..5e46662e5 --- /dev/null +++ b/test/fuzzer.cpp @@ -0,0 +1,92 @@ +/* + 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 . +*/ +/** + * Executable for use with AFL . + * Reads a single source from stdin and signals a failure for internal errors. + */ + +#include + +#include +#include + +using namespace std; + +extern "C" +{ +extern char const* compileJSON(char const* _input, bool _optimize); +} + +string contains(string const& _haystack, vector const& _needles) +{ + for (string const& needle: _needles) + if (_haystack.find(needle) != string::npos) + return needle; + return ""; +} + +int main() +{ + string input; + while (!cin.eof()) + { + string s; + getline(cin, s); + input += s + '\n'; + } + + bool optimize = true; + string outputString(compileJSON(input.c_str(), optimize)); + Json::Value outputJson; + if (!Json::Reader().parse(outputString, outputJson)) + { + cout << "Compiler produced invalid JSON output." << endl; + return -1; + } + if (outputJson.isMember("errors")) + { + if (!outputJson["errors"].isArray()) + { + cout << "Output JSON has \"errors\" but it is not an array." << endl; + return -1; + } + for (Json::Value const& error: outputJson["errors"]) + { + string invalid = contains(error.asString(), vector{ + "Compiler error", + "Internal compiler error", + "Exception during compilation", + "Unknown exception during compilation", + "Unknown exception while generating contract data output", + "Unknown exception while generating formal method output", + "Unknown exception while generating source name output", + "Unknown error while generating JSON" + }); + if (!invalid.empty()) + { + cout << "Invalid error: \"" << invalid << "\"" << endl; + return -1; + } + } + } + else if (!outputJson.isMember("contracts")) + { + cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl; + return -1; + } + return 0; +} From 8be318e75bbed68520d3a3ef34f777804b6b60e0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 17:13:55 +0100 Subject: [PATCH 293/414] Include non-fuzzing fuzzer tests in commandline run. --- test/cmdlineTests.sh | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index fc48654a0..cb714efe2 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -31,7 +31,7 @@ set -e REPO_ROOT="$(dirname "$0")"/.. SOLC="$REPO_ROOT/build/solc/solc" - # Compile all files in std and examples. +# Compile all files in std and examples. for f in "$REPO_ROOT"/std/*.sol do @@ -46,6 +46,21 @@ do test -z "$output" -a "$failed" -eq 0 done -# Test library checksum -echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 -! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null +echo "Testing library checksum..." +echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 +! echo '' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null + +echo "Testing soljson via the fuzzer..." +TMPDIR=$(mktemp -d) +( + cd "$REPO_ROOT" + REPO_ROOT=$(pwd) # make it absolute + cd "$TMPDIR" + "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/contracts/* "$REPO_ROOT"/test/libsolidity/*EndToEnd* + for f in *.sol + do + "$REPO_ROOT"/build/test/solfuzzer < "$f" + done +) +rm -rf "$TMPDIR" +echo "Done." From f66ebbc8e2e07bc5e1b75b6708f2f209229f6bec Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 18:05:11 +0100 Subject: [PATCH 294/414] Report failures correctly to AFL. --- test/fuzzer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index 5e46662e5..85a8fe99b 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -55,14 +55,14 @@ int main() if (!Json::Reader().parse(outputString, outputJson)) { cout << "Compiler produced invalid JSON output." << endl; - return -1; + abort(); } if (outputJson.isMember("errors")) { if (!outputJson["errors"].isArray()) { cout << "Output JSON has \"errors\" but it is not an array." << endl; - return -1; + abort(); } for (Json::Value const& error: outputJson["errors"]) { @@ -79,14 +79,14 @@ int main() if (!invalid.empty()) { cout << "Invalid error: \"" << invalid << "\"" << endl; - return -1; + abort(); } } } else if (!outputJson.isMember("contracts")) { cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl; - return -1; + abort(); } return 0; } From 354f92f813704158e53267412fea91fb9a6ca8a8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 17 Feb 2017 16:04:42 +0100 Subject: [PATCH 295/414] Check error messages for assembly tests. --- test/libsolidity/InlineAssembly.cpp | 98 ++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index ddbc3c632..aef2d59ca 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -20,14 +20,19 @@ * Unit tests for inline assembly. */ -#include -#include -#include -#include +#include "../TestHelper.h" + #include +#include #include #include -#include "../TestHelper.h" +#include +#include + +#include + +#include +#include using namespace std; @@ -41,31 +46,44 @@ namespace test namespace { -bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true) +boost::optional parseAndReturnFirstError(string const& _source, bool _assemble = false, bool _allowWarnings = true) { assembly::InlineAssemblyStack stack; + bool success = false; try { - if (!stack.parse(std::make_shared(CharStream(_source)))) - return false; - if (_assemble) - { + success = stack.parse(std::make_shared(CharStream(_source))); + if (success && _assemble) stack.assemble(); - if (!stack.errors().empty()) - if (!_allowWarnings || !Error::containsOnlyWarnings(stack.errors())) - return false; + } + catch (FatalError const& e) + { + BOOST_FAIL("Fatal error leaked."); + success = false; + } + if (!success) + { + BOOST_CHECK_EQUAL(stack.errors().size(), 1); + return *stack.errors().front(); + } + else + { + // If success is true, there might still be an error in the assembly stage. + if (_allowWarnings && Error::containsOnlyWarnings(stack.errors())) + return {}; + else if (!stack.errors().empty()) + { + if (!_allowWarnings) + BOOST_CHECK_EQUAL(stack.errors().size(), 1); + return *stack.errors().front(); } } - catch (FatalError const&) - { - if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) - return false; - } - if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) - return false; + return {}; +} - BOOST_CHECK(Error::containsOnlyWarnings(stack.errors())); - return true; +bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true) +{ + return !parseAndReturnFirstError(_source, _assemble, _allowWarnings); } bool successAssemble(string const& _source, bool _allowWarnings = true) @@ -73,6 +91,14 @@ bool successAssemble(string const& _source, bool _allowWarnings = true) return successParse(_source, true, _allowWarnings); } +Error expectError(std::string const& _source, bool _assemble, bool _allowWarnings = false) +{ + + auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings); + BOOST_REQUIRE(error); + return *error; +} + void parsePrintCompare(string const& _source) { assembly::InlineAssemblyStack stack; @@ -83,6 +109,21 @@ void parsePrintCompare(string const& _source) } +#define CHECK_ERROR(text, assemble, typ, substring) \ +do \ +{ \ + Error err = expectError((text), (assemble), false); \ + BOOST_CHECK(err.type() == (Error::Type::typ)); \ + BOOST_CHECK(searchErrorMessage(err, (substring))); \ +} while(0) + +#define CHECK_PARSE_ERROR(text, type, substring) \ +CHECK_ERROR(text, false, type, substring) + +#define CHECK_ASSEMBLE_ERROR(text, type, substring) \ +CHECK_ERROR(text, true, type, substring) + + BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly) @@ -255,8 +296,8 @@ BOOST_AUTO_TEST_CASE(assignment_after_tag) BOOST_AUTO_TEST_CASE(magic_variables) { - BOOST_CHECK(!successAssemble("{ this }")); - BOOST_CHECK(!successAssemble("{ ecrecover }")); + CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique"); + CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique"); BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }")); } @@ -279,20 +320,17 @@ BOOST_AUTO_TEST_CASE(designated_invalid_instruction) BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration) { - // Error message: "Cannot use instruction names for identifier names." - BOOST_CHECK(!successAssemble("{ let gas := 1 }")); + CHECK_ASSEMBLE_ERROR("{ let gas := 1 }", ParserError, "Cannot use instruction names for identifier names."); } BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment) { - // Error message: "Identifier expected, got instruction name." - BOOST_CHECK(!successAssemble("{ 2 =: gas }")); + CHECK_ASSEMBLE_ERROR("{ 2 =: gas }", ParserError, "Identifier expected, got instruction name."); } BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment) { - // Error message: "Cannot use instruction names for identifier names." - BOOST_CHECK(!successAssemble("{ gas := 2 }")); + CHECK_ASSEMBLE_ERROR("{ gas := 2 }", ParserError, "Label name / variable name must precede \":\""); } BOOST_AUTO_TEST_CASE(revert) From d794d35e503e85d6fa2730eea9250a1c22ce430f Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 20 Feb 2017 11:42:23 +0100 Subject: [PATCH 296/414] Also check imbalanced stack. --- test/libsolidity/InlineAssembly.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index aef2d59ca..10a61777c 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -286,7 +286,7 @@ BOOST_AUTO_TEST_CASE(string_literals) BOOST_AUTO_TEST_CASE(oversize_string_literals) { - BOOST_CHECK(!successAssemble("{ let x := \"123456789012345678901234567890123\" }")); + CHECK_ASSEMBLE_ERROR("{ let x := \"123456789012345678901234567890123\" }", TypeError, "String literal too long"); } BOOST_AUTO_TEST_CASE(assignment_after_tag) @@ -304,7 +304,8 @@ BOOST_AUTO_TEST_CASE(magic_variables) BOOST_AUTO_TEST_CASE(imbalanced_stack) { BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false)); - BOOST_CHECK(!successAssemble("{ 1 }", false)); + CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves"); + CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes"); BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false)); } From 50894c6af84f5feedf4f6a1fcada75584e7dd4ac Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 20 Feb 2017 11:57:50 +0100 Subject: [PATCH 297/414] Fix compiler warning. --- test/libsolidity/InlineAssembly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 10a61777c..9035599ba 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -56,7 +56,7 @@ boost::optional parseAndReturnFirstError(string const& _source, bool _ass if (success && _assemble) stack.assemble(); } - catch (FatalError const& e) + catch (FatalError const&) { BOOST_FAIL("Fatal error leaked."); success = false; From 5cd01ab7d1efc108c3dc711ca76b4f75be4ac5c6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 20 Feb 2017 12:32:31 +0100 Subject: [PATCH 298/414] Test for unbalanced stack due to loading two values from outside. --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 507d90575..a1ebc3008 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4852,6 +4852,19 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) CHECK_WARNING(text, "Inline assembly block is not balanced"); } +BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) +{ + char const* text = R"( + contract c { + uint8 x; + function f() { + assembly { x pop } + } + } + )"; + CHECK_WARNING(text, "Inline assembly block is not balanced"); +} + BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) { char const* text = R"( From c0961664f9666e5f4e8e35e80ef9a71426f6c394 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 19:21:33 +0100 Subject: [PATCH 299/414] Deposit one stack item for non-value types in inline assembly type checking. --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 72947dab0..45aaf04a5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ Bugfixes: * Commandline interface: Do not try creating paths ``.`` and ``..``. * Type system: Fix a crash caused by continuing on fatal errors in the code. * Type system: Disallow arrays with negative length. + * Inline assembly: Charge one stack slot for non-value types during analysis. ### 0.4.9 (2017-01-31) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 28cb9acc3..4025831ea 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -611,7 +611,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); if (var->isLocalVariable()) pushes = var->type()->sizeOnStack(); - else if (var->type()->isValueType()) + else if (!var->type()->isValueType()) pushes = 1; else pushes = 2; // slot number, intra slot offset From a2f92033e78abf3b3e04fca4bf8a5a2c27e465fb Mon Sep 17 00:00:00 2001 From: Dmitriy Merkurev Date: Tue, 21 Feb 2017 22:44:18 +0300 Subject: [PATCH 300/414] add payable attribute --- docs/common-patterns.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index fa5e68a69..a2d7ce719 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -81,7 +81,7 @@ This is as opposed to the more intuitive sending pattern: mostSent = msg.value; } - function becomeRichest() returns (bool) { + function becomeRichest() payable returns (bool) { if (msg.value > mostSent) { // Check if call succeeds to prevent an attacker // from trapping the previous person's funds in From c9e4e1d7cac6d27a578d738f4c199ff96fff365a Mon Sep 17 00:00:00 2001 From: Mikko Ohtamaa Date: Wed, 22 Feb 2017 00:45:15 +0200 Subject: [PATCH 301/414] Downgrade instructions for Homebrew Solidity Because 0.4.9+ causes a lot of havoc, breaking tools --- docs/installing-solidity.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 42905ede4..fb405475d 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -102,6 +102,22 @@ We will re-add the pre-built bottles soon. brew install solidity brew linkapps solidity +If you need a specific version of Solidity you can install a +Homebrew formula directly from Github. + +View +`solidity.rb commits on Github `_. + +Follow the history links until you have a raw file link of a +specific commit of ``solidity.rb``. + +Install it using ``brew``: + +.. code:: bash + + brew unlink solidity + # Install 0.4.8 + brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb .. _building-from-source: @@ -264,4 +280,4 @@ Example: 3. a breaking change is introduced - version is bumped to 0.5.0 4. the 0.5.0 release is made -This behaviour works well with the version pragma. \ No newline at end of file +This behaviour works well with the version pragma. From 4b1e8111cc2469808d08e1718d3edd64b2cc4484 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Feb 2017 19:42:38 +0100 Subject: [PATCH 302/414] Remove assert for now. --- docs/control-structures.rst | 6 +--- docs/miscellaneous.rst | 3 +- libsolidity/analysis/GlobalContext.cpp | 5 ++-- test/libsolidity/SolidityEndToEndTest.cpp | 36 +++++++++++------------ 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index f1b2e6dac..019714f8e 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -400,11 +400,7 @@ While a user-provided exception is generated in the following situations: #. Calling ``throw``. Internally, Solidity performs a revert operation (instruction ``0xfd``) when a user-provided exception is thrown. In contrast, it performs an invalid operation -(instruction ``0xfe``) if a runtime exception is encountered or the condition of an ``assert`` call is not met. In both cases, this causes +(instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. - -If contracts are written so that ``assert`` is only used to test internal conditions and ``throw`` or -``revert`` is used in case of malformed input, a formal analysis tool that verifies that the invalid -opcode can never be reached can be used to check for the absence of errors assuming valid inputs. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 80326bab2..6e272eaa9 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -435,7 +435,7 @@ The following is the order of precedence for operators, listed in order of evalu | *16* | Comma operator | ``,`` | +------------+-------------------------------------+--------------------------------------------+ -.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Global Variables ================ @@ -461,7 +461,6 @@ Global Variables - ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error - ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256`` - ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256`` -- ``assert(bool condition)``: throws if the condition is false (using an invalid opcode) - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 4f100cd0e..069d10f5f 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -66,8 +66,9 @@ m_magicVariables(vector>{make_shared< make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared("ripemd160", make_shared(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), - make_shared("assert", - make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert)), +// Disabled until decision about semantics of assert is made. +// make_shared("assert", +// make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert)), make_shared("revert", make_shared(strings(), strings(), FunctionType::Location::Revert))}) { diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 68f8fbef3..19665a26f 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9083,24 +9083,24 @@ BOOST_AUTO_TEST_CASE(invalid_instruction) BOOST_CHECK(callContractFunction("f()") == encodeArgs()); } -BOOST_AUTO_TEST_CASE(assert) -{ - char const* sourceCode = R"( - contract C { - function f() { - assert(false); - } - function g(bool val) returns (bool) { - assert(val == true); - return true; - } - } - )"; - compileAndRun(sourceCode, 0, "C"); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); - BOOST_CHECK(callContractFunction("g(bool)", false) == encodeArgs()); - BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); -} +//BOOST_AUTO_TEST_CASE(assert) +//{ +// char const* sourceCode = R"( +// contract C { +// function f() { +// assert(false); +// } +// function g(bool val) returns (bool) { +// assert(val == true); +// return true; +// } +// } +// )"; +// compileAndRun(sourceCode, 0, "C"); +// BOOST_CHECK(callContractFunction("f()") == encodeArgs()); +// BOOST_CHECK(callContractFunction("g(bool)", false) == encodeArgs()); +// BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); +//} BOOST_AUTO_TEST_CASE(revert) { From 4264625c69a30c4534e977ce3ca709bb95103dad Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sun, 5 Feb 2017 20:21:14 +0000 Subject: [PATCH 303/414] Implement address.transfer() --- Changelog.md | 1 + libsolidity/ast/Types.cpp | 4 +++- libsolidity/ast/Types.h | 1 + libsolidity/codegen/ExpressionCompiler.cpp | 19 +++++++++++++++---- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Changelog.md b/Changelog.md index 45aaf04a5..37f20c716 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Features: * Add ``assert(condition)``, which throws if condition is false. + * Introduce ``.transfer(value)`` for sending Ether. * Code generator: Support ``revert()`` to abort with rolling back, but not consuming all gas. * Inline assembly: Support ``revert`` (EIP140) as an opcode. * Type system: Support explicit conversion of external function to address. diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 96b3ed170..7fccccbc0 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -464,7 +464,8 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons {"call", make_shared(strings(), strings{"bool"}, FunctionType::Location::Bare, true, false, true)}, {"callcode", make_shared(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true, false, true)}, {"delegatecall", make_shared(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)}, - {"send", make_shared(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)} + {"send", make_shared(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}, + {"transfer", make_shared(strings{"uint"}, strings(), FunctionType::Location::Transfer)} }; else return MemberList::MemberMap(); @@ -2097,6 +2098,7 @@ string FunctionType::identifier() const case Location::BareDelegateCall: id += "baredelegatecall"; break; case Location::Creation: id += "creation"; break; case Location::Send: id += "send"; break; + case Location::Transfer: id += "transfer"; break; case Location::SHA3: id += "sha3"; break; case Location::Selfdestruct: id += "selfdestruct"; break; case Location::Revert: id += "revert"; break; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 3546e5220..022b67c48 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -826,6 +826,7 @@ public: BareDelegateCall, ///< DELEGATECALL without function hash Creation, ///< external call using CREATE Send, ///< CALL, but without data and gas + Transfer, ///< CALL, but without data and throws on error SHA3, ///< SHA3 Selfdestruct, ///< SELFDESTRUCT Revert, ///< REVERT diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 2ed19a833..b0031513c 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -616,6 +616,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments.front()->accept(*this); break; case Location::Send: + case Location::Transfer: _functionCall.expression().accept(*this); // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. @@ -625,9 +626,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) *arguments.front()->annotation().type, *function.parameterTypes().front(), true ); - // gas <- gas * !value - m_context << Instruction::SWAP1 << Instruction::DUP2; - m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; + if (function.location() != Location::Transfer) + { + // gas <- gas * !value + m_context << Instruction::SWAP1 << Instruction::DUP2; + m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; + } appendExternalFunctionCall( FunctionType( TypePointers{}, @@ -644,6 +648,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) ), {} ); + if (function.location() == Location::Transfer) + { + // Check if zero (out of stack or not enough balance). + m_context << Instruction::ISZERO; + m_context.appendConditionalInvalid(); + } break; case Location::Selfdestruct: arguments.front()->accept(*this); @@ -960,6 +970,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case FunctionType::Location::Bare: case FunctionType::Location::BareCallCode: case FunctionType::Location::BareDelegateCall: + case FunctionType::Location::Transfer: _memberAccess.expression().accept(*this); m_context << funType->externalIdentifier(); break; @@ -1041,7 +1052,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); m_context << Instruction::BALANCE; } - else if ((set{"send", "call", "callcode", "delegatecall"}).count(member)) + else if ((set{"send", "transfer", "call", "callcode", "delegatecall"}).count(member)) utils().convertType( *_memberAccess.expression().annotation().type, IntegerType(0, IntegerType::Modifier::Address), From 16e48219d3071038bf7f696f234b50f6370a1171 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sun, 5 Feb 2017 22:43:36 +0000 Subject: [PATCH 304/414] Add test for address.transfer() --- test/libsolidity/SolidityEndToEndTest.cpp | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 68f8fbef3..cb0cc1684 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1681,6 +1681,42 @@ BOOST_AUTO_TEST_CASE(send_ether) BOOST_CHECK_EQUAL(balanceAt(address), amount); } +BOOST_AUTO_TEST_CASE(transfer_ether) +{ + char const* sourceCode = R"( + contract A { + function A() payable {} + function a(address addr, uint amount) returns (uint) { + addr.transfer(amount); + return this.balance; + } + function b(address addr, uint amount) { + addr.transfer(amount); + } + } + + contract B { + } + + contract C { + function () payable { + throw; + } + } + )"; + compileAndRun(sourceCode, 0, "B"); + u160 const nonPayableRecipient = m_contractAddress; + compileAndRun(sourceCode, 0, "C"); + u160 const oogRecipient = m_contractAddress; + compileAndRun(sourceCode, 20, "A"); + u160 payableRecipient(23); + BOOST_CHECK(callContractFunction("a(address,uint256)", payableRecipient, 10) == encodeArgs(10)); + BOOST_CHECK_EQUAL(balanceAt(payableRecipient), 10); + BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10); + BOOST_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10) == encodeArgs()); + BOOST_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10) == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(log0) { char const* sourceCode = R"( From ba437ef31a95f40f510a475e8b329f061e929b90 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 16:14:43 +0000 Subject: [PATCH 305/414] Add type checking test for address methods --- .../SolidityNameAndTypeResolution.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index a1ebc3008..bb2746147 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -5108,6 +5108,24 @@ BOOST_AUTO_TEST_CASE(early_exit_on_fatal_errors) CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique"); } +BOOST_AUTO_TEST_CASE(address_methods) +{ + char const* text = R"( + contract C { + function f() { + address addr; + uint balance = addr.balance; + bool callRet = addr.call(); + bool callcodeRet = addr.callcode(); + bool delegatecallRet = addr.delegatecall(); + bool sendRet = addr.send(1); + addr.transfer(1); + } + } + )"; + CHECK_SUCCESS(text); +} + BOOST_AUTO_TEST_SUITE_END() } From 81006dae98ee18c33994af0274de10857774ff70 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 16:54:05 +0000 Subject: [PATCH 306/414] Support gas modifier on addr.transfer() --- libsolidity/ast/Types.cpp | 3 ++- libsolidity/codegen/ExpressionCompiler.cpp | 3 ++- test/libsolidity/SolidityEndToEndTest.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 7fccccbc0..3e3a3818c 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2315,9 +2315,10 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con case Location::Bare: case Location::BareCallCode: case Location::BareDelegateCall: + case Location::Transfer: { MemberList::MemberMap members; - if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall) + if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall && m_location != Location::Transfer) { if (m_isPayable) members.push_back(MemberList::Member( diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index b0031513c..92b915639 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -620,7 +620,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.expression().accept(*this); // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. - m_context << u256(eth::GasCosts::callStipend); + if (!function.gasSet()) + m_context << u256(eth::GasCosts::callStipend); arguments.front()->accept(*this); utils().convertType( *arguments.front()->annotation().type, diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index cb0cc1684..30430aefe 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1693,6 +1693,10 @@ BOOST_AUTO_TEST_CASE(transfer_ether) function b(address addr, uint amount) { addr.transfer(amount); } + function c(address addr, uint amount, uint gas) returns (uint) { + addr.transfer.gas(gas)(amount); + return this.balance; + } } contract B { @@ -1715,6 +1719,8 @@ BOOST_AUTO_TEST_CASE(transfer_ether) BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10); BOOST_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10) == encodeArgs()); BOOST_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10) == encodeArgs()); + BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", payableRecipient, 1, 9000) == encodeArgs(9)); + BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", payableRecipient, 1, 0) == encodeArgs()); } BOOST_AUTO_TEST_CASE(log0) From a36e2ce0cbb7e1830e25dacd8d79f207c9bcca6a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 23:37:23 +0000 Subject: [PATCH 307/414] Document transfer() --- docs/control-structures.rst | 1 + docs/miscellaneous.rst | 5 +++-- docs/types.rst | 6 +++++- docs/units-and-global-variables.rst | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index df8ac729c..ebc459657 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -395,6 +395,7 @@ Currently, Solidity automatically generates a runtime exception in the following #. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). #. If your contract receives Ether via a public getter function. #. If you call a zero-initialized variable of internal function type. +#. If a ``.transfer()`` fails. While a user-provided exception is generated in the following situations: #. Calling ``throw``. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 3c57507ef..7b0305d5a 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -465,8 +465,9 @@ Global Variables - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address -- ``
.balance`` (``uint256``): balance of the address in Wei -- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to address, returns ``false`` on failure +- ``
.balance`` (``uint256``): balance of the :ref:`address` in Wei +- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure +- ``
.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure .. index:: visibility, public, private, external, internal diff --git a/docs/types.rst b/docs/types.rst index 69c23e6e0..342b67f4e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -64,7 +64,7 @@ expression ``x << y`` is equivalent to ``x * 2**y`` and ``x >> y`` is equivalent to ``x / 2**y``. This means that shifting negative numbers sign extends. Shifting by a negative amount throws a runtime exception. -.. index:: address, balance, send, call, callcode, delegatecall +.. index:: address, balance, send, call, callcode, delegatecall, transfer .. _address: @@ -102,6 +102,10 @@ and to send Ether (in units of wei) to an address using the ``send`` function: to make safe Ether transfers, always check the return value of ``send`` or even better: Use a pattern where the recipient withdraws the money. +* ``transfer`` + +Transfer operates the same way as ``send``, with the exception that it will cause a exception if the transfer has failed. + * ``call``, ``callcode`` and ``delegatecall`` Furthermore, to interface with contracts that do not adhere to the ABI, diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 72741b672..49fe5d84c 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -130,6 +130,8 @@ Address Related balance of the :ref:`address` in Wei ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure +``
.transfer(uint256 amount)``: + send given amount of Wei to :ref:`address`, throws on failure For more information, see the section on :ref:`address`. From c674155e584d2a1d6a88c49485e281e2a12b85d0 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 14:38:43 +0000 Subject: [PATCH 308/414] Do not keep the gas stipend if sending non-zero value --- libsolidity/codegen/ExpressionCompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 92b915639..4956871d7 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -627,7 +627,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) *arguments.front()->annotation().type, *function.parameterTypes().front(), true ); - if (function.location() != Location::Transfer) + if (!function.gasSet()) { // gas <- gas * !value m_context << Instruction::SWAP1 << Instruction::DUP2; From c46c68dfd8a5a8d82c19335c20d2bfa3aa8dd9ec Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 23:15:32 +0000 Subject: [PATCH 309/414] Prefer .transfer() over .send() in the documentation --- docs/types.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 342b67f4e..f7d1d54fa 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -80,31 +80,31 @@ Operators: Members of Addresses ^^^^^^^^^^^^^^^^^^^^ -* ``balance`` and ``send`` +* ``balance`` and ``transfer`` For a quick reference, see :ref:`address_related`. It is possible to query the balance of an address using the property ``balance`` -and to send Ether (in units of wei) to an address using the ``send`` function: +and to send Ether (in units of wei) to an address using the ``transfer`` function: :: address x = 0x123; address myAddress = this; - if (x.balance < 10 && myAddress.balance >= 10) x.send(10); + if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); .. note:: - If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``send`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted. In this case, ``send`` returns ``false``. + If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. + +* ``send`` + +Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``. .. warning:: There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order - to make safe Ether transfers, always check the return value of ``send`` or even better: - Use a pattern where the recipient withdraws the money. - -* ``transfer`` - -Transfer operates the same way as ``send``, with the exception that it will cause a exception if the transfer has failed. + to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: + use a pattern where the recipient withdraws the money. * ``call``, ``callcode`` and ``delegatecall`` From cde027d144643883106d8bcef034c9e8ace2b3b2 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sun, 12 Feb 2017 14:07:52 +0000 Subject: [PATCH 310/414] Fix test for gas overloading in .transfer() --- test/libsolidity/SolidityEndToEndTest.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 30430aefe..f77a8935b 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1707,11 +1707,22 @@ BOOST_AUTO_TEST_CASE(transfer_ether) throw; } } + + contract D { + // This takes about ~3600 gas, which exceeds the 2300 gas stipend. + function () payable { + bytes32 tmp = 1; + for (uint i = 0; i < 20; i++) + tmp = sha3(tmp); + } + } )"; compileAndRun(sourceCode, 0, "B"); u160 const nonPayableRecipient = m_contractAddress; compileAndRun(sourceCode, 0, "C"); u160 const oogRecipient = m_contractAddress; + compileAndRun(sourceCode, 0, "D"); + u160 const expensiveRecipient = m_contractAddress; compileAndRun(sourceCode, 20, "A"); u160 payableRecipient(23); BOOST_CHECK(callContractFunction("a(address,uint256)", payableRecipient, 10) == encodeArgs(10)); @@ -1719,8 +1730,8 @@ BOOST_AUTO_TEST_CASE(transfer_ether) BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10); BOOST_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10) == encodeArgs()); BOOST_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10) == encodeArgs()); - BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", payableRecipient, 1, 9000) == encodeArgs(9)); - BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", payableRecipient, 1, 0) == encodeArgs()); + BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", expensiveRecipient, 1, 9000) == encodeArgs(9)); + BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", expensiveRecipient, 1, 0) == encodeArgs()); } BOOST_AUTO_TEST_CASE(log0) From 59514d8268585f02aa1abb1fdb1b56bcf6e56ef4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 13 Feb 2017 02:17:17 +0000 Subject: [PATCH 311/414] Remove obsolete .send() entry from FAQ --- docs/frequently-asked-questions.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index d2fc5b1ce..8a68ae5b9 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -660,16 +660,6 @@ https://github.com/ethereum/wiki/wiki/Subtleties After a successful CREATE operation's sub-execution, if the operation returns x, 5 * len(x) gas is subtracted from the remaining gas before the contract is created. If the remaining gas is less than 5 * len(x), then no gas is subtracted, the code of the created contract becomes the empty string, but this is not treated as an exceptional condition - no reverts happen. -How do I use ``.send()``? -========================= - -If you want to send 20 Ether from a contract to the address ``x``, you use ``x.send(20 ether);``. -Here, ``x`` can be a plain address or a contract. If the contract already explicitly defines -a function ``send`` (and thus overwrites the special function), you can use ``address(x).send(20 ether);``. - -Note that the call to ``send`` may fail in certain conditions, such as if you have insufficient funds, so you should always check the return value. -``send`` returns ``true`` if the send was successful and ``false`` otherwise. - What does the following strange check do in the Custom Token contract? ====================================================================== From 4d290e551c2d563671f9d56744883d3f3dff98ec Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 24 Feb 2017 00:27:36 +0000 Subject: [PATCH 312/414] Disallow setting .gas() on .transfer() --- libsolidity/ast/Types.cpp | 3 +-- libsolidity/codegen/ExpressionCompiler.cpp | 12 ++++-------- test/libsolidity/SolidityEndToEndTest.cpp | 17 ----------------- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3e3a3818c..7fccccbc0 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2315,10 +2315,9 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con case Location::Bare: case Location::BareCallCode: case Location::BareDelegateCall: - case Location::Transfer: { MemberList::MemberMap members; - if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall && m_location != Location::Transfer) + if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall) { if (m_isPayable) members.push_back(MemberList::Member( diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 4956871d7..fd4d87a58 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -620,19 +620,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.expression().accept(*this); // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. - if (!function.gasSet()) - m_context << u256(eth::GasCosts::callStipend); + m_context << u256(eth::GasCosts::callStipend); arguments.front()->accept(*this); utils().convertType( *arguments.front()->annotation().type, *function.parameterTypes().front(), true ); - if (!function.gasSet()) - { - // gas <- gas * !value - m_context << Instruction::SWAP1 << Instruction::DUP2; - m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; - } + // gas <- gas * !value + m_context << Instruction::SWAP1 << Instruction::DUP2; + m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; appendExternalFunctionCall( FunctionType( TypePointers{}, diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index f77a8935b..cb0cc1684 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1693,10 +1693,6 @@ BOOST_AUTO_TEST_CASE(transfer_ether) function b(address addr, uint amount) { addr.transfer(amount); } - function c(address addr, uint amount, uint gas) returns (uint) { - addr.transfer.gas(gas)(amount); - return this.balance; - } } contract B { @@ -1707,22 +1703,11 @@ BOOST_AUTO_TEST_CASE(transfer_ether) throw; } } - - contract D { - // This takes about ~3600 gas, which exceeds the 2300 gas stipend. - function () payable { - bytes32 tmp = 1; - for (uint i = 0; i < 20; i++) - tmp = sha3(tmp); - } - } )"; compileAndRun(sourceCode, 0, "B"); u160 const nonPayableRecipient = m_contractAddress; compileAndRun(sourceCode, 0, "C"); u160 const oogRecipient = m_contractAddress; - compileAndRun(sourceCode, 0, "D"); - u160 const expensiveRecipient = m_contractAddress; compileAndRun(sourceCode, 20, "A"); u160 payableRecipient(23); BOOST_CHECK(callContractFunction("a(address,uint256)", payableRecipient, 10) == encodeArgs(10)); @@ -1730,8 +1715,6 @@ BOOST_AUTO_TEST_CASE(transfer_ether) BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10); BOOST_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10) == encodeArgs()); BOOST_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10) == encodeArgs()); - BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", expensiveRecipient, 1, 9000) == encodeArgs(9)); - BOOST_CHECK(callContractFunction("c(address,uint256,uint256)", expensiveRecipient, 1, 0) == encodeArgs()); } BOOST_AUTO_TEST_CASE(log0) From 46d3c2dd3d3101a3d9c0eba485cf4503c8a40890 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Feb 2017 19:58:08 +0100 Subject: [PATCH 313/414] Print source location before items. --- libevmasm/Assembly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index b7859c1f0..f12e8aa87 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -130,8 +130,8 @@ public: if (!_item.location().isEmpty() && _item.location() != m_location) { flush(); - printLocation(); m_location = _item.location(); + printLocation(); } if (!( _item.canBeFunctional() && From de1317331f2607cc1e89779ccfd0ffa812628642 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 24 Feb 2017 08:25:01 +0100 Subject: [PATCH 314/414] Changelog entry. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 45aaf04a5..1d61f09d5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ Bugfixes: * Type system: Fix a crash caused by continuing on fatal errors in the code. * Type system: Disallow arrays with negative length. * Inline assembly: Charge one stack slot for non-value types during analysis. + * Assembly output: Print source location before the operation it refers to instead of after. ### 0.4.9 (2017-01-31) From 7a24a5764edf728f0caa14e7aa8f832d7e6f42e3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 24 Feb 2017 19:31:20 +0100 Subject: [PATCH 315/414] Add line info to serious exceptions. --- libdevcore/Exceptions.h | 3 +++ libsolidity/interface/Exceptions.cpp | 14 ++++++++++++++ solc/jsonCompiler.cpp | 6 +++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index 4622774a4..37cdbed96 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -41,6 +41,9 @@ struct Exception: virtual std::exception, virtual boost::exception Exception(std::string _message = std::string()): m_message(std::move(_message)) {} const char* what() const noexcept override { return m_message.empty() ? std::exception::what() : m_message.c_str(); } + /// @returns "FileName:LineNumber" referring to the point where the exception was thrown. + std::string lineInfo() const; + private: std::string m_message; }; diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp index 90a680b4d..41890b91a 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/libsolidity/interface/Exceptions.cpp @@ -23,6 +23,7 @@ #include #include +using namespace std; using namespace dev; using namespace dev::solidity; @@ -56,3 +57,16 @@ Error::Error(Type _type): m_type(_type) break; } } + +string Exception::lineInfo() const +{ + char const* const* file = boost::get_error_info(*this); + int const* line = boost::get_error_info(*this); + string ret; + if (file) + ret += *file; + ret += ':'; + if (line) + ret += boost::lexical_cast(*line); + return ret; +} diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index d761b541c..6ebd1a55a 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -184,15 +184,15 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback } catch (CompilerError const& exception) { - errors.append(formatError(exception, "Compiler error", scannerFromSourceName)); + errors.append(formatError(exception, "Compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (InternalCompilerError const& exception) { - errors.append(formatError(exception, "Internal compiler error", scannerFromSourceName)); + errors.append(formatError(exception, "Internal compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (UnimplementedFeatureError const& exception) { - errors.append(formatError(exception, "Unimplemented feature", scannerFromSourceName)); + errors.append(formatError(exception, "Unimplemented feature (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (Exception const& exception) { From 8877d4a7819a589d41b87e10237fa281e453a604 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 24 Feb 2017 19:31:39 +0100 Subject: [PATCH 316/414] Compiler error is not a failure. --- test/fuzzer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index 85a8fe99b..f957d4edd 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -67,7 +67,6 @@ int main() for (Json::Value const& error: outputJson["errors"]) { string invalid = contains(error.asString(), vector{ - "Compiler error", "Internal compiler error", "Exception during compilation", "Unknown exception during compilation", From 9acfdb80444e94c2fe8845f796f13f8dedc49b22 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 24 Feb 2017 19:31:52 +0100 Subject: [PATCH 317/414] Print full error on failure. --- test/fuzzer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index f957d4edd..410313c5c 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -77,7 +77,7 @@ int main() }); if (!invalid.empty()) { - cout << "Invalid error: \"" << invalid << "\"" << endl; + cout << "Invalid error: \"" << error.asString() << "\"" << endl; abort(); } } From 41360ccd57997edfd6807c75a33f4dbc4b65558b Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 24 Feb 2017 19:33:05 +0100 Subject: [PATCH 318/414] Script for filtering unique failures. --- scripts/uniqueErrors.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 scripts/uniqueErrors.sh diff --git a/scripts/uniqueErrors.sh b/scripts/uniqueErrors.sh new file mode 100755 index 000000000..eee1df903 --- /dev/null +++ b/scripts/uniqueErrors.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +REPO=$(dirname $0)/.. + +echo "Finding unique failures..." +( +for x in $* +do + echo -n $x " # " + # This subshell is a workaround to prevent the shell from printing + # "Aborted" + ("$REPO"/build/test/solfuzzer < "$x" || true) 2>&1 | head -n 1 +done +) | sort -u -t'#' -k 2 From 4305ecb0e7bca7f64324e0804df9543c4775aa6f Mon Sep 17 00:00:00 2001 From: chriseth Date: Sat, 25 Feb 2017 00:11:26 +0100 Subject: [PATCH 319/414] Try reading multiple times from IPC. --- test/RPCSession.cpp | 39 +++++++++++++++++++++++++-------------- test/RPCSession.h | 10 ++++++++-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index ff00d7833..57829ff5c 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -16,19 +16,20 @@ The Implementation originally from https://msdn.microsoft.com/en-us/library/windows/desktop/aa365592(v=vs.85).aspx */ -/** @file RPCSession.cpp - * @author Dimtiry Khokhlov - * @author Alex Beregszaszi - * @date 2016 - */ +/// @file RPCSession.cpp +/// Low-level IPC communication between the test framework and the Ethereum node. + +#include "RPCSession.h" + +#include + +#include +#include #include #include #include -#include -#include -#include -#include "RPCSession.h" +#include using namespace std; using namespace dev; @@ -107,13 +108,23 @@ string IPCSocket::sendRequest(string const& _req) return string(m_readBuf, m_readBuf + cbRead); #else if (send(m_socket, _req.c_str(), _req.length(), 0) != (ssize_t)_req.length()) - BOOST_FAIL("Writing on IPC failed"); + BOOST_FAIL("Writing on IPC failed."); - ssize_t ret = recv(m_socket, m_readBuf, sizeof(m_readBuf), 0); + auto start = chrono::steady_clock::now(); + ssize_t ret; + do + { + ret = recv(m_socket, m_readBuf, sizeof(m_readBuf), 0); + // Also consider closed socket an error. + if (ret < 0) + BOOST_FAIL("Reading on IPC failed."); + } while ( + ret == 0 && + chrono::duration_cast(chrono::steady_clock::now() - start).count() < m_readTimeOutMS + ); - // Also consider closed socket an error. - if (ret <= 0) - BOOST_FAIL("Reading on IPC failed"); + if (ret == 0) + BOOST_FAIL("Timeout reading on IPC."); return string(m_readBuf, m_readBuf + ret); #endif diff --git a/test/RPCSession.h b/test/RPCSession.h index 414db3232..843036e14 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -28,11 +28,13 @@ #include #endif +#include + +#include + #include #include #include -#include -#include #if defined(_WIN32) class IPCSocket : public boost::noncopyable @@ -60,8 +62,12 @@ public: std::string const& path() const { return m_path; } private: + std::string m_path; int m_socket; + /// Socket read timeout in milliseconds. Needs to be large because the key generation routine + /// might take long. + unsigned static constexpr m_readTimeOutMS = 15000; char m_readBuf[512000]; }; #endif From 9f1a67caa5f532e8221b64225abd0b313afca097 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 11:32:29 +0100 Subject: [PATCH 320/414] Some logging around account creation. --- test/RPCSession.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 57829ff5c..a71ff2862 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -197,12 +197,17 @@ string RPCSession::eth_getStorageRoot(string const& _address, string const& _blo void RPCSession::personal_unlockAccount(string const& _address, string const& _password, int _duration) { - BOOST_REQUIRE(rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }) == true); + BOOST_REQUIRE_MESSAGE( + rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }), + "Error unlocking account " + _address + ); } string RPCSession::personal_newAccount(string const& _password) { - return rpcCall("personal_newAccount", { quote(_password) }).asString(); + string addr = rpcCall("personal_newAccount", { quote(_password) }).asString(); + BOOST_MESSAGE("Created account " + addr); + return addr; } void RPCSession::test_setChainParams(vector const& _accounts) From f35b70f3a325f7dd31eb51b0cff3eaf993bfffbb Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 15:53:24 +0100 Subject: [PATCH 321/414] Test case for external function type with calldata argument. --- .../SolidityNameAndTypeResolution.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index bb2746147..866bd9aa4 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4736,6 +4736,23 @@ BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(external_function_to_function_type_calldata_parameter) +{ + // This is a test that checks that the type of the `bytes` parameter is + // correctly changed from its own type `bytes calldata` to `bytes memory` + // when converting to a function type. + char const* text = R"( + contract C { + function f(function(bytes memory x) external g) { } + function callback(bytes x) external {} + function g() { + f(this.callback); + } + } + )"; + CHECK_SUCCESS(text); +} + BOOST_AUTO_TEST_CASE(external_function_type_to_address) { char const* text = R"( From a689152c4bcf21f6f1d7ffbc5e2444a4c2ff75ee Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 16:02:36 +0100 Subject: [PATCH 322/414] Convert reference types to pointers in member function conversion. --- libsolidity/ast/Types.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 99d6f4a26..7f267cc9b 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2493,7 +2493,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) { auto refType = dynamic_cast(t.get()); if (refType && refType->location() == DataLocation::CallData) - parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, false)); + parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, true)); else parameterTypes.push_back(t); } From b832b70e1bf09087005f0e620fa5e20d57fd3310 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 16:09:19 +0100 Subject: [PATCH 323/414] Changelog entry. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index c46ba3bde..3eaadf21e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,7 @@ Bugfixes: * Type system: Fix a crash caused by continuing on fatal errors in the code. * Type system: Disallow arrays with negative length. * Type system: Fix a crash related to invalid binary operators. + * Type system: Correctly convert function argument types to pointers for member functions. * Inline assembly: Charge one stack slot for non-value types during analysis. * Assembly output: Print source location before the operation it refers to instead of after. From ea7f5f96408bbdd06417480ac570f2b289f25581 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 2 Mar 2017 12:07:50 +0100 Subject: [PATCH 324/414] Style. --- test/RPCSession.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index a71ff2862..be8774bcf 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -118,7 +118,8 @@ string IPCSocket::sendRequest(string const& _req) // Also consider closed socket an error. if (ret < 0) BOOST_FAIL("Reading on IPC failed."); - } while ( + } + while ( ret == 0 && chrono::duration_cast(chrono::steady_clock::now() - start).count() < m_readTimeOutMS ); From 2600fa041319b285f43ac0c751756d422a4e2658 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 15:42:41 +0100 Subject: [PATCH 325/414] Test for declaring variable with empty tuple type. --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 866bd9aa4..3b1375721 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2950,6 +2950,19 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_6) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(tuple_assignment_from_void_function) +{ + char const* text = R"( + contract C { + function f() { } + function g() { + var (x,) = (f(), f()); + } + } + )"; + CHECK_ERROR(text, TypeError, "Cannot declare variable with void (empty tuple) type."); +} + BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity) { char const* text = R"( From cc01d870ff642ee3c52f5473f6f2a2fba5ec15c8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 15:42:53 +0100 Subject: [PATCH 326/414] Disallow variable declaration with inferred empty tuple type. --- libsolidity/analysis/TypeChecker.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 4025831ea..ff55ef1f9 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -824,6 +824,11 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) else solAssert(false, ""); } + else if (*var.annotation().type == TupleType()) + typeError( + var.location(), + "Cannot declare variable with void (empty tuple) type." + ); var.accept(*this); } else From 6a9df162fd30237e9313a28599d4b0f26f2ea4f2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Mar 2017 15:47:25 +0100 Subject: [PATCH 327/414] Changelog entry. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 3eaadf21e..5eb1e401e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,7 @@ Bugfixes: * Type system: Fix a crash caused by continuing on fatal errors in the code. * Type system: Disallow arrays with negative length. * Type system: Fix a crash related to invalid binary operators. + * Type system: Disallow ``var`` declaration with empty tuple type. * Type system: Correctly convert function argument types to pointers for member functions. * Inline assembly: Charge one stack slot for non-value types during analysis. * Assembly output: Print source location before the operation it refers to instead of after. From fd62adebf3e594298c171e43e78153dbefc42759 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Feb 2017 18:14:40 +0100 Subject: [PATCH 328/414] Tests for labels with stack information. --- test/libsolidity/InlineAssembly.cpp | 10 +++++++++ test/libsolidity/SolidityEndToEndTest.cpp | 27 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 9035599ba..824fce958 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -200,6 +200,11 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); } +BOOST_AUTO_TEST_CASE(labels_with_stack_info) +{ + BOOST_CHECK(successParse("{ x[-1]: y[a]: z[d, e]: h[100]: g[]: }")); +} + BOOST_AUTO_TEST_CASE(function_definitions) { BOOST_CHECK(successParse("{ function f() { } function g(a) -> (x) { } }")); @@ -244,6 +249,11 @@ BOOST_AUTO_TEST_CASE(print_label) parsePrintCompare("{\n loop:\n jump(loop)\n}"); } +BOOST_AUTO_TEST_CASE(print_label_with_stack) +{ + parsePrintCompare("{\n loop[x, y]:\n other[-2]:\n third[10]:\n}"); +} + BOOST_AUTO_TEST_CASE(print_assignments) { parsePrintCompare("{\n let x := mul(2, 3)\n 7\n =: x\n x := add(1, 2)\n}"); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index cb0cc1684..d20283449 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7418,6 +7418,33 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access) BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(10))); } +BOOST_AUTO_TEST_CASE(inline_assembly_labels_with_stack_info) +{ + char const* sourceCode = R"( + contract C { + function f(uint y) { + assembly { + y 7 + jump(fun) + exit[-1]: + y add + 0 mstore + return(0, 0x20) + fun: + { + entry[a, b]: + a := div(a, b) + pop + jump(exit) + } + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", u256(15)) == encodeArgs(15 / 7 + 15)); +} + BOOST_AUTO_TEST_CASE(index_access_with_type_conversion) { // Test for a bug where higher order bits cleanup was not done for array index access. From 98e343b3fc11f3e94297b016c3f625e3b319b09b Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 1 Feb 2017 21:40:50 +0100 Subject: [PATCH 329/414] Parsing of labels with stack info. --- libsolidity/inlineasm/AsmCodeGen.cpp | 1 + libsolidity/inlineasm/AsmData.h | 4 ++-- libsolidity/inlineasm/AsmParser.cpp | 32 ++++++++++++++++++++++++++++ libsolidity/inlineasm/AsmPrinter.cpp | 6 +++++- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index faa7dabdf..abc0ca01b 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -91,6 +91,7 @@ public: void operator()(T const& /*_item*/) { } void operator()(Label const& _item) { + solAssert(_item.stackInfo.empty(), "Labels with stack info not yet supported."); if (m_state.labels.count(_item.name)) //@TODO secondary location m_state.addError( diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index d61b5803e..5969bf963 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -42,8 +42,8 @@ struct Literal { SourceLocation location; bool isNumber; std::string value; }; /// External / internal identifier or label reference struct Identifier { SourceLocation location; std::string name; }; struct FunctionalInstruction; -/// Jump label ("name:") -struct Label { SourceLocation location; std::string name; }; +/// Jump label ("name:" or "name [x, y, z]:" or "name [-3]:") +struct Label { SourceLocation location; std::string name; std::vector stackInfo; }; /// Assignemnt (":= x", moves stack top into x, potentially multiple slots) struct Assignment { SourceLocation location; Identifier variableName; }; struct FunctionalAssignment; diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 0fc0a34f8..5d439b2f0 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -121,6 +121,38 @@ assembly::Statement Parser::parseStatement() return label; } } + case Token::LBrack: + { + if (statement.type() != typeid(assembly::Identifier)) + fatalParserError("Label name must precede \"[\"."); + assembly::Identifier const& identifier = boost::get(statement); + Label label = createWithLocation