diff options
86 files changed, 1582 insertions, 520 deletions
diff --git a/CODING_STYLE.md b/CODING_STYLE.md new file mode 100644 index 00000000..2cc9ac70 --- /dev/null +++ b/CODING_STYLE.md @@ -0,0 +1,262 @@ +0. Formatting + +GOLDEN RULE: Follow the style of the existing code when you make changes. + +a. Use tabs for leading indentation +- tab stops are every 4 characters (only relevant for line length). +- One indentation level -> exactly one byte (i.e. a tab character) in the source file. +b. Line widths: +- Lines should be at most 99 characters wide to make diff views readable and reduce merge conflicts. +- Lines of comments should be formatted according to ease of viewing, but simplicity is to be preferred over beauty. +c. Single-statement blocks should not have braces, unless required for clarity. +d. Never place condition bodies on same line as condition. +e. Space between keyword and opening parenthesis, but not following opening parenthesis or before final parenthesis. +f. No spaces for unary operators, `->` or `.`. +g. No space before ':' but one after it, except in the ternary operator: one on both sides. +h. Add spaces around all other operators. +i. Braces, when used, always have their own lines and are at same indentation level as "parent" scope. +j. If lines are broken, a list of elements enclosed with parentheses (of any kind) and separated by a + separator (of any kind) are formatted such that there is exactly one element per line, followed by + the separator, the opening parenthesis is on the first line, followed by a line break and the closing + parenthesis is on a line of its own (unindented). See example below. + +(WRONG) +if( a==b[ i ] ) { printf ("Hello\n"); } +foo->bar(someLongVariableName, + anotherLongVariableName, + anotherLongVariableName, + anotherLongVariableName, + anotherLongVariableName); +cout << "some very long string that contains completely irrelevant text that talks about this and that and contains the words \"lorem\" and \"ipsum\"" << endl; + +(RIGHT) +if (a == b[i]) + printf("Hello\n"); // NOTE spaces used instead of tab here for clarity - first byte should be '\t'. +foo->bar( + someLongVariableName, + anotherLongVariableName, + anotherLongVariableName, + anotherLongVariableName, + anotherLongVariableName +); +cout << + "some very long string that contains completely irrelevant " << + "text that talks about this and that and contains the words " << + "\"lorem\" and \"ipsum\"" << + endl; + + + +1. Namespaces; + +a. No "using namespace" declarations in header files. +b. All symbols should be declared in a namespace except for final applications. +c. Use anonymous namespaces for helpers whose scope is a cpp file only. +d. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore. + +(WRONG) +#include <cassert> +using namespace std; +tuple<float, float> meanAndSigma(vector<float> const& _v); + +(CORRECT) +#include <cassert> +std::tuple<float, float> meanAndSigma(std::vector<float> const& _v); + + + +2. Preprocessor; + +a. File comment is always at top, and includes: +- Copyright. +- License (e.g. see COPYING). +b. Never use #ifdef/#define/#endif file guards. Prefer #pragma once as first line below file comment. +c. Prefer static const variable to value macros. +d. Prefer inline constexpr functions to function macros. +e. Split complex macro on multiple lines with '\'. + + + +3. Capitalization; + +GOLDEN RULE: Preprocessor: ALL_CAPS; C++: camelCase. + +a. Use camelCase for splitting words in names, except where obviously extending STL/boost functionality in which case follow those naming conventions. +b. The following entities' first alpha is upper case: +- Type names. +- Template parameters. +- Enum members. +- static const variables that form an external API. +c. All preprocessor symbols (macros, macro arguments) in full uppercase with underscore word separation. + + +All other entities' first alpha is lower case. + + + +4. Variable prefixes: + +a. Leading underscore "_" to parameter names. +- Exception: "o_parameterName" when it is used exclusively for output. See 6(f). +- Exception: "io_parameterName" when it is used for both input and output. See 6(f). +b. Leading "g_" to global (non-const) variables. +c. Leading "s_" to static (non-const, non-global) variables. + + + +5. Assertions: + +- use `solAssert` and `solUnimplementedAssert` generously to check assumptions + that span across different parts of the code base, for example before dereferencing + a pointer. + + +6. Declarations: + +a. {Typename} + {qualifiers} + {name}. +b. Only one per line. +c. Associate */& with type, not variable (at ends with parser, but more readable, and safe if in conjunction with (b)). +d. Favour declarations close to use; don't habitually declare at top of scope ala C. +e. Pass non-trivial parameters as const reference, unless the data is to be copied into the function, then either pass by const reference or by value and use std::move. +f. If a function returns multiple values, use std::tuple (std::pair acceptable) or better introduce a struct type. Do not use */& arguments. +g. Use parameters of pointer type only if ``nullptr`` is a valid argument, use references otherwise. Often, ``boost::optional`` is better suited than a raw pointer. +h. Never use a macro where adequate non-preprocessor C++ can be written. +i. Only use ``auto`` if the type is very long and rather irrelevant. +j. Do not pass bools: prefer enumerations instead. +k. Prefer enum class to straight enum. +l. Always initialize POD variables, even if their value is overwritten later. + + +(WRONG) +const double d = 0; +int i, j; +char *s; +float meanAndSigma(std::vector<float> _v, float* _sigma, bool _approximate); +Derived* x(dynamic_cast<Derived*>(base)); +for (map<ComplexTypeOne, ComplexTypeTwo>::iterator i = l.begin(); i != l.end(); ++l) {} + + +(CORRECT) +enum class Accuracy +{ + Approximate, + Exact +}; +struct MeanSigma +{ + float mean; + float standardDeviation; +}; +double const d = 0; +int i; +int j; +char* s; +MeanAndSigma ms meanAndSigma(std::vector<float> const& _v, Accuracy _a); +Derived* x = dynamic_cast<Derived*>(base); +for (auto i = x->begin(); i != x->end(); ++i) {} + + +7. Structs & classes + +a. Structs to be used when all members public and no virtual functions. +- In this case, members should be named naturally and not prefixed with 'm_' +b. Classes to be used in all other circumstances. + + + +8. Members: + +a. One member per line only. +b. Private, non-static, non-const fields prefixed with m_. +c. Avoid public fields, except in structs. +d. Use override, final and const as much as possible. +e. No implementations with the class declaration, except: +- template or force-inline method (though prefer implementation at bottom of header file). +- one-line implementation (in which case include it in same line as declaration). +f. For a property 'foo' +- Member: m_foo; +- Getter: foo() [ also: for booleans, isFoo() ]; +- Setter: setFoo(); + + + +9. Naming + +a. Avoid unpronouncable names +b. Names should be shortened only if they are extremely common, but shortening should be generally avoided +c. Avoid prefixes of initials (e.g. do not use IMyInterface, CMyImplementation) +c. Find short, memorable & (at least semi-) descriptive names for commonly used classes or name-fragments. +- A dictionary and thesaurus are your friends. +- Spell correctly. +- Think carefully about the class's purpose. +- Imagine it as an isolated component to try to decontextualise it when considering its name. +- Don't be trapped into naming it (purely) in terms of its implementation. + + + +10. Type-definitions + +a. Prefer 'using' to 'typedef'. e.g. using ints = std::vector<int>; rather than typedef std::vector<int> ints; +b. Generally avoid shortening a standard form that already includes all important information: +- e.g. stick to shared_ptr<X> rather than shortening to ptr<X>. +c. Where there are exceptions to this (due to excessive use and clear meaning), note the change prominently and use it consistently. +- e.g. using Guard = std::lock_guard<std::mutex>; ///< Guard is used throughout the codebase since it is clear in meaning and used commonly. +d. In general expressions should be roughly as important/semantically meaningful as the space they occupy. +e. Avoid introducing aliases for types unless they are very complicated. Consider the number of items a brain can keep track of at the same time. + + + +11. Commenting + +a. Comments should be doxygen-compilable, using @notation rather than \notation. +b. Document the interface, not the implementation. +- Documentation should be able to remain completely unchanged, even if the method is reimplemented. +- Comment in terms of the method properties and intended alteration to class state (or what aspects of the state it reports). +- Be careful to scrutinise documentation that extends only to intended purpose and usage. +- Reject documentation that is simply an English transaction of the implementation. +c. Avoid in-code comments. Instead, try to extract blocks of functionality into functions. This often already eliminates the need for an in-code comment. + + +12. Include Headers + +Includes should go in increasing order of generality (libsolidity -> libevmasm -> libdevcore -> boost -> STL). +The corresponding .h file should be the first include in the respective .cpp file. +Insert empty lines between blocks of include files. + +Example: + +``` +#include <libsolidity/codegen/ExpressionCompiler.h> + +#include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/CompilerContext.h> +#include <libsolidity/codegen/CompilerUtils.h> +#include <libsolidity/codegen/LValue.h> + +#include <libevmasm/GasMeter.h> + +#include <libdevcore/Common.h> +#include <libdevcore/SHA3.h> + +#include <boost/range/adaptor/reversed.hpp> +#include <boost/algorithm/string/replace.hpp> + +#include <utility> +#include <numeric> +``` + +See http://stackoverflow.com/questions/614302/c-header-order/614333#614333 for the reason: this makes it easier to find missing includes in header files. + + +13. Recommended reading + +Herb Sutter and Bjarne Stroustrup +- "C++ Core Guidelines" (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) + +Herb Sutter and Andrei Alexandrescu +- "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices" + +Scott Meyers +- "Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)" +- "More Effective C++: 35 New Ways to Improve Your Programs and Designs" +- "Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14" diff --git a/Changelog.md b/Changelog.md index d35a2b45..d6860bdf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,17 @@ ### 0.4.22 (unreleased) Features: + * Code Generator: Initialize arrays without using ``msize()``. + * Code Generator: More specialized and thus optimized implementation for ``x.push(...)`` * Commandline interface: Error when missing or inaccessible file detected. Suppress it with the ``--ignore-missing`` flag. * General: Support accessing dynamic return data in post-byzantium EVMs. * Interfaces: Allow overriding external functions in interfaces with public in an implementing contract. + * Optimizer: Remove useless ``SWAP1`` instruction preceding a commutative instruction (such as ``ADD``, ``MUL``, etc). + * Optimizer: Replace comparison operators (``LT``, ``GT``, etc) with opposites if preceded by ``SWAP1``, e.g. ``SWAP1 LT`` is replaced with ``GT``. + * Optimizer: Optimize across ``mload`` if ``msize()`` is not used. * Syntax Checker: Issue warning for empty structs (or error as experimental 0.5.0 feature). + * General: Introduce new constructor syntax using the ``constructor`` keyword as experimental 0.5.0 feature. + * Inheritance: Error when using empty parenthesis for base class constructors that require arguments as experimental 0.5.0 feature. Bugfixes: * Code Generator: Allow ``block.blockhash`` without being called. @@ -12,9 +19,11 @@ Bugfixes: * Code Generator: Properly skip unneeded storage array cleanup when not reducing length. * Code Generator: Bugfix in modifier lookup in libraries. * Code Generator: Implement packed encoding of external function types. + * Code Generator: Treat empty base constructor argument list as not provided. * Commandline interface: Support ``--evm-version constantinople`` properly. * DocString Parser: Fix error message for empty descriptions. * Standard JSON: Support ``constantinople`` as ``evmVersion`` properly. + * Type Checker: Fix detection of recursive structs. * Type System: Improve error message when attempting to shift by a fractional amount. * Type System: Make external library functions accessible. * Type System: Prevent encoding of weird types. @@ -1,3 +1,10 @@ +defaults: + # The default for tags is to not run, so we have to explicitly match a filter. + - build_on_tags: &build_on_tags + filters: + tags: + only: /.*/ + version: 2 jobs: build_emscripten: @@ -157,15 +164,18 @@ workflows: version: 2 build_all: jobs: - - build_emscripten + - build_emscripten: *build_on_tags - test_emscripten_solcjs: + <<: *build_on_tags requires: - build_emscripten - test_emscripten_external: + <<: *build_on_tags requires: - build_emscripten - - build_x86 + - build_x86: *build_on_tags - test_x86: + <<: *build_on_tags requires: - build_x86 - - docs + - docs: *build_on_tags diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 4d84a7da..98301fdc 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -54,7 +54,7 @@ The following elementary types exist: - ``ufixed<M>x<N>``: unsigned variant of ``fixed<M>x<N>``. -- ``fixed``, ``ufixed``: synonyms for ``fixed128x19``, ``ufixed128x19`` respectively. For computing the function selector, ``fixed128x19`` and ``ufixed128x19`` have to be used. +- ``fixed``, ``ufixed``: synonyms for ``fixed128x18``, ``ufixed128x18`` respectively. For computing the function selector, ``fixed128x18`` and ``ufixed128x18`` have to be used. - ``bytes<M>``: binary type of ``M`` bytes, ``0 < M <= 32``. @@ -164,9 +164,9 @@ on the type of ``X`` being - ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is 32 bytes. - ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false`` - ``fixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``. -- ``fixed``: as in the ``fixed128x19`` case +- ``fixed``: as in the ``fixed128x18`` case - ``ufixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``uint256``. -- ``ufixed``: as in the ``ufixed128x19`` case +- ``ufixed``: as in the ``ufixed128x18`` case - ``bytes<M>``: ``enc(X)`` is the sequence of bytes in ``X`` padded with trailing zero-bytes to a length of 32 bytes. Note that for any ``X``, ``len(enc(X))`` is a multiple of 32. diff --git a/docs/assembly.rst b/docs/assembly.rst index cf9bf840..705cd1b8 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -647,6 +647,11 @@ 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. +The first 64 bytes of memory can be used as "scratch space" for short-term +allocation. The 32 bytes after the free memory pointer (i.e. starting at ``0x60``) +is meant to be zero permanently and is used as the initial value for +empty dynamic memory arrays. + 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 diff --git a/docs/contracts.rst b/docs/contracts.rst index 121c4de0..f8a44fb3 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -40,7 +40,7 @@ This means that cyclic creation dependencies are impossible. :: - pragma solidity ^0.4.16; + pragma solidity >0.4.21; contract OwnedToken { // TokenCreator is a contract type that is defined below. @@ -52,7 +52,7 @@ This means that cyclic creation dependencies are impossible. // This is the constructor which registers the // creator and the assigned name. - function OwnedToken(bytes32 _name) public { + constructor(bytes32 _name) public { // State variables are accessed via their name // and not via e.g. this.owner. This also applies // to functions and especially in the constructors, @@ -679,7 +679,7 @@ candidate, resolution fails. } } -Calling ``f(50)`` would create a type error since ``250`` can be implicitly converted both to ``uint8`` +Calling ``f(50)`` would create a type error since ``50`` can be implicitly converted both to ``uint8`` and ``uint256`` types. On another hand ``f(256)`` would resolve to ``f(uint256)`` overload as ``256`` cannot be implicitly converted to ``uint8``. @@ -803,7 +803,7 @@ as topics. The event call above can be performed in the same way as } where the long hexadecimal number is equal to -``keccak256("Deposit(address,hash256,uint256)")``, the signature of the event. +``keccak256("Deposit(address,bytes32,uint256)")``, the signature of the event. Additional Resources for Understanding Events ============================================== @@ -976,8 +976,31 @@ virtual method lookup. Constructors ============ -A constructor is an optional function with the same name as the contract which is executed upon contract creation. -Constructor functions can be either ``public`` or ``internal``. +A constructor is an optional function declared with the ``constructor`` keyword which is executed upon contract creation. +Constructor functions can be either ``public`` or ``internal``. If there is no constructor, the contract will assume the +default constructor: ``contructor() public {}``. + + +:: + + pragma solidity >0.4.21; + + contract A { + uint public a; + + constructor(uint _a) internal { + a = _a; + } + } + + contract B is A(1) { + constructor() public {} + } + +A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`. + +.. note :: + Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. This syntax is now deprecated. :: @@ -995,7 +1018,6 @@ Constructor functions can be either ``public`` or ``internal``. function B() public {} } -A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`. .. index:: ! base;constructor @@ -1009,11 +1031,11 @@ the base constructors. This can be done in two ways:: contract Base { uint x; - function Base(uint _x) public { x = _x; } + constructor(uint _x) public { x = _x; } } contract Derived is Base(7) { - function Derived(uint _y) Base(_y * _y) public { + constructor(uint _y) Base(_y * _y) public { } } diff --git a/docs/contributing.rst b/docs/contributing.rst index 1bcaed7c..6717a8b9 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -55,8 +55,8 @@ However, if you are making a larger change, please consult with the `Solidity De focused on compiler and language development instead of language use) first. -Finally, please make sure you respect the `coding standards -<https://raw.githubusercontent.com/ethereum/cpp-ethereum/develop/CodingStandards.txt>`_ +Finally, please make sure you respect the `coding style +<https://raw.githubusercontent.com/ethereum/solidity/develop/CODING_STYLE.md>`_ for this project. Also, even though we do CI testing, please test your code and ensure that it builds locally before submitting a pull request. @@ -170,6 +170,57 @@ and re-run the test. It will now pass again: Please choose a name for the contract file, that is self-explainatory in the sense of what is been tested, e.g. ``double_variable_declaration.sol``. Do not put more than one contract into a single file. ``isoltest`` is currently not able to recognize them individually. + +Running the Fuzzer via AFL +========================== + +Fuzzing is a technique that runs programs on more or less random inputs to find exceptional execution +states (segmentation faults, exceptions, etc). Modern fuzzers are clever and do a directed search +inside the input. We have a specialized binary called ``solfuzzer`` which takes source code as input +and fails whenever it encounters an internal compiler error, segmentation fault or similar, but +does not fail if e.g. the code contains an error. This way, internal problems in the compiler +can be found by fuzzing tools. + +We mainly use `AFL <http://lcamtuf.coredump.cx/afl/>`_ for fuzzing. You need to download and +build AFL manually. Next, build Solidity (or just the ``solfuzzer`` binary) with AFL as your compiler: + +:: + + cd build + # if needed + make clean + cmake .. -DCMAKE_C_COMPILER=path/to/afl-gcc -DCMAKE_CXX_COMPILER=path/to/afl-g++ + make solfuzzer + +Next, you need some example source files. This will make it much easer for the fuzzer +to find errors. You can either copy some files from the syntax tests or extract test files +from the documentation or the other tests: + +:: + + mkdir /tmp/test_cases + cd /tmp/test_cases + # extract from tests: + path/to/solidity/scripts/isolate_tests.py path/to/solidity/test/libsolidity/SolidityEndToEndTest.cpp + # extract from documentation: + path/to/solidity/scripts/isolate_tests.py path/to/solidity/docs docs + +The AFL documentation states that the corpus (the initial input files) should not be +too large. The files themselves should not be larger than 1 kB and there should be +at most one input file per functionality, so better start with a small number of +input files. There is also a tool called ``afl-cmin`` that can trim input files +that result in similar behaviour of the binary. + +Now run the fuzzer (the ``-m`` extends the size of memory to 60 MB): + +:: + + afl-fuzz -m 60 -i /tmp/test_cases -o /tmp/fuzzer_reports -- /path/to/solfuzzer + +The fuzzer will create source files that lead to failures in ``/tmp/fuzzer_reports``. +Often it finds many similar source files that produce the same error. You can +use the tool ``scripts/uniqueErrors.sh`` to filter out the unique errors. + Whiskers ======== diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index e26870f0..6726ded9 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -122,7 +122,6 @@ We will re-add the pre-built bottles soon. brew upgrade brew tap ethereum/ethereum brew install solidity - brew linkapps solidity If you need a specific version of Solidity you can install a Homebrew formula directly from Github. diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 56f0fe3e..84b1fff8 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -326,7 +326,13 @@ EVM bytecode and executed. The output of this execution is permanently stored as the code of the contract. This means that in order to create a contract, you do not send the actual code of the contract, but in fact code that -returns that code. +returns that code when executed. + +.. note:: + While a contract is being created, its code is still empty. + Because of that, you should not call back into the + contract under construction until its constructor has + finished executing. .. index:: ! gas, ! gas price diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 01154854..20400aa2 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -64,12 +64,15 @@ The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint25 Layout in Memory **************** -Solidity reserves three 256-bit slots: +Solidity reserves four 32 byte slots: -- 0 - 64: scratch space for hashing methods -- 64 - 96: currently allocated memory size (aka. free memory pointer) +- ``0x00`` - ``0x3f``: scratch space for hashing methods +- ``0x40`` - ``0x5f``: currently allocated memory size (aka. free memory pointer) +- ``0x60`` - ``0x7f``: zero slot -Scratch space can be used between statements (ie. within inline assembly). +Scratch space can be used between statements (ie. within inline assembly). The zero slot +is used as initial value for dynamic memory arrays and should never be written to +(the free memory pointer points to ``0x80`` initially). Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future). diff --git a/docs/types.rst b/docs/types.rst index e704687e..5de6d07e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -81,7 +81,7 @@ Fixed Point Numbers ``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represents the number of bits taken by the type and ``N`` represents how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive. -``ufixed`` and ``fixed`` are aliases for ``ufixed128x19`` and ``fixed128x19``, respectively. +``ufixed`` and ``fixed`` are aliases for ``ufixed128x18`` and ``fixed128x18``, respectively. Operators: diff --git a/libdevcore/Algorithms.h b/libdevcore/Algorithms.h new file mode 100644 index 00000000..b2540668 --- /dev/null +++ b/libdevcore/Algorithms.h @@ -0,0 +1,76 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +#pragma once + + +#include <functional> +#include <set> + +namespace dev +{ + +/** + * Detector for cycles in directed graphs. It returns the first + * vertex on the path towards a cycle or a nullptr if there is + * no reachable cycle starting from a given vertex. + */ +template <typename V> +class CycleDetector +{ +public: + /// Initializes the cycle detector + /// @param _visit function that is given the current vertex + /// and is supposed to call @a run on all + /// adjacent vertices. + explicit CycleDetector(std::function<void(V const&, CycleDetector&)> _visit): + m_visit(std::move(_visit)) + { } + + /// Recursively perform cycle detection starting + /// (or continuing) with @param _vertex + /// @returns the first vertex on the path towards a cycle from @a _vertex + /// or nullptr if no cycle is reachable from @a _vertex. + V const* run(V const& _vertex) + { + if (m_firstCycleVertex) + return m_firstCycleVertex; + if (m_processed.count(&_vertex)) + return nullptr; + else if (m_processing.count(&_vertex)) + return m_firstCycleVertex = &_vertex; + m_processing.insert(&_vertex); + + m_depth++; + m_visit(_vertex, *this); + m_depth--; + if (m_firstCycleVertex && m_depth == 1) + m_firstCycleVertex = &_vertex; + + m_processing.erase(&_vertex); + m_processed.insert(&_vertex); + return m_firstCycleVertex; + } + +private: + std::function<void(V const&, CycleDetector&)> m_visit; + std::set<V const*> m_processing; + std::set<V const*> m_processed; + size_t m_depth = 0; + V const* m_firstCycleVertex = nullptr; +}; + +} diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index bd4ebf59..b71bc80c 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -438,13 +438,15 @@ map<u256, u256> Assembly::optimiseInternal( // function types that can be stored in storage. AssemblyItems optimisedItems; + bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem(Instruction::MSIZE)) != m_items.end()); + auto iter = m_items.begin(); while (iter != m_items.end()) { KnownState emptyState; CommonSubexpressionEliminator eliminator(emptyState); auto orig = iter; - iter = eliminator.feedItems(iter, m_items.end()); + iter = eliminator.feedItems(iter, m_items.end(), usesMSize); bool shouldReplace = false; AssemblyItems optimisedChunk; try diff --git a/libevmasm/CommonSubexpressionEliminator.h b/libevmasm/CommonSubexpressionEliminator.h index 0b957a0e..b20de246 100644 --- a/libevmasm/CommonSubexpressionEliminator.h +++ b/libevmasm/CommonSubexpressionEliminator.h @@ -65,8 +65,9 @@ public: /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first /// item that must be fed into a new instance of the eliminator. + /// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect template <class _AssemblyItemIterator> - _AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end); + _AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant); /// @returns the resulting items after optimization. AssemblyItems getOptimizedItems(); @@ -168,11 +169,12 @@ private: template <class _AssemblyItemIterator> _AssemblyItemIterator CommonSubexpressionEliminator::feedItems( _AssemblyItemIterator _iterator, - _AssemblyItemIterator _end + _AssemblyItemIterator _end, + bool _msizeImportant ) { assertThrow(!m_breakingItem, OptimizerException, "Invalid use of CommonSubexpressionEliminator."); - for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator) + for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator, _msizeImportant); ++_iterator) feedItem(*_iterator); if (_iterator != _end) m_breakingItem = &(*_iterator++); diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index a677a631..f9bbad2c 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -199,7 +199,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo = { 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::KECCAK256, { "KECCAK256", 0, 2, 1, false, Tier::Special } }, + { Instruction::KECCAK256, { "KECCAK256", 0, 2, 1, true, Tier::Special } }, { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } }, { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } }, { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } }, diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 168d1109..8a39de24 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -154,6 +154,51 @@ struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2> } }; +struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap, 2> +{ + static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out) + { + // Remove SWAP1 if following instruction is commutative + if ( + _swap.type() == Operation && + _swap.instruction() == Instruction::SWAP1 && + SemanticInformation::isCommutativeOperation(_op) + ) + { + *_out = _op; + return true; + } + else + return false; + } +}; + +struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2> +{ + static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out) + { + map<Instruction, Instruction> swappableOps{ + { Instruction::LT, Instruction::GT }, + { Instruction::GT, Instruction::LT }, + { Instruction::SLT, Instruction::SGT }, + { Instruction::SGT, Instruction::SLT } + }; + + if ( + _swap.type() == Operation && + _swap.instruction() == Instruction::SWAP1 && + _op.type() == Operation && + swappableOps.count(_op.instruction()) + ) + { + *_out = swappableOps.at(_op.instruction()); + return true; + } + else + return false; + } +}; + struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3> { static size_t applySimple( @@ -260,7 +305,7 @@ bool PeepholeOptimiser::optimise() { OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; while (state.i < m_items.size()) - applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity()); + applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity()); if (m_optimisedItems.size() < m_items.size() || ( m_optimisedItems.size() == m_items.size() && ( eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3) || diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 03870f7c..71267ee8 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -28,7 +28,7 @@ using namespace std; using namespace dev; using namespace dev::eth; -bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item) +bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant) { switch (_item.type()) { @@ -59,6 +59,11 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item) return false; if (_item.instruction() == Instruction::MSTORE) return false; + if (!_msizeImportant && ( + _item.instruction() == Instruction::MLOAD || + _item.instruction() == Instruction::KECCAK256 + )) + return false; //@todo: We do not handle the following memory instructions for now: // calldatacopy, codecopy, extcodecopy, mstore8, // msize (note that msize also depends on memory read access) diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h index 83656252..8bdc70be 100644 --- a/libevmasm/SemanticInformation.h +++ b/libevmasm/SemanticInformation.h @@ -38,7 +38,8 @@ class AssemblyItem; struct SemanticInformation { /// @returns true if the given items starts a new block for common subexpression analysis. - static bool breaksCSEAnalysisBlock(AssemblyItem const& _item); + /// @param _msizeImportant if false, consider an operation non-breaking if its only side-effect is that it modifies msize. + static bool breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant); /// @returns true if the item is a two-argument operation whose value does not depend on the /// order of its arguments. static bool isCommutativeOperation(AssemblyItem const& _item); diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index fbc72e52..19d0b708 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -21,6 +21,8 @@ #include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/Version.h> +#include <libdevcore/Algorithms.h> + #include <boost/range/adaptor/map.hpp> #include <memory> @@ -47,7 +49,7 @@ void PostTypeChecker::endVisit(ContractDefinition const&) { solAssert(!m_currentConstVariable, ""); for (auto declaration: m_constVariables) - if (auto identifier = findCycle(declaration)) + if (auto identifier = findCycle(*declaration)) m_errorReporter.typeError( declaration->location(), "The value of the constant " + declaration->name() + @@ -87,20 +89,24 @@ bool PostTypeChecker::visit(Identifier const& _identifier) return true; } -VariableDeclaration const* PostTypeChecker::findCycle( - VariableDeclaration const* _startingFrom, - set<VariableDeclaration const*> const& _seen -) +VariableDeclaration const* PostTypeChecker::findCycle(VariableDeclaration const& _startingFrom) { - if (_seen.count(_startingFrom)) - return _startingFrom; - else if (m_constVariableDependencies.count(_startingFrom)) + auto visitor = [&](VariableDeclaration const& _variable, CycleDetector<VariableDeclaration>& _cycleDetector) { - set<VariableDeclaration const*> seen(_seen); - seen.insert(_startingFrom); - for (auto v: m_constVariableDependencies[_startingFrom]) - if (findCycle(v, seen)) - return v; - } - return nullptr; + // Iterating through the dependencies needs to be deterministic and thus cannot + // depend on the memory layout. + // Because of that, we sort by AST node id. + vector<VariableDeclaration const*> dependencies( + m_constVariableDependencies[&_variable].begin(), + m_constVariableDependencies[&_variable].end() + ); + sort(dependencies.begin(), dependencies.end(), [](VariableDeclaration const* _a, VariableDeclaration const* _b) -> bool + { + return _a->id() < _b->id(); + }); + for (auto v: dependencies) + if (_cycleDetector.run(*v)) + return; + }; + return CycleDetector<VariableDeclaration>(visitor).run(_startingFrom); } diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h index bafc1ae6..4f9dac6e 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -55,10 +55,7 @@ private: virtual bool visit(Identifier const& _identifier) override; - VariableDeclaration const* findCycle( - VariableDeclaration const* _startingFrom, - std::set<VariableDeclaration const*> const& _seen = std::set<VariableDeclaration const*>{} - ); + VariableDeclaration const* findCycle(VariableDeclaration const& _startingFrom); ErrorReporter& m_errorReporter; diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 6aee260e..d96f8748 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -50,6 +50,16 @@ void StaticAnalyzer::endVisit(ContractDefinition const&) bool StaticAnalyzer::visit(FunctionDefinition const& _function) { + const bool isInterface = m_currentContract->contractKind() == ContractDefinition::ContractKind::Interface; + + if (_function.noVisibilitySpecified()) + m_errorReporter.warning( + _function.location(), + "No visibility specified. Defaulting to \"" + + Declaration::visibilityToString(_function.visibility()) + + "\". " + + (isInterface ? "In interfaces it defaults to external." : "") + ); if (_function.isImplemented()) m_currentFunction = &_function; else diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 3a32810b..343b4ba8 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -214,16 +214,22 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function) { bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); - if (_function.noVisibilitySpecified()) + if (v050 && _function.noVisibilitySpecified()) + m_errorReporter.syntaxError(_function.location(), "No visibility specified."); + + if (_function.isOldStyleConstructor()) { if (v050) - m_errorReporter.syntaxError(_function.location(), "No visibility specified."); + m_errorReporter.syntaxError( + _function.location(), + "Functions are not allowed to have the same name as the contract. " + "If you intend this to be a constructor, use \"constructor(...) { ... }\" to define it." + ); else m_errorReporter.warning( _function.location(), - "No visibility specified. Defaulting to \"" + - Declaration::visibilityToString(_function.visibility()) + - "\"." + "Defining constructors as functions with the same name as the contract is deprecated. " + "Use \"constructor(...) { ... }\" instead." ); } return true; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 620dfca4..a252742d 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -320,7 +320,7 @@ void TypeChecker::checkContractAbstractConstructors(ContractDefinition const& _c { auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(base->name())); solAssert(baseContract, ""); - if (!base->arguments().empty()) + if (base->arguments() && !base->arguments()->empty()) argumentsNeeded.erase(baseContract); } } @@ -506,30 +506,46 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) // Interfaces do not have constructors, so there are zero parameters. parameterTypes = ContractType(*base).newExpressionType()->parameterTypes(); - if (!arguments.empty() && parameterTypes.size() != arguments.size()) + if (arguments) { - m_errorReporter.typeError( - _inheritance.location(), - "Wrong argument count for constructor call: " + - toString(arguments.size()) + - " arguments given but expected " + - toString(parameterTypes.size()) + - "." - ); - return; - } + bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); - for (size_t i = 0; i < arguments.size(); ++i) - if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) - m_errorReporter.typeError( - arguments[i]->location(), - "Invalid type for argument in constructor call. " - "Invalid implicit conversion from " + - type(*arguments[i])->toString() + - " to " + - parameterTypes[i]->toString() + - " requested." - ); + if (parameterTypes.size() != arguments->size()) + { + if (arguments->size() == 0 && !v050) + m_errorReporter.warning( + _inheritance.location(), + "Wrong argument count for constructor call: " + + toString(arguments->size()) + + " arguments given but expected " + + toString(parameterTypes.size()) + + "." + ); + else + { + m_errorReporter.typeError( + _inheritance.location(), + "Wrong argument count for constructor call: " + + toString(arguments->size()) + + " arguments given but expected " + + toString(parameterTypes.size()) + + "." + ); + return; + } + } + for (size_t i = 0; i < arguments->size(); ++i) + if (!type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) + m_errorReporter.typeError( + (*arguments)[i]->location(), + "Invalid type for argument in constructor call. " + "Invalid implicit conversion from " + + type(*(*arguments)[i])->toString() + + " to " + + parameterTypes[i]->toString() + + " requested." + ); + } } void TypeChecker::endVisit(UsingForDirective const& _usingFor) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 9c67d354..bc85349b 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -425,19 +425,22 @@ public: InheritanceSpecifier( SourceLocation const& _location, ASTPointer<UserDefinedTypeName> const& _baseName, - std::vector<ASTPointer<Expression>> _arguments + std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments ): - ASTNode(_location), m_baseName(_baseName), m_arguments(_arguments) {} + ASTNode(_location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; UserDefinedTypeName const& name() const { return *m_baseName; } - std::vector<ASTPointer<Expression>> const& arguments() const { return m_arguments; } + // Returns nullptr if no argument list was given (``C``). + // If an argument list is given (``C(...)``), the arguments are returned + // as a vector of expressions. Note that this vector can be empty (``C()``). + std::vector<ASTPointer<Expression>> const* arguments() const { return m_arguments.get(); } private: ASTPointer<UserDefinedTypeName> m_baseName; - std::vector<ASTPointer<Expression>> m_arguments; + std::unique_ptr<std::vector<ASTPointer<Expression>>> m_arguments; }; /** @@ -607,7 +610,8 @@ public: StateMutability stateMutability() const { return m_stateMutability; } bool isConstructor() const { return m_isConstructor; } - bool isFallback() const { return name().empty(); } + bool isOldStyleConstructor() const { return m_isConstructor && !name().empty(); } + bool isFallback() const { return !m_isConstructor && name().empty(); } bool isPayable() const { return m_stateMutability == StateMutability::Payable; } std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; } std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); } diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 4fef67c3..94932eca 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -268,7 +268,7 @@ bool ASTJsonConverter::visit(InheritanceSpecifier const& _node) { setJsonNode(_node, "InheritanceSpecifier", { make_pair("baseName", toJson(_node.name())), - make_pair("arguments", toJson(_node.arguments())) + make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::Value(Json::arrayValue)) }); return false; } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index 70ee997e..dac414fc 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -94,7 +94,8 @@ void InheritanceSpecifier::accept(ASTVisitor& _visitor) if (_visitor.visit(*this)) { m_baseName->accept(_visitor); - listAccept(m_arguments, _visitor); + if (m_arguments) + listAccept(*m_arguments, _visitor); } _visitor.endVisit(*this); } @@ -104,7 +105,8 @@ void InheritanceSpecifier::accept(ASTConstVisitor& _visitor) const if (_visitor.visit(*this)) { m_baseName->accept(_visitor); - listAccept(m_arguments, _visitor); + if (m_arguments) + listAccept(*m_arguments, _visitor); } _visitor.endVisit(*this); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 4c462d09..ac1d3b01 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -28,6 +28,7 @@ #include <libdevcore/CommonData.h> #include <libdevcore/SHA3.h> #include <libdevcore/UTF8.h> +#include <libdevcore/Algorithms.h> #include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/replace.hpp> @@ -208,9 +209,9 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) case Token::UInt: return make_shared<IntegerType>(256, IntegerType::Modifier::Unsigned); case Token::Fixed: - return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Signed); + return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Signed); case Token::UFixed: - return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Unsigned); + return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Unsigned); case Token::Byte: return make_shared<FixedBytesType>(1); case Token::Address: @@ -1971,25 +1972,19 @@ bool StructType::recursive() const { if (!m_recursive.is_initialized()) { - set<StructDefinition const*> structsSeen; - function<bool(StructType const*)> check = [&](StructType const* t) -> bool + auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector) { - StructDefinition const* str = &t->structDefinition(); - if (structsSeen.count(str)) - return true; - structsSeen.insert(str); - for (ASTPointer<VariableDeclaration> const& variable: str->members()) + for (ASTPointer<VariableDeclaration> const& variable: _struct.members()) { Type const* memberType = variable->annotation().type.get(); while (dynamic_cast<ArrayType const*>(memberType)) memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get(); if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType)) - if (check(innerStruct)) - return true; + if (_cycleDetector.run(innerStruct->structDefinition())) + return; } - return false; }; - m_recursive = check(this); + m_recursive = (CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr); } return *m_recursive; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index b7e64891..2c392705 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -229,6 +229,9 @@ public: /// i.e. it behaves differently in lvalue context and in value context. virtual bool isValueType() const { return false; } virtual unsigned sizeOnStack() const { return 1; } + /// If it is possible to initialize such a value in memory by just writing zeros + /// of the size memoryHeadSize(). + virtual bool hasSimpleZeroValueInMemory() const { return true; } /// @returns the mobile (in contrast to static) type corresponding to the given type. /// This returns the corresponding IntegerType or FixedPointType for RationalNumberType /// and the pointer type for storage reference types. @@ -568,6 +571,7 @@ public: virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); } virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } + virtual bool hasSimpleZeroValueInMemory() const override { return false; } /// Storage references can be pointers or bound references. In general, local variables are of /// pointer type, state variables are bound references. Assignments to pointers or deleting @@ -855,6 +859,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override; + virtual bool hasSimpleZeroValueInMemory() const override { return false; } virtual TypePointer mobileType() const override; /// Converts components to their temporary types and performs some wildcard matching. virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override; @@ -999,6 +1004,7 @@ public: virtual bool isValueType() const override { return true; } virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } virtual unsigned sizeOnStack() const override; + virtual bool hasSimpleZeroValueInMemory() const override { return false; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; @@ -1104,6 +1110,8 @@ public: return _inLibrary ? shared_from_this() : TypePointer(); } virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; } + /// Cannot be stored in memory, but just in case. + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } TypePointer const& keyType() const { return m_keyType; } TypePointer const& valueType() const { return m_valueType; } @@ -1132,6 +1140,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override; + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; @@ -1154,6 +1163,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override { return 0; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual std::string richIdentifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; @@ -1179,6 +1189,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual unsigned sizeOnStack() const override { return 0; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; @@ -1209,6 +1220,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual unsigned sizeOnStack() const override { return 0; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; @@ -1238,6 +1250,7 @@ public: virtual bool canLiveOutsideStorage() const override { return false; } virtual bool isValueType() const override { return true; } virtual unsigned sizeOnStack() const override { return 1; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual std::string toString(bool) const override { return "inaccessible dynamic type"; } virtual TypePointer decodingType() const override { return std::make_shared<IntegerType>(256); } }; diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 4703fc1f..0fe66d2d 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -774,6 +774,55 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const ); } +void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) 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."); + + if (_type.isByteArray()) + { + // We almost always just add 2 (length of byte arrays is shifted left by one) + // except for the case where we transition from a short byte array + // to a long byte array, there we have to copy. + // This happens if the length is exactly 31, which means that the + // lowest-order byte (we actually use a mask with fewer bits) must + // be (31*2+0) = 62 + + m_context.appendInlineAssembly(R"({ + let data := sload(ref) + let shifted_length := and(data, 63) + // We have to copy if length is exactly 31, because that marks + // the transition between in-place and out-of-place storage. + switch shifted_length + case 62 + { + mstore(0, ref) + let data_area := keccak256(0, 0x20) + sstore(data_area, and(data, not(0xff))) + // New length is 32, encoded as (32 * 2 + 1) + sstore(ref, 65) + // Replace ref variable by new length + ref := 32 + } + default + { + sstore(ref, add(data, 2)) + // Replace ref variable by new length + if iszero(and(data, 1)) { data := shifted_length } + ref := add(div(data, 2), 1) + } + })", {"ref"}); + } + else + m_context.appendInlineAssembly(R"({ + let new_length := add(sload(ref), 1) + sstore(ref, new_length) + ref := new_length + })", {"ref"}); +} + void ArrayUtils::clearStorageLoop(TypePointer const& _type) const { m_context.callLowLevelFunction( diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index f3ddc4ee..99786397 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -67,6 +67,12 @@ public: /// Stack pre: reference (excludes byte offset) new_length /// Stack post: void resizeDynamicArray(ArrayType const& _type) const; + /// Increments the size of a dynamic array by one. + /// Does not touch the new data element. In case of a byte array, this might move the + /// data. + /// Stack pre: reference (excludes byte offset) + /// Stack post: new_length + void incrementDynamicArraySize(ArrayType const& _type) const; /// 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 diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 676d5d4e..79aef7b0 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -21,6 +21,7 @@ */ #include <libsolidity/codegen/CompilerUtils.h> + #include <libsolidity/ast/AST.h> #include <libsolidity/codegen/ArrayUtils.h> #include <libsolidity/codegen/LValue.h> @@ -39,11 +40,17 @@ namespace solidity const unsigned CompilerUtils::dataStartOffset = 4; const size_t CompilerUtils::freeMemoryPointer = 64; +const size_t CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32; +const size_t CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32; const unsigned CompilerUtils::identityContractAddress = 4; +static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area."); +static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer."); +static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPointer + 32, "General purpose memory must not overlap with zero area."); + void CompilerUtils::initialiseFreeMemoryPointer() { - m_context << u256(freeMemoryPointer + 32); + m_context << u256(generalPurposeMemoryStart); storeFreeMemoryPointer(); } @@ -495,14 +502,34 @@ void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromM void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) { - auto repeat = m_context.newTag(); - m_context << repeat; - pushZeroValue(*_type.baseType()); - storeInMemoryDynamic(*_type.baseType()); - m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1; - m_context << Instruction::SUB << Instruction::SWAP1; - m_context << Instruction::DUP2; - m_context.appendConditionalJumpTo(repeat); + if (_type.baseType()->hasSimpleZeroValueInMemory()) + { + solAssert(_type.baseType()->isValueType(), ""); + Whiskers templ(R"({ + let size := mul(length, <element_size>) + // cheap way of zero-initializing a memory range + codecopy(memptr, codesize(), size) + memptr := add(memptr, size) + })"); + templ("element_size", to_string(_type.baseType()->memoryHeadSize())); + m_context.appendInlineAssembly(templ.render(), {"length", "memptr"}); + } + else + { + // TODO: Potential optimization: + // When we create a new multi-dimensional dynamic array, each element + // is initialized to an empty array. It actually does not hurt + // to re-use exactly the same empty array for all elements. Currently, + // a new one is created each time. + auto repeat = m_context.newTag(); + m_context << repeat; + pushZeroValue(*_type.baseType()); + storeInMemoryDynamic(*_type.baseType()); + m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1; + m_context << Instruction::SUB << Instruction::SWAP1; + m_context << Instruction::DUP2; + m_context.appendConditionalJumpTo(repeat); + } m_context << Instruction::SWAP1 << Instruction::POP; } @@ -1031,6 +1058,13 @@ void CompilerUtils::pushZeroValue(Type const& _type) return; } solAssert(referenceType->location() == DataLocation::Memory, ""); + if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) + if (arrayType->isDynamicallySized()) + { + // Push a memory location that is (hopefully) always zero. + pushZeroPointer(); + return; + } TypePointer type = _type.shared_from_this(); m_context.callLowLevelFunction( @@ -1051,13 +1085,8 @@ void CompilerUtils::pushZeroValue(Type const& _type) } else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get())) { - if (arrayType->isDynamicallySized()) - { - // zero length - _context << u256(0); - utils.storeInMemoryDynamic(IntegerType(256)); - } - else if (arrayType->length() > 0) + solAssert(!arrayType->isDynamicallySized(), ""); + if (arrayType->length() > 0) { _context << arrayType->length() << Instruction::SWAP1; // stack: items_to_do memory_pos @@ -1074,6 +1103,11 @@ void CompilerUtils::pushZeroValue(Type const& _type) ); } +void CompilerUtils::pushZeroPointer() +{ + m_context << u256(zeroPointer); +} + void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) { unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable)); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 389673ef..a32c5c6e 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -210,6 +210,9 @@ public: /// Creates a zero-value for the given type and puts it onto the stack. This might allocate /// memory for memory references. void pushZeroValue(Type const& _type); + /// Pushes a pointer to the stack that points to a (potentially shared) location in memory + /// that always contains a zero. It is not allowed to write there. + void pushZeroPointer(); /// Moves the value that is at the top of the stack to a stack variable. void moveToStackVariable(VariableDeclaration const& _variable); @@ -255,6 +258,10 @@ public: /// Position of the free-memory-pointer in memory; static const size_t freeMemoryPointer; + /// Position of the memory slot that is always zero. + static const size_t zeroPointer; + /// Starting offset for memory available to the user (aka the contract). + static const size_t generalPurposeMemoryStart; private: /// Address of the precompiled identity contract. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 791edc65..d3a7e4ea 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -143,8 +143,9 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c for (auto const& modifier: constructor->modifiers()) { auto baseContract = dynamic_cast<ContractDefinition const*>( - modifier->name()->annotation().referencedDeclaration); - if (baseContract) + modifier->name()->annotation().referencedDeclaration + ); + if (baseContract && !modifier->arguments().empty()) if (m_baseArguments.count(baseContract->constructor()) == 0) m_baseArguments[baseContract->constructor()] = &modifier->arguments(); } @@ -156,8 +157,8 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c ); solAssert(baseContract, ""); - if (m_baseArguments.count(baseContract->constructor()) == 0) - m_baseArguments[baseContract->constructor()] = &base->arguments(); + if (!m_baseArguments.count(baseContract->constructor()) && base->arguments() && !base->arguments()->empty()) + m_baseArguments[baseContract->constructor()] = base->arguments(); } } // Initialization of state variables in base-to-derived order. @@ -238,6 +239,7 @@ void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _construc solAssert(m_baseArguments.count(&_constructor), ""); std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor]; solAssert(arguments, ""); + solAssert(arguments->size() == constructorType.parameterTypes().size(), ""); for (unsigned i = 0; i < arguments->size(); ++i) compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]); } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 9e2d30d5..57d49ac6 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -821,24 +821,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) function.kind() == FunctionType::Kind::ArrayPush ? make_shared<ArrayType>(DataLocation::Storage, paramType) : make_shared<ArrayType>(DataLocation::Storage); - // get the current length - ArrayUtils(m_context).retrieveLength(*arrayType); - m_context << Instruction::DUP1; - // stack: ArrayReference currentLength currentLength - m_context << u256(1) << Instruction::ADD; - // stack: ArrayReference currentLength newLength - m_context << Instruction::DUP3 << Instruction::DUP2; - ArrayUtils(m_context).resizeDynamicArray(*arrayType); - m_context << Instruction::SWAP2 << Instruction::SWAP1; - // stack: newLength ArrayReference oldLength - ArrayUtils(m_context).accessIndex(*arrayType, false); - // stack: newLength storageSlot slotOffset + // stack: ArrayReference arguments[0]->accept(*this); + TypePointer const& argType = arguments[0]->annotation().type; + // stack: ArrayReference argValue + utils().moveToStackTop(argType->sizeOnStack(), 1); + // stack: argValue ArrayReference + m_context << Instruction::DUP1; + ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); + // stack: argValue ArrayReference newLength + m_context << Instruction::SWAP1; + // stack: argValue newLength ArrayReference + m_context << u256(1) << Instruction::DUP3 << Instruction::SUB; + // stack: argValue newLength ArrayReference (newLength-1) + ArrayUtils(m_context).accessIndex(*arrayType, false); + // stack: argValue newLength storageSlot slotOffset + utils().moveToStackTop(3, argType->sizeOnStack()); // stack: newLength storageSlot slotOffset argValue TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); solAssert(type, ""); - utils().convertType(*arguments[0]->annotation().type, *type); + utils().convertType(*argType, *type); utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack()); // stack: newLength argValue storageSlot slotOffset @@ -850,8 +853,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::ObjectCreation: { - // Will allocate at the end of memory (MSIZE) and not write at all unless the base - // type is dynamically sized. ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type); _functionCall.expression().accept(*this); solAssert(arguments.size() == 1, ""); @@ -861,15 +862,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().convertType(*arguments[0]->annotation().type, IntegerType(256)); // Stack: requested_length - // Allocate at max(MSIZE, freeMemoryPointer) utils().fetchFreeMemoryPointer(); - m_context << Instruction::DUP1 << Instruction::MSIZE; - m_context << Instruction::LT; - auto initialise = m_context.appendConditionalJump(); - // Free memory pointer does not point to empty memory, use MSIZE. - m_context << Instruction::POP; - m_context << Instruction::MSIZE; - m_context << initialise; // Stack: requested_length memptr m_context << Instruction::SWAP1; @@ -894,13 +887,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // Check if length is zero m_context << Instruction::DUP1 << Instruction::ISZERO; auto skipInit = m_context.appendConditionalJump(); - - // We only have to initialise if the base type is a not a value type. - if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get())) - { - m_context << Instruction::DUP2 << u256(32) << Instruction::ADD; - utils().zeroInitialiseMemoryArray(arrayType); - } + // Always initialize because the free memory pointer might point at + // a dirty memory area. + m_context << Instruction::DUP2 << u256(32) << Instruction::ADD; + utils().zeroInitialiseMemoryArray(arrayType); m_context << skipInit; m_context << Instruction::POP; break; diff --git a/libsolidity/formal/SymbolicBoolVariable.cpp b/libsolidity/formal/SymbolicBoolVariable.cpp index e5c56e46..5cf22d7d 100644 --- a/libsolidity/formal/SymbolicBoolVariable.cpp +++ b/libsolidity/formal/SymbolicBoolVariable.cpp @@ -30,7 +30,11 @@ SymbolicBoolVariable::SymbolicBoolVariable( SymbolicVariable(_decl, _interface) { solAssert(m_declaration.type()->category() == Type::Category::Bool, ""); - m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Bool)); +} + +smt::Expression SymbolicBoolVariable::valueAtSequence(int _seq) const +{ + return m_interface.newBool(uniqueSymbol(_seq)); } void SymbolicBoolVariable::setZeroValue(int _seq) diff --git a/libsolidity/formal/SymbolicBoolVariable.h b/libsolidity/formal/SymbolicBoolVariable.h index 3510b770..678f97d9 100644 --- a/libsolidity/formal/SymbolicBoolVariable.h +++ b/libsolidity/formal/SymbolicBoolVariable.h @@ -41,6 +41,9 @@ public: void setZeroValue(int _seq); /// Does nothing since the SMT solver already knows the valid values. void setUnknownValue(int _seq); + +protected: + smt::Expression valueAtSequence(int _seq) const; }; } diff --git a/libsolidity/formal/SymbolicIntVariable.cpp b/libsolidity/formal/SymbolicIntVariable.cpp index eb7b1c17..5e71fdcc 100644 --- a/libsolidity/formal/SymbolicIntVariable.cpp +++ b/libsolidity/formal/SymbolicIntVariable.cpp @@ -30,7 +30,11 @@ SymbolicIntVariable::SymbolicIntVariable( SymbolicVariable(_decl, _interface) { solAssert(m_declaration.type()->category() == Type::Category::Integer, ""); - m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Int)); +} + +smt::Expression SymbolicIntVariable::valueAtSequence(int _seq) const +{ + return m_interface.newInteger(uniqueSymbol(_seq)); } void SymbolicIntVariable::setZeroValue(int _seq) diff --git a/libsolidity/formal/SymbolicIntVariable.h b/libsolidity/formal/SymbolicIntVariable.h index eb36b899..d591e8db 100644 --- a/libsolidity/formal/SymbolicIntVariable.h +++ b/libsolidity/formal/SymbolicIntVariable.h @@ -44,6 +44,9 @@ public: static smt::Expression minValue(IntegerType const& _t); static smt::Expression maxValue(IntegerType const& _t); + +protected: + smt::Expression valueAtSequence(int _seq) const; }; } diff --git a/libsolidity/formal/SymbolicVariable.cpp b/libsolidity/formal/SymbolicVariable.cpp index d59b55b1..caefa3a3 100644 --- a/libsolidity/formal/SymbolicVariable.cpp +++ b/libsolidity/formal/SymbolicVariable.cpp @@ -32,9 +32,9 @@ SymbolicVariable::SymbolicVariable( { } -string SymbolicVariable::uniqueSymbol() const +string SymbolicVariable::uniqueSymbol(int _seq) const { - return m_declaration.name() + "_" + to_string(m_declaration.id()); + return m_declaration.name() + "_" + to_string(m_declaration.id()) + "_" + to_string(_seq); } diff --git a/libsolidity/formal/SymbolicVariable.h b/libsolidity/formal/SymbolicVariable.h index 75eb9fa5..e4e4ea8d 100644 --- a/libsolidity/formal/SymbolicVariable.h +++ b/libsolidity/formal/SymbolicVariable.h @@ -46,7 +46,7 @@ public: return valueAtSequence(_seq); } - std::string uniqueSymbol() const; + std::string uniqueSymbol(int _seq) const; /// Sets the var to the default value of its type. virtual void setZeroValue(int _seq) = 0; @@ -55,13 +55,9 @@ public: virtual void setUnknownValue(int _seq) = 0; protected: - smt::Expression valueAtSequence(int _seq) const - { - return (*m_expression)(_seq); - } + virtual smt::Expression valueAtSequence(int _seq) const = 0; Declaration const& m_declaration; - std::shared_ptr<smt::Expression> m_expression = nullptr; smt::SolverInterface& m_interface; }; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 8c97f55f..9a7731d8 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -238,7 +238,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp Token::Value currentTokenValue = m_scanner->currentToken(); if (currentTokenValue == Token::RBrace) break; - else if (currentTokenValue == Token::Function) + else if ( + currentTokenValue == Token::Function || + (currentTokenValue == Token::Identifier && m_scanner->currentLiteral() == "constructor") + ) // This can be a function or a state variable of function type (especially // complicated to distinguish fallback function from function type state variable) subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get())); @@ -283,17 +286,17 @@ ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier() RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<UserDefinedTypeName> name(parseUserDefinedTypeName()); - vector<ASTPointer<Expression>> arguments; + unique_ptr<vector<ASTPointer<Expression>>> arguments; if (m_scanner->currentToken() == Token::LParen) { m_scanner->next(); - arguments = parseFunctionCallListArguments(); + arguments.reset(new vector<ASTPointer<Expression>>(parseFunctionCallListArguments())); nodeFactory.markEndPosition(); expectToken(Token::RParen); } else nodeFactory.setEndPositionFromNode(name); - return nodeFactory.createNode<InheritanceSpecifier>(name, arguments); + return nodeFactory.createNode<InheritanceSpecifier>(name, std::move(arguments)); } Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token) @@ -329,15 +332,31 @@ StateMutability Parser::parseStateMutability(Token::Value _token) return stateMutability; } -Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) +Parser::FunctionHeaderParserResult Parser::parseFunctionHeader( + bool _forceEmptyName, + bool _allowModifiers, + ASTString const* _contractName +) { RecursionGuard recursionGuard(*this); FunctionHeaderParserResult result; - expectToken(Token::Function); - if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) - result.name = make_shared<ASTString>(); // anonymous function + + result.isConstructor = false; + + if (m_scanner->currentToken() == Token::Identifier && m_scanner->currentLiteral() == "constructor") + result.isConstructor = true; + else if (m_scanner->currentToken() != Token::Function) + solAssert(false, "Function or constructor expected."); + m_scanner->next(); + + if (result.isConstructor || _forceEmptyName || m_scanner->currentToken() == Token::LParen) + result.name = make_shared<ASTString>(); else result.name = expectIdentifierToken(); + + if (!result.name->empty() && _contractName && *result.name == *_contractName) + result.isConstructor = true; + VarDeclParserOptions options; options.allowLocationSpecifier = true; result.parameters = parseParameterList(options); @@ -407,7 +426,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A if (m_scanner->currentCommentLiteral() != "") docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral()); - FunctionHeaderParserResult header = parseFunctionHeader(false, true); + FunctionHeaderParserResult header = parseFunctionHeader(false, true, _contractName); if ( !header.modifiers.empty() || @@ -426,12 +445,11 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A } else m_scanner->next(); // just consume the ';' - bool const c_isConstructor = (_contractName && *header.name == *_contractName); return nodeFactory.createNode<FunctionDefinition>( header.name, header.visibility, header.stateMutability, - c_isConstructor, + header.isConstructor, docstring, header.parameters, header.modifiers, diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 3f780af9..eb120a61 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -56,6 +56,7 @@ private: /// This struct is shared for parsing a function header and a function type. struct FunctionHeaderParserResult { + bool isConstructor; ASTPointer<ASTString> name; ASTPointer<ParameterList> parameters; ASTPointer<ParameterList> returnParameters; @@ -73,7 +74,11 @@ private: ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); StateMutability parseStateMutability(Token::Value _token); - FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); + FunctionHeaderParserResult parseFunctionHeader( + bool _forceEmptyName, + bool _allowModifiers, + ASTString const* _contractName = nullptr + ); ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); ASTPointer<StructDefinition> parseStructDefinition(); diff --git a/scripts/cpp-ethereum/build.sh b/scripts/cpp-ethereum/build.sh new file mode 100755 index 00000000..23ed1290 --- /dev/null +++ b/scripts/cpp-ethereum/build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +# Script to build the eth binary from latest develop +# for ubuntu trusty and ubuntu artful. +# Requires docker. + +set -e + +REPO_ROOT="$(dirname "$0")"/../.. + +for rel in artful trusty +do + docker build -t eth_$rel -f "$REPO_ROOT"/scripts/cpp-ethereum/eth_$rel.docker . + tmp_container=$(docker create eth_$rel sh) + echo "Built eth ($rel) at $REPO_ROOT/build/eth_$rel" + docker cp ${tmp_container}:/build/eth/eth "$REPO_ROOT"/build/eth_$rel +done
\ No newline at end of file diff --git a/scripts/cpp-ethereum/eth_artful.docker b/scripts/cpp-ethereum/eth_artful.docker new file mode 100644 index 00000000..7ce9faae --- /dev/null +++ b/scripts/cpp-ethereum/eth_artful.docker @@ -0,0 +1,7 @@ +FROM ubuntu:artful + +RUN apt update +RUN apt -y install libleveldb-dev cmake g++ git +RUN git clone --recursive https://github.com/ethereum/cpp-ethereum --branch develop --single-branch --depth 1 +RUN mkdir /build && cd /build && cmake /cpp-ethereum -DCMAKE_BUILD_TYPE=RelWithDebInfo -DTOOLS=Off -DTESTS=Off +RUN cd /build && make eth diff --git a/scripts/cpp-ethereum/eth_trusty.docker b/scripts/cpp-ethereum/eth_trusty.docker new file mode 100644 index 00000000..0c4d7a02 --- /dev/null +++ b/scripts/cpp-ethereum/eth_trusty.docker @@ -0,0 +1,7 @@ +FROM ubuntu:trusty + +RUN apt update +RUN apt -y install libleveldb-dev cmake g++ git +RUN git clone --recursive https://github.com/ethereum/cpp-ethereum --branch develop --single-branch --depth 1 +RUN mkdir /build && cd /build && cmake /cpp-ethereum -DCMAKE_BUILD_TYPE=RelWithDebInfo -DTOOLS=Off -DTESTS=Off +RUN cd /build && make eth diff --git a/std/StandardToken.sol b/std/StandardToken.sol index 1b218d67..5afc9747 100644 --- a/std/StandardToken.sol +++ b/std/StandardToken.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.0; +pragma solidity >0.4.21; import "./Token.sol"; @@ -8,7 +8,7 @@ contract StandardToken is Token { mapping (address => mapping (address => uint256)) m_allowance; - function StandardToken(address _initialOwner, uint256 _supply) public { + constructor(address _initialOwner, uint256 _supply) public { supply = _supply; balance[_initialOwner] = _supply; } diff --git a/std/owned.sol b/std/owned.sol index ee9860d3..8e1d5917 100644 --- a/std/owned.sol +++ b/std/owned.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.0; +pragma solidity >0.4.21; contract owned { address owner; @@ -9,7 +9,7 @@ contract owned { } } - function owned() public { + constructor() public { owner = msg.sender; } } diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 03b1341c..f4eae865 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -226,6 +226,8 @@ void RPCSession::test_setChainParams(vector<string> const& _accounts) forks += "\"EIP158ForkBlock\": \"0x00\",\n"; if (test::Options::get().evmVersion() >= solidity::EVMVersion::byzantium()) forks += "\"byzantiumForkBlock\": \"0x00\",\n"; + if (test::Options::get().evmVersion() >= solidity::EVMVersion::constantinople()) + forks += "\"constantinopleForkBlock\": \"0x00\",\n"; static string const c_configString = R"( { "sealEngine": "NoProof", @@ -337,7 +339,9 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector<string> const& BOOST_TEST_MESSAGE("Reply: " + reply); Json::Value result; - BOOST_REQUIRE(jsonParseStrict(reply, result)); + string errorMsg; + if (!jsonParseStrict(reply, result, &errorMsg)) + BOOST_REQUIRE_MESSAGE(false, errorMsg); if (result.isMember("error")) { diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index f630b304..089be45d 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -69,8 +69,9 @@ namespace { AssemblyItems input = addDummyLocations(_input); + bool usesMsize = (find(_input.begin(), _input.end(), AssemblyItem{Instruction::MSIZE}) != _input.end()); eth::CommonSubexpressionEliminator cse(_state); - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); + BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end()); AssemblyItems output = cse.getOptimizedItems(); for (AssemblyItem const& item: output) @@ -124,7 +125,7 @@ BOOST_AUTO_TEST_CASE(cse_intermediate_swap) Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1, Instruction::DIV, u256(0xff), Instruction::AND }; - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); + BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.end()); AssemblyItems output = cse.getOptimizedItems(); BOOST_CHECK(!output.empty()); } @@ -857,6 +858,115 @@ BOOST_AUTO_TEST_CASE(peephole_pop_calldatasize) BOOST_CHECK(items.empty()); } +BOOST_AUTO_TEST_CASE(peephole_commutative_swap1) +{ + vector<Instruction> ops{ + Instruction::ADD, + Instruction::MUL, + Instruction::EQ, + Instruction::AND, + Instruction::OR, + Instruction::XOR + }; + for (Instruction const op: ops) + { + AssemblyItems items{ + u256(1), + u256(2), + Instruction::SWAP1, + op, + u256(4), + u256(5) + }; + AssemblyItems expectation{ + u256(1), + u256(2), + op, + u256(4), + u256(5) + }; + PeepholeOptimiser peepOpt(items); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); + } +} + +BOOST_AUTO_TEST_CASE(peephole_noncommutative_swap1) +{ + // NOTE: not comprehensive + vector<Instruction> ops{ + Instruction::SUB, + Instruction::DIV, + Instruction::SDIV, + Instruction::MOD, + Instruction::SMOD, + Instruction::EXP + }; + for (Instruction const op: ops) + { + AssemblyItems items{ + u256(1), + u256(2), + Instruction::SWAP1, + op, + u256(4), + u256(5) + }; + AssemblyItems expectation{ + u256(1), + u256(2), + Instruction::SWAP1, + op, + u256(4), + u256(5) + }; + PeepholeOptimiser peepOpt(items); + BOOST_REQUIRE(!peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); + } +} + +BOOST_AUTO_TEST_CASE(peephole_swap_comparison) +{ + map<Instruction, Instruction> swappableOps{ + { Instruction::LT, Instruction::GT }, + { Instruction::GT, Instruction::LT }, + { Instruction::SLT, Instruction::SGT }, + { Instruction::SGT, Instruction::SLT } + }; + + for (auto const& op: swappableOps) + { + AssemblyItems items{ + u256(1), + u256(2), + Instruction::SWAP1, + op.first, + u256(4), + u256(5) + }; + AssemblyItems expectation{ + u256(1), + u256(2), + op.second, + u256(4), + u256(5) + }; + PeepholeOptimiser peepOpt(items); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); + } +} + BOOST_AUTO_TEST_CASE(jumpdest_removal) { AssemblyItems items{ diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index 4538757d..72b86767 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -56,12 +56,23 @@ AnalysisFramework::parseAnalyseAndReturnError( m_compiler.analyze(); + ErrorList errors = filterErrors(m_compiler.errors(), _reportWarnings); + if (errors.size() > 1 && !_allowMultipleErrors) + BOOST_FAIL("Multiple errors found: " + formatErrors()); + + return make_pair(&m_compiler.ast(""), std::move(errors)); +} + +ErrorList AnalysisFramework::filterErrors(ErrorList const& _errorList, bool _includeWarnings) const +{ ErrorList errors; - for (auto const& currentError: m_compiler.errors()) + for (auto const& currentError: _errorList) { solAssert(currentError->comment(), ""); if (currentError->type() == Error::Type::Warning) { + if (!_includeWarnings) + continue; bool ignoreWarning = false; for (auto const& filter: m_warningsToFilter) if (currentError->comment()->find(filter) == 0) @@ -73,17 +84,10 @@ AnalysisFramework::parseAnalyseAndReturnError( continue; } - if (_reportWarnings || (currentError->type() != Error::Type::Warning)) - { - if (!_allowMultipleErrors && !errors.empty()) - { - BOOST_FAIL("Multiple errors found: " + formatErrors()); - } - errors.emplace_back(std::move(currentError)); - } + errors.emplace_back(currentError); } - return make_pair(&m_compiler.ast(""), errors); + return errors; } SourceUnit const* AnalysisFramework::parseAndAnalyse(string const& _source) @@ -110,7 +114,7 @@ ErrorList AnalysisFramework::expectError(std::string const& _source, bool _warni return sourceAndErrors.second; } -string AnalysisFramework::formatErrors() +string AnalysisFramework::formatErrors() const { string message; for (auto const& error: m_compiler.errors()) @@ -118,7 +122,7 @@ string AnalysisFramework::formatErrors() return message; } -string AnalysisFramework::formatError(Error const& _error) +string AnalysisFramework::formatError(Error const& _error) const { return SourceReferenceFormatter::formatExceptionInformation( _error, diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h index 6ecf4a5a..05490a42 100644 --- a/test/libsolidity/AnalysisFramework.h +++ b/test/libsolidity/AnalysisFramework.h @@ -57,8 +57,8 @@ protected: bool success(std::string const& _source); ErrorList expectError(std::string const& _source, bool _warning = false, bool _allowMultiple = false); - std::string formatErrors(); - std::string formatError(Error const& _error); + std::string formatErrors() const; + std::string formatError(Error const& _error) const; static ContractDefinition const* retrieveContractByName(SourceUnit const& _source, std::string const& _name); static FunctionTypePointer retrieveFunctionBySignature( @@ -66,6 +66,9 @@ protected: std::string const& _signature ); + // filter out the warnings in m_warningsToFilter or all warnings if _includeWarnings is false + ErrorList filterErrors(ErrorList const& _errorList, bool _includeWarnings) const; + std::vector<std::string> m_warningsToFilter = {"This is a pre-release compiler version"}; dev::solidity::CompilerStack m_compiler; }; diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp index aed0a370..cdcc22a6 100644 --- a/test/libsolidity/JSONCompiler.cpp +++ b/test/libsolidity/JSONCompiler.cpp @@ -111,12 +111,12 @@ BOOST_AUTO_TEST_CASE(basic_compilation) BOOST_CHECK(contract["bytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), - "60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00" + "60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00" ); BOOST_CHECK(contract["runtimeBytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()), - "6060604052600080fd00" + "6080604052600080fd00" ); BOOST_CHECK(contract["functionHashes"].isObject()); BOOST_CHECK(contract["gasEstimates"].isObject()); @@ -153,12 +153,12 @@ BOOST_AUTO_TEST_CASE(single_compilation) BOOST_CHECK(contract["bytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), - "60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00" + "60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00" ); BOOST_CHECK(contract["runtimeBytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()), - "6060604052600080fd00" + "6080604052600080fd00" ); BOOST_CHECK(contract["functionHashes"].isObject()); BOOST_CHECK(contract["gasEstimates"].isObject()); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index f5813aed..beeae786 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4884,6 +4884,48 @@ BOOST_AUTO_TEST_CASE(array_push) ABI_CHECK(callContractFunction("test()"), encodeArgs(5, 4, 3, 3)); } +BOOST_AUTO_TEST_CASE(array_push_struct) +{ + char const* sourceCode = R"( + contract c { + struct S { uint16 a; uint16 b; uint16[3] c; uint16[] d; } + S[] data; + function test() returns (uint16, uint16, uint16, uint16) { + S memory s; + s.a = 2; + s.b = 3; + s.c[2] = 4; + s.d = new uint16[](4); + s.d[2] = 5; + data.push(s); + return (data[0].a, data[0].b, data[0].c[2], data[0].d[2]); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("test()"), encodeArgs(2, 3, 4, 5)); +} + +BOOST_AUTO_TEST_CASE(array_push_packed_array) +{ + char const* sourceCode = R"( + contract c { + uint80[] x; + function test() returns (uint80, uint80, uint80, uint80) { + x.push(1); + x.push(2); + x.push(3); + x.push(4); + x.push(5); + x.length = 4; + return (x[0], x[1], x[2], x[3]); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("test()"), encodeArgs(1, 2, 3, 4)); +} + BOOST_AUTO_TEST_CASE(byte_array_push) { char const* sourceCode = R"( @@ -4904,6 +4946,29 @@ BOOST_AUTO_TEST_CASE(byte_array_push) ABI_CHECK(callContractFunction("test()"), encodeArgs(false)); } +BOOST_AUTO_TEST_CASE(byte_array_push_transition) +{ + // Tests transition between short and long encoding + char const* sourceCode = R"( + contract c { + bytes data; + function test() returns (uint) { + for (uint i = 1; i < 40; i++) + { + data.push(byte(i)); + if (data.length != i) return 0x1000 + i; + if (data[data.length - 1] != byte(i)) return i; + } + for (i = 1; i < 40; i++) + if (data[i - 1] != byte(i)) return 0x1000000 + i; + return 0; + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("test()"), encodeArgs(0)); +} + BOOST_AUTO_TEST_CASE(external_array_args) { char const* sourceCode = R"( @@ -7687,7 +7752,6 @@ BOOST_AUTO_TEST_CASE(create_memory_array_allocation_size) ABI_CHECK(callContractFunction("f()"), encodeArgs(0x40, 0x40, 0x20 + 256)); } - BOOST_AUTO_TEST_CASE(memory_arrays_of_various_sizes) { // Computes binomial coefficients the chinese way @@ -7710,6 +7774,41 @@ BOOST_AUTO_TEST_CASE(memory_arrays_of_various_sizes) ABI_CHECK(callContractFunction("f(uint256,uint256)", encodeArgs(u256(9), u256(5))), encodeArgs(u256(70))); } +BOOST_AUTO_TEST_CASE(create_multiple_dynamic_arrays) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint) { + uint[][] memory x = new uint[][](42); + assert(x[0].length == 0); + x[0] = new uint[](1); + x[0][0] = 1; + assert(x[4].length == 0); + x[4] = new uint[](1); + x[4][0] = 2; + assert(x[10].length == 0); + x[10] = new uint[](1); + x[10][0] = 44; + uint[][] memory y = new uint[][](24); + assert(y[0].length == 0); + y[0] = new uint[](1); + y[0][0] = 1; + assert(y[4].length == 0); + y[4] = new uint[](1); + y[4][0] = 2; + assert(y[10].length == 0); + y[10] = new uint[](1); + y[10][0] = 88; + if ((x[0][0] == y[0][0]) && (x[4][0] == y[4][0]) && (x[10][0] == 44) && (y[10][0] == 88)) + return 7; + return 0; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7))); +} + BOOST_AUTO_TEST_CASE(memory_overwrite) { char const* sourceCode = R"( @@ -8756,6 +8855,32 @@ BOOST_AUTO_TEST_CASE(create_dynamic_array_with_zero_length) ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(correctly_initialize_memory_array_in_constructor) +{ + // Memory arrays are initialized using codecopy past the size of the code. + // This test checks that it also works in the constructor context. + char const* sourceCode = R"( + contract C { + bool public success; + function C() public { + // Make memory dirty. + assembly { + for { let i := 0 } lt(i, 64) { i := add(i, 1) } { + mstore(msize, not(0)) + } + } + uint16[3] memory c; + require(c[0] == 0 && c[1] == 0 && c[2] == 0); + uint16[] memory x = new uint16[](3); + require(x[0] == 0 && x[1] == 0 && x[2] == 0); + success = true; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("success()"), encodeArgs(u256(1))); +} + BOOST_AUTO_TEST_CASE(return_does_not_skip_modifier) { char const* sourceCode = R"( @@ -9119,7 +9244,7 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) int mutex; function t() returns (uint) { if (mutex > 0) - return 7; + { assembly { mstore(0, 7) return(0, 0x20) } } mutex = 1; // Avoid re-executing this function if we jump somewhere. x(); @@ -9132,6 +9257,27 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) ABI_CHECK(callContractFunction("t()"), encodeArgs()); } +BOOST_AUTO_TEST_CASE(calling_uninitialized_function_through_array) +{ + char const* sourceCode = R"( + contract C { + int mutex; + function t() returns (uint) { + if (mutex > 0) + { assembly { mstore(0, 7) return(0, 0x20) } } + mutex = 1; + // Avoid re-executing this function if we jump somewhere. + function() internal returns (uint)[200] x; + x[0](); + return 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("t()"), encodeArgs()); +} + BOOST_AUTO_TEST_CASE(pass_function_types_internally) { char const* sourceCode = R"( @@ -10951,6 +11097,50 @@ BOOST_AUTO_TEST_CASE(staticcall_for_view_and_pure) } } +BOOST_AUTO_TEST_CASE(swap_peephole_optimisation) +{ + char const* sourceCode = R"( + contract C { + function lt(uint a, uint b) returns (bool c) { + assembly { + a + b + swap1 + lt + =: c + } + } + function add(uint a, uint b) returns (uint c) { + assembly { + a + b + swap1 + add + =: c + } + } + function div(uint a, uint b) returns (uint c) { + assembly { + a + b + swap1 + div + =: c + } + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("lt(uint256,uint256)", u256(1), u256(2)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("lt(uint256,uint256)", u256(2), u256(1)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("add(uint256,uint256)", u256(1), u256(2)) == encodeArgs(u256(3))); + BOOST_CHECK(callContractFunction("add(uint256,uint256)", u256(100), u256(200)) == encodeArgs(u256(300))); + BOOST_CHECK(callContractFunction("div(uint256,uint256)", u256(2), u256(1)) == encodeArgs(u256(2))); + BOOST_CHECK(callContractFunction("div(uint256,uint256)", u256(200), u256(10)) == encodeArgs(u256(20))); + BOOST_CHECK(callContractFunction("div(uint256,uint256)", u256(1), u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("div(uint256,uint256)", u256(0), u256(1)) == encodeArgs(u256(0))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index dd29bf48..fcee0df3 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -92,61 +92,6 @@ BOOST_AUTO_TEST_CASE(reference_to_later_declaration) CHECK_SUCCESS(text); } -BOOST_AUTO_TEST_CASE(struct_definition_directly_recursive) -{ - char const* text = R"( - contract test { - struct MyStructName { - address addr; - MyStructName x; - } - } - )"; - CHECK_ERROR(text, TypeError, "Recursive struct definition."); -} - -BOOST_AUTO_TEST_CASE(struct_definition_indirectly_recursive) -{ - char const* text = R"( - contract test { - struct MyStructName1 { - address addr; - uint256 count; - MyStructName2 x; - } - struct MyStructName2 { - MyStructName1 x; - } - } - )"; - CHECK_ERROR(text, TypeError, "Recursive struct definition."); -} - -BOOST_AUTO_TEST_CASE(struct_definition_not_really_recursive) -{ - char const* text = R"( - contract test { - struct s1 { uint a; } - struct s2 { s1 x; s1 y; } - } - )"; - CHECK_SUCCESS(text); -} - -BOOST_AUTO_TEST_CASE(struct_definition_recursion_via_mapping) -{ - char const* text = R"( - contract test { - struct MyStructName1 { - address addr; - uint256 count; - mapping(uint => MyStructName1) x; - } - } - )"; - CHECK_SUCCESS(text); -} - BOOST_AUTO_TEST_CASE(type_inference_smoke_test) { char const* text = R"( @@ -962,6 +907,35 @@ BOOST_AUTO_TEST_CASE(base_constructor_arguments_override) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(new_constructor_syntax) +{ + char const* text = R"( + contract A { constructor() public {} } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(old_constructor_syntax) +{ + char const* text = R"( + contract A { function A() public {} } + )"; + CHECK_WARNING( + text, + "Defining constructors as functions with the same name as the contract is deprecated." + ); + + text = R"( + pragma experimental "v0.5.0"; + contract A { function A() public {} } + )"; + CHECK_ERROR( + text, + SyntaxError, + "Functions are not allowed to have the same name as the contract." + ); +} + BOOST_AUTO_TEST_CASE(implicit_derived_to_base_conversion) { char const* text = R"( @@ -2452,8 +2426,8 @@ BOOST_AUTO_TEST_CASE(test_fromElementaryTypeName) BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::BytesM, 31, 0)) == *make_shared<FixedBytesType>(31)); BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::BytesM, 32, 0)) == *make_shared<FixedBytesType>(32)); - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::Fixed, 0, 0)) == *make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Signed)); - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixed, 0, 0)) == *make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Unsigned)); + BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::Fixed, 0, 0)) == *make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Signed)); + BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixed, 0, 0)) == *make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Unsigned)); } BOOST_AUTO_TEST_CASE(test_byte_is_alias_of_byte1) @@ -4471,7 +4445,7 @@ BOOST_AUTO_TEST_CASE(invalid_int_implicit_conversion_from_fixed) } } )"; - CHECK_ERROR(text, TypeError, "Type fixed128x19 is not implicitly convertible to expected type int256"); + CHECK_ERROR(text, TypeError, "Type fixed128x18 is not implicitly convertible to expected type int256"); } BOOST_AUTO_TEST_CASE(rational_unary_operation) @@ -4589,7 +4563,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_invalid_implicit_conversion_size) } } )"; - CHECK_ERROR(text, TypeError, "Type ufixed128x19 is not implicitly convertible to expected type ufixed248x8"); + CHECK_ERROR(text, TypeError, "Type ufixed128x18 is not implicitly convertible to expected type ufixed248x8"); } BOOST_AUTO_TEST_CASE(fixed_type_invalid_implicit_conversion_lost_data) @@ -4676,7 +4650,7 @@ BOOST_AUTO_TEST_CASE(fixed_to_bytes_implicit_conversion) } } )"; - CHECK_ERROR(text, TypeError, "fixed128x19 is not implicitly convertible to expected type bytes32"); + CHECK_ERROR(text, TypeError, "fixed128x18 is not implicitly convertible to expected type bytes32"); } BOOST_AUTO_TEST_CASE(mapping_with_fixed_literal) @@ -6193,44 +6167,6 @@ BOOST_AUTO_TEST_CASE(read_returned_struct) )"; CHECK_WARNING(text, "Experimental features"); } - -BOOST_AUTO_TEST_CASE(return_recursive_structs) -{ - char const* text = R"( - contract C { - struct S { uint a; S[] sub; } - function f() returns (uint, S) { - } - } - )"; - CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); -} - -BOOST_AUTO_TEST_CASE(return_recursive_structs2) -{ - char const* text = R"( - contract C { - struct S { uint a; S[2][] sub; } - function f() returns (uint, S) { - } - } - )"; - CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); -} - -BOOST_AUTO_TEST_CASE(return_recursive_structs3) -{ - char const* text = R"( - contract C { - struct S { uint a; S[][][] sub; } - struct T { S s; } - function f() returns (uint x, T t) { - } - } - )"; - CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); -} - BOOST_AUTO_TEST_CASE(address_checksum_type_deduction) { char const* text = R"( @@ -6353,38 +6289,6 @@ BOOST_AUTO_TEST_CASE(address_methods) CHECK_SUCCESS(text); } -BOOST_AUTO_TEST_CASE(cyclic_dependency_for_constants) -{ - char const* text = R"( - contract C { - uint constant a = a; - } - )"; - CHECK_ERROR(text, TypeError, "cyclic dependency via a"); - text = R"( - contract C { - uint constant a = b * c; - uint constant b = 7; - uint constant c = b + uint(keccak256(d)); - uint constant d = 2 + a; - } - )"; - CHECK_ERROR_ALLOW_MULTI(text, TypeError, (std::vector<std::string>{ - "a has a cyclic dependency via c", - "c has a cyclic dependency via d", - "d has a cyclic dependency via a" - })); - text = R"( - contract C { - uint constant a = b * c; - uint constant b = 7; - uint constant c = 4 + uint(keccak256(d)); - uint constant d = 2 + b; - } - )"; - CHECK_SUCCESS(text); -} - BOOST_AUTO_TEST_CASE(interface) { char const* text = R"( @@ -6429,54 +6333,6 @@ BOOST_AUTO_TEST_CASE(interface_function_bodies) CHECK_ERROR(text, TypeError, "Functions in interfaces cannot have an implementation"); } -BOOST_AUTO_TEST_CASE(interface_function_external) -{ - char const* text = R"( - pragma experimental "v0.5.0"; - interface I { - function f() external; - } - )"; - CHECK_SUCCESS(text); -} - -BOOST_AUTO_TEST_CASE(interface_function_public) -{ - char const* text = R"( - interface I { - function f() public; - } - )"; - CHECK_WARNING(text, "Functions in interfaces should be declared external."); - text = R"( - pragma experimental "v0.5.0"; - interface I { - function f() public; - } - )"; - CHECK_ERROR(text, TypeError, "Functions in interfaces must be declared external."); -} - -BOOST_AUTO_TEST_CASE(interface_function_internal) -{ - char const* text = R"( - interface I { - function f() internal; - } - )"; - CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private."); -} - -BOOST_AUTO_TEST_CASE(interface_function_private) -{ - char const* text = R"( - interface I { - function f() private; - } - )"; - CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private."); -} - BOOST_AUTO_TEST_CASE(interface_events) { char const* text = R"( @@ -6964,7 +6820,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_ignores_constructor) { char const* text = R"( contract C { - function C() public {} + constructor() public {} } )"; CHECK_SUCCESS_NO_WARNINGS(text); @@ -7376,7 +7232,7 @@ BOOST_AUTO_TEST_CASE(using_this_in_constructor) { char const* text = R"( contract C { - function C() public { + constructor() public { this.f(); } function f() pure public { diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 33039ca9..5326feaf 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -74,11 +74,11 @@ public: unsigned const _optimizeRuns = 200 ) { - bytes nonOptimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, false, _optimizeRuns); + m_nonOptimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, false, _optimizeRuns); m_nonOptimizedContract = m_contractAddress; - bytes optimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, true, _optimizeRuns); - size_t nonOptimizedSize = numInstructions(nonOptimizedBytecode); - size_t optimizedSize = numInstructions(optimizedBytecode); + m_optimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, true, _optimizeRuns); + size_t nonOptimizedSize = numInstructions(m_nonOptimizedBytecode); + size_t optimizedSize = numInstructions(m_optimizedBytecode); BOOST_CHECK_MESSAGE( _optimizeRuns < 50 || optimizedSize < nonOptimizedSize, string("Optimizer did not reduce bytecode size. Non-optimized size: ") + @@ -93,8 +93,10 @@ public: { m_contractAddress = m_nonOptimizedContract; bytes nonOptimizedOutput = callContractFunction(_sig, _arguments...); + m_gasUsedNonOptimized = m_gasUsed; m_contractAddress = m_optimizedContract; bytes optimizedOutput = callContractFunction(_sig, _arguments...); + m_gasUsedOptimized = m_gasUsed; BOOST_CHECK_MESSAGE(!optimizedOutput.empty(), "No optimized output for " + _sig); BOOST_CHECK_MESSAGE(!nonOptimizedOutput.empty(), "No un-optimized output for " + _sig); BOOST_CHECK_MESSAGE(nonOptimizedOutput == optimizedOutput, "Computed values do not match." @@ -104,7 +106,7 @@ public: /// @returns the number of intructions in the given bytecode, not taking the metadata hash /// into account. - size_t numInstructions(bytes const& _bytecode) + size_t numInstructions(bytes const& _bytecode, boost::optional<Instruction> _which = boost::optional<Instruction>{}) { BOOST_REQUIRE(_bytecode.size() > 5); size_t metadataSize = (_bytecode[_bytecode.size() - 2] << 8) + _bytecode[_bytecode.size() - 1]; @@ -112,13 +114,18 @@ public: BOOST_REQUIRE(_bytecode.size() >= metadataSize + 2); bytes realCode = bytes(_bytecode.begin(), _bytecode.end() - metadataSize - 2); size_t instructions = 0; - solidity::eachInstruction(realCode, [&](Instruction, u256 const&) { - instructions++; + solidity::eachInstruction(realCode, [&](Instruction _instr, u256 const&) { + if (!_which || *_which == _instr) + instructions++; }); return instructions; } protected: + u256 m_gasUsedOptimized; + u256 m_gasUsedNonOptimized; + bytes m_nonOptimizedBytecode; + bytes m_optimizedBytecode; Address m_optimizedContract; Address m_nonOptimizedContract; }; @@ -581,6 +588,49 @@ BOOST_AUTO_TEST_CASE(invalid_state_at_control_flow_join) compareVersions("test()"); } +BOOST_AUTO_TEST_CASE(init_empty_dynamic_arrays) +{ + // This is not so much an optimizer test, but rather a test + // that allocating empty arrays is implemented efficiently. + // In particular, initializing a dynamic memory array does + // not use any memory. + char const* sourceCode = R"( + contract Test { + function f() pure returns (uint r) { + uint[][] memory x = new uint[][](20000); + return x.length; + } + } + )"; + compileBothVersions(sourceCode); + compareVersions("f()"); + BOOST_CHECK_LE(m_gasUsedNonOptimized, 1900000); + BOOST_CHECK_LE(1600000, m_gasUsedNonOptimized); +} + +BOOST_AUTO_TEST_CASE(optimise_multi_stores) +{ + char const* sourceCode = R"( + contract Test { + struct S { uint16 a; uint16 b; uint16[3] c; uint[] dyn; } + uint padding; + S[] s; + function f() public returns (uint16, uint16, uint16[3], uint) { + uint16[3] memory c; + c[0] = 7; + c[1] = 8; + c[2] = 9; + s.push(S(1, 2, c, new uint[](4))); + return (s[0].a, s[0].b, s[0].c, s[0].dyn[2]); + } + } + )"; + compileBothVersions(sourceCode); + compareVersions("f()"); + BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::SSTORE), 9); + BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::SSTORE), 8); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 4e862f60..93e6bcaa 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -112,26 +112,6 @@ while(0) BOOST_AUTO_TEST_SUITE(SolidityParser) -BOOST_AUTO_TEST_CASE(smoke_test) -{ - char const* text = R"( - contract test { - uint256 stateVariable1; - } - )"; - BOOST_CHECK(successParse(text)); -} - -BOOST_AUTO_TEST_CASE(missing_variable_name_in_declaration) -{ - char const* text = R"( - contract test { - uint256 ; - } - )"; - CHECK_PARSE_ERROR(text, "Expected identifier"); -} - BOOST_AUTO_TEST_CASE(empty_function) { char const* text = R"( diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index dd6eb7c4..b285a2a0 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -261,14 +261,14 @@ BOOST_AUTO_TEST_CASE(basic_compilation) BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()), - "60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00" + "60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00" ); BOOST_CHECK(contract["evm"]["assembly"].isString()); BOOST_CHECK(contract["evm"]["assembly"].asString().find( - " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x60)\n jumpi(tag_1, iszero(callvalue))\n" + " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n jumpi(tag_1, iszero(callvalue))\n" " 0x0\n dup1\n revert\ntag_1:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n" " return\nstop\n\nsub_0: assembly {\n /* \"fileA\":0:14 contract A { } */\n" - " mstore(0x40, 0x60)\n 0x0\n dup1\n revert\n\n" + " mstore(0x40, 0x80)\n 0x0\n dup1\n revert\n\n" " auxdata: 0xa165627a7a7230582") == 0); BOOST_CHECK(contract["evm"]["gasEstimates"].isObject()); BOOST_CHECK_EQUAL( diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index ca051138..329543bf 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -16,6 +16,7 @@ */ #include <test/libsolidity/SyntaxTest.h> +#include <test/Options.h> #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/throw_exception.hpp> @@ -59,93 +60,52 @@ SyntaxTest::SyntaxTest(string const& _filename) bool SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) { - m_errorList = parseAnalyseAndReturnError(m_source, true, true, true).second; - if (!matchesExpectations(m_errorList)) + m_compiler.reset(); + m_compiler.addSource("", "pragma solidity >=0.0;\n" + m_source); + m_compiler.setEVMVersion(dev::test::Options::get().evmVersion()); + + if (m_compiler.parse()) + m_compiler.analyze(); + + for (auto const& currentError: filterErrors(m_compiler.errors(), true)) + m_errorList.emplace_back(SyntaxTestError{currentError->typeName(), errorMessage(*currentError)}); + + if (m_expectations != m_errorList) { - std::string nextIndentLevel = _linePrefix + " "; + string nextIndentLevel = _linePrefix + " "; FormattedScope(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; - printExpected(_stream, nextIndentLevel, _formatted); + printErrorList(_stream, m_expectations, nextIndentLevel, _formatted); FormattedScope(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:\n"; - printErrorList(_stream, m_errorList, nextIndentLevel, false, false, _formatted); + printErrorList(_stream, m_errorList, nextIndentLevel, _formatted); return false; } return true; } -void SyntaxTest::printExpected(ostream& _stream, string const& _linePrefix, bool const _formatted) const -{ - if (m_expectations.empty()) - FormattedScope(_stream, _formatted, {BOLD, GREEN}) << _linePrefix << "Success" << endl; - else - for (auto const& expectation: m_expectations) - { - FormattedScope(_stream, _formatted, {BOLD, expectation.type == "Warning" ? YELLOW : RED}) << - _linePrefix << expectation.type << ": "; - _stream << expectation.message << endl; - } -} - void SyntaxTest::printErrorList( ostream& _stream, - ErrorList const& _errorList, + vector<SyntaxTestError> const& _errorList, string const& _linePrefix, - bool const _ignoreWarnings, - bool const _lineNumbers, bool const _formatted -) const +) { if (_errorList.empty()) FormattedScope(_stream, _formatted, {BOLD, GREEN}) << _linePrefix << "Success" << endl; else for (auto const& error: _errorList) { - bool isWarning = (error->type() == Error::Type::Warning); - if (isWarning && _ignoreWarnings) continue; - { - FormattedScope scope(_stream, _formatted, {BOLD, isWarning ? YELLOW : RED}); + FormattedScope scope(_stream, _formatted, {BOLD, (error.type == "Warning") ? YELLOW : RED}); _stream << _linePrefix; - if (_lineNumbers) - { - int line = offsetToLineNumber( - boost::get_error_info<errinfo_sourceLocation>(*error)->start - ); - if (line >= 0) - _stream << "(" << line << "): "; - } - _stream << error->typeName() << ": "; + _stream << error.type << ": "; } - _stream << errorMessage(*error) << endl; + _stream << error.message << endl; } } -int SyntaxTest::offsetToLineNumber(int _location) const -{ - // parseAnalyseAndReturnError(...) prepends a version pragma - _location -= strlen("pragma solidity >=0.0;\n"); - if (_location < 0 || static_cast<size_t>(_location) >= m_source.size()) - return -1; - else - return 1 + std::count(m_source.begin(), m_source.begin() + _location, '\n'); -} - -bool SyntaxTest::matchesExpectations(ErrorList const& _errorList) const -{ - if (_errorList.size() != m_expectations.size()) - return false; - else - for (size_t i = 0; i < _errorList.size(); i++) - if ( - (_errorList[i]->typeName() != m_expectations[i].type) || - (errorMessage(*_errorList[i]) != m_expectations[i].message) - ) - return false; - return true; -} - -string SyntaxTest::errorMessage(Error const& _e) +string SyntaxTest::errorMessage(Exception const& _e) { - if (_e.comment()) + if (_e.comment() && !_e.comment()->empty()) return boost::replace_all_copy(*_e.comment(), "\n", "\\n"); else return "NONE"; @@ -164,9 +124,9 @@ string SyntaxTest::parseSource(istream& _stream) return source; } -vector<SyntaxTestExpectation> SyntaxTest::parseExpectations(istream& _stream) +vector<SyntaxTestError> SyntaxTest::parseExpectations(istream& _stream) { - vector<SyntaxTestExpectation> expectations; + vector<SyntaxTestError> expectations; string line; while (getline(_stream, line)) { @@ -188,7 +148,7 @@ vector<SyntaxTestExpectation> SyntaxTest::parseExpectations(istream& _stream) skipWhitespace(it, line.end()); string errorMessage(it, line.end()); - expectations.emplace_back(SyntaxTestExpectation{move(errorType), move(errorMessage)}); + expectations.emplace_back(SyntaxTestError{move(errorType), move(errorMessage)}); } return expectations; } @@ -239,9 +199,11 @@ int SyntaxTest::registerTests( _suite.add(make_test_case( [fullpath] { - std::stringstream errorStream; - if (!SyntaxTest(fullpath.string()).run(errorStream)) - BOOST_ERROR("Test expectation mismatch.\n" + errorStream.str()); + BOOST_REQUIRE_NO_THROW({ + stringstream errorStream; + if (!SyntaxTest(fullpath.string()).run(errorStream)) + BOOST_ERROR("Test expectation mismatch.\n" + errorStream.str()); + }); }, _path.stem().string(), *filenames.back(), diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index cb6ee05c..dddd86ef 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -36,10 +36,14 @@ namespace solidity namespace test { -struct SyntaxTestExpectation +struct SyntaxTestError { std::string type; std::string message; + bool operator==(SyntaxTestError const& _rhs) const + { + return type == _rhs.type && message == _rhs.message; + } }; @@ -50,21 +54,16 @@ public: bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false); - std::vector<SyntaxTestExpectation> const& expectations() const { return m_expectations; } + std::vector<SyntaxTestError> const& expectations() const { return m_expectations; } std::string const& source() const { return m_source; } - ErrorList const& errorList() const { return m_errorList; } - ErrorList const& compilerErrors() const { return m_compiler.errors(); } + std::vector<SyntaxTestError> const& errorList() const { return m_errorList; } - void printExpected(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted = false) const; - - void printErrorList( + static void printErrorList( std::ostream& _stream, - ErrorList const& _errors, + std::vector<SyntaxTestError> const& _errors, std::string const& _linePrefix, - bool const _ignoreWarnings, - bool const _lineNumbers, bool const _formatted = false - ) const; + ); static int registerTests( boost::unit_test::test_suite& _suite, @@ -72,16 +71,14 @@ public: boost::filesystem::path const& _path ); static bool isTestFilename(boost::filesystem::path const& _filename); + static std::string errorMessage(Exception const& _e); private: - bool matchesExpectations(ErrorList const& _errors) const; - static std::string errorMessage(Error const& _e); static std::string parseSource(std::istream& _stream); - static std::vector<SyntaxTestExpectation> parseExpectations(std::istream& _stream); - int offsetToLineNumber(int _location) const; + static std::vector<SyntaxTestError> parseExpectations(std::istream& _stream); std::string m_source; - std::vector<SyntaxTestExpectation> m_expectations; - ErrorList m_errorList; + std::vector<SyntaxTestError> m_expectations; + std::vector<SyntaxTestError> m_errorList; }; } diff --git a/test/libsolidity/syntaxTests/constants/cyclic_dependency_1.sol b/test/libsolidity/syntaxTests/constants/cyclic_dependency_1.sol new file mode 100644 index 00000000..2b6aa088 --- /dev/null +++ b/test/libsolidity/syntaxTests/constants/cyclic_dependency_1.sol @@ -0,0 +1,5 @@ +contract C { + uint constant a = a; +} +// ---- +// TypeError: The value of the constant a has a cyclic dependency via a. diff --git a/test/libsolidity/syntaxTests/constants/cyclic_dependency_2.sol b/test/libsolidity/syntaxTests/constants/cyclic_dependency_2.sol new file mode 100644 index 00000000..461979f8 --- /dev/null +++ b/test/libsolidity/syntaxTests/constants/cyclic_dependency_2.sol @@ -0,0 +1,10 @@ +contract C { + uint constant a = b * c; + uint constant b = 7; + uint constant c = b + uint(keccak256(d)); + uint constant d = 2 + a; +} +// ---- +// TypeError: The value of the constant a has a cyclic dependency via c. +// TypeError: The value of the constant c has a cyclic dependency via d. +// TypeError: The value of the constant d has a cyclic dependency via a. diff --git a/test/libsolidity/syntaxTests/constants/cyclic_dependency_3.sol b/test/libsolidity/syntaxTests/constants/cyclic_dependency_3.sol new file mode 100644 index 00000000..f63be05e --- /dev/null +++ b/test/libsolidity/syntaxTests/constants/cyclic_dependency_3.sol @@ -0,0 +1,11 @@ +contract C { + uint constant x = a; + uint constant a = b * c; + uint constant b = c; + uint constant c = b; +} +// ---- +// TypeError: The value of the constant x has a cyclic dependency via a. +// TypeError: The value of the constant a has a cyclic dependency via b. +// TypeError: The value of the constant b has a cyclic dependency via c. +// TypeError: The value of the constant c has a cyclic dependency via b. diff --git a/test/libsolidity/syntaxTests/constants/cyclic_dependency_4.sol b/test/libsolidity/syntaxTests/constants/cyclic_dependency_4.sol new file mode 100644 index 00000000..f01cb98e --- /dev/null +++ b/test/libsolidity/syntaxTests/constants/cyclic_dependency_4.sol @@ -0,0 +1,6 @@ +contract C { + uint constant a = b * c; + uint constant b = 7; + uint constant c = 4 + uint(keccak256(d)); + uint constant d = 2 + b; +}
\ No newline at end of file diff --git a/test/libsolidity/syntaxTests/inheritance/base_arguments_empty_parentheses.sol b/test/libsolidity/syntaxTests/inheritance/base_arguments_empty_parentheses.sol new file mode 100644 index 00000000..b3fbd04a --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/base_arguments_empty_parentheses.sol @@ -0,0 +1,7 @@ +contract Base { + constructor(uint) public {} +} +contract Derived is Base(2) { } +contract Derived2 is Base(), Derived() { } +// ---- +// Warning: Wrong argument count for constructor call: 0 arguments given but expected 1. diff --git a/test/libsolidity/syntaxTests/inheritance/base_arguments_empty_parentheses_V050.sol b/test/libsolidity/syntaxTests/inheritance/base_arguments_empty_parentheses_V050.sol new file mode 100644 index 00000000..b3728634 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/base_arguments_empty_parentheses_V050.sol @@ -0,0 +1,9 @@ +pragma experimental "v0.5.0"; + +contract Base { + constructor(uint) public {} +} +contract Derived is Base(2) { } +contract Derived2 is Base(), Derived() { } +// ---- +// TypeError: Wrong argument count for constructor call: 0 arguments given but expected 1. diff --git a/test/libsolidity/syntaxTests/inheritance/base_arguments_no_parentheses.sol b/test/libsolidity/syntaxTests/inheritance/base_arguments_no_parentheses.sol new file mode 100644 index 00000000..24cca8f0 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/base_arguments_no_parentheses.sol @@ -0,0 +1,5 @@ +contract Base { + constructor(uint) public {} +} +contract Derived is Base(2) { } +contract Derived2 is Base, Derived {} diff --git a/test/libsolidity/syntaxTests/inheritance/too_few_base_arguments.sol b/test/libsolidity/syntaxTests/inheritance/too_few_base_arguments.sol new file mode 100644 index 00000000..45a0770f --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/too_few_base_arguments.sol @@ -0,0 +1,10 @@ +contract Base { + constructor(uint, uint) public {} +} +contract Derived is Base(2) { } +contract Derived2 is Base { + constructor() Base(2) public { } +} +// ---- +// TypeError: Wrong argument count for constructor call: 1 arguments given but expected 2. +// TypeError: Wrong argument count for modifier invocation: 1 arguments given but expected 2. diff --git a/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol b/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol new file mode 100644 index 00000000..c03fd97d --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol @@ -0,0 +1,5 @@ +contract test { + uint256 ; +} +// ---- +// ParserError: Expected identifier, got 'Semicolon' diff --git a/test/libsolidity/syntaxTests/parsing/smoke_test.sol b/test/libsolidity/syntaxTests/parsing/smoke_test.sol new file mode 100644 index 00000000..d328b167 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/smoke_test.sol @@ -0,0 +1,4 @@ +contract test { + uint256 stateVariable1; +} +// ---- diff --git a/test/libsolidity/syntaxTests/structs/recursion/multi_struct_composition.sol b/test/libsolidity/syntaxTests/structs/recursion/multi_struct_composition.sol new file mode 100644 index 00000000..9a1c22f1 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/multi_struct_composition.sol @@ -0,0 +1,15 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct T { U u; V v; } + + struct U { W w; } + + struct V { W w; } + + struct W { uint x; } + + function f(T) public pure { } +} +// ---- +// Warning: Experimental features are turned on. Do not use experimental features on live deployments. diff --git a/test/libsolidity/syntaxTests/structs/recursion/parallel_structs.sol b/test/libsolidity/syntaxTests/structs/recursion/parallel_structs.sol new file mode 100644 index 00000000..d4ad088d --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/parallel_structs.sol @@ -0,0 +1,15 @@ +pragma experimental ABIEncoderV2; + +contract TestContract +{ + struct SubStruct { + uint256 id; + } + struct TestStruct { + SubStruct subStruct1; + SubStruct subStruct2; + } + function addTestStruct(TestStruct) public pure {} +} +// ---- +// Warning: Experimental features are turned on. Do not use experimental features on live deployments. diff --git a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs.sol b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs.sol new file mode 100644 index 00000000..c02a8aa4 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs.sol @@ -0,0 +1,7 @@ +contract C { + struct S { uint a; S[] sub; } + function f() public pure returns (uint, S) { + } +} +// ---- +// TypeError: Internal or recursive type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs2.sol b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs2.sol new file mode 100644 index 00000000..e9488cf4 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs2.sol @@ -0,0 +1,7 @@ +contract C { + struct S { uint a; S[2][] sub; } + function f() public pure returns (uint, S) { + } +} +// ---- +// TypeError: Internal or recursive type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs3.sol b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs3.sol new file mode 100644 index 00000000..6728baec --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/return_recursive_structs3.sol @@ -0,0 +1,8 @@ +contract C { + struct S { uint a; S[][][] sub; } + struct T { S s; } + function f() public pure returns (uint x, T t) { + } +} +// ---- +// TypeError: Internal or recursive type is not allowed for public or external functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/struct_definition_directly_recursive.sol b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_directly_recursive.sol new file mode 100644 index 00000000..cac2e23f --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_directly_recursive.sol @@ -0,0 +1,8 @@ +contract Test { + struct MyStructName { + address addr; + MyStructName x; + } +} +// ---- +// TypeError: Recursive struct definition. diff --git a/test/libsolidity/syntaxTests/structs/recursion/struct_definition_indirectly_recursive.sol b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_indirectly_recursive.sol new file mode 100644 index 00000000..11fc6307 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_indirectly_recursive.sol @@ -0,0 +1,12 @@ +contract Test { + struct MyStructName1 { + address addr; + uint256 count; + MyStructName2 x; + } + struct MyStructName2 { + MyStructName1 x; + } +} +// ---- +// TypeError: Recursive struct definition. diff --git a/test/libsolidity/syntaxTests/structs/recursion/struct_definition_not_really_recursive.sol b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_not_really_recursive.sol new file mode 100644 index 00000000..6ec4ee01 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_not_really_recursive.sol @@ -0,0 +1,4 @@ +contract Test { + struct S1 { uint a; } + struct S2 { S1 x; S1 y; } +} diff --git a/test/libsolidity/syntaxTests/structs/recursion/struct_definition_recursion_via_mapping.sol b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_recursion_via_mapping.sol new file mode 100644 index 00000000..926981b3 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/struct_definition_recursion_via_mapping.sol @@ -0,0 +1,7 @@ +contract Test { + struct MyStructName1 { + address addr; + uint256 count; + mapping(uint => MyStructName1) x; + } +} diff --git a/test/libsolidity/syntaxTests/visibility/interface/function_default.sol b/test/libsolidity/syntaxTests/visibility/interface/function_default.sol new file mode 100644 index 00000000..7b9044dd --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/interface/function_default.sol @@ -0,0 +1,6 @@ +interface I { + function f(); +} +// ---- +// Warning: Functions in interfaces should be declared external. +// Warning: No visibility specified. Defaulting to "public". In interfaces it defaults to external. diff --git a/test/libsolidity/syntaxTests/visibility/interface/function_default050.sol b/test/libsolidity/syntaxTests/visibility/interface/function_default050.sol new file mode 100644 index 00000000..127d4e92 --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/interface/function_default050.sol @@ -0,0 +1,7 @@ +pragma experimental "v0.5.0"; +interface I { + function f(); +} +// ---- +// SyntaxError: No visibility specified. +// TypeError: Functions in interfaces must be declared external. diff --git a/test/libsolidity/syntaxTests/visibility/interface/function_external050.sol b/test/libsolidity/syntaxTests/visibility/interface/function_external050.sol new file mode 100644 index 00000000..3f0a9aca --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/interface/function_external050.sol @@ -0,0 +1,5 @@ +pragma experimental "v0.5.0"; +interface I { + function f() external; +} +// ---- diff --git a/test/libsolidity/syntaxTests/visibility/interface/function_internal.sol b/test/libsolidity/syntaxTests/visibility/interface/function_internal.sol new file mode 100644 index 00000000..a1cf5246 --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/interface/function_internal.sol @@ -0,0 +1,5 @@ +interface I { + function f() internal; +} +// ---- +// TypeError: Functions in interfaces cannot be internal or private. diff --git a/test/libsolidity/syntaxTests/visibility/interface/function_private.sol b/test/libsolidity/syntaxTests/visibility/interface/function_private.sol new file mode 100644 index 00000000..887ebd4b --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/interface/function_private.sol @@ -0,0 +1,5 @@ +interface I { + function f() private; +} +// ---- +// TypeError: Functions in interfaces cannot be internal or private. diff --git a/test/libsolidity/syntaxTests/visibility/interface/function_public.sol b/test/libsolidity/syntaxTests/visibility/interface/function_public.sol new file mode 100644 index 00000000..146d4f5b --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/interface/function_public.sol @@ -0,0 +1,5 @@ +interface I { + function f() public; +} +// ---- +// Warning: Functions in interfaces should be declared external.
\ No newline at end of file diff --git a/test/libsolidity/syntaxTests/visibility/interface/function_public050.sol b/test/libsolidity/syntaxTests/visibility/interface/function_public050.sol new file mode 100644 index 00000000..f957f0b4 --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/interface/function_public050.sol @@ -0,0 +1,6 @@ +pragma experimental "v0.5.0"; +interface I { + function f() public; +} +// ---- +// TypeError: Functions in interfaces must be declared external.
\ No newline at end of file diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 5efec421..07df8f60 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -55,8 +55,8 @@ public: { Success, Failure, - ParserError, - InputOutputError + InputOutputError, + Exception }; Result process(); @@ -76,7 +76,7 @@ private: Quit }; - Request handleResponse(bool const _parserError); + Request handleResponse(bool const _exception); void printContract() const; @@ -100,7 +100,6 @@ void SyntaxTestTool::printContract() const SyntaxTestTool::Result SyntaxTestTool::process() { bool success; - bool parserError = false; std::stringstream outputMessages; (FormattedScope(cout, m_formatted, {BOLD}) << m_name << ": ").flush(); @@ -119,10 +118,35 @@ SyntaxTestTool::Result SyntaxTestTool::process() { success = m_test->run(outputMessages, " ", m_formatted); } - catch (...) + catch(CompilerError const& _e) { - success = false; - parserError = true; + FormattedScope(cout, m_formatted, {BOLD, RED}) << + "Exception: " << SyntaxTest::errorMessage(_e) << endl; + return Result::Exception; + } + catch(InternalCompilerError const& _e) + { + FormattedScope(cout, m_formatted, {BOLD, RED}) << + "InternalCompilerError: " << SyntaxTest::errorMessage(_e) << endl; + return Result::Exception; + } + catch(FatalError const& _e) + { + FormattedScope(cout, m_formatted, {BOLD, RED}) << + "FatalError: " << SyntaxTest::errorMessage(_e) << endl; + return Result::Exception; + } + catch(UnimplementedFeatureError const& _e) + { + FormattedScope(cout, m_formatted, {BOLD, RED}) << + "UnimplementedFeatureError: " << SyntaxTest::errorMessage(_e) << endl; + return Result::Exception; + } + catch(...) + { + FormattedScope(cout, m_formatted, {BOLD, RED}) << + "Unknown Exception" << endl; + return Result::Exception; } if (success) @@ -137,25 +161,14 @@ SyntaxTestTool::Result SyntaxTestTool::process() FormattedScope(cout, m_formatted, {BOLD, CYAN}) << " Contract:" << endl; printContract(); - if (parserError) - { - cout << " "; - FormattedScope(cout, m_formatted, {INVERSE, RED}) << "Parsing failed:" << endl; - m_test->printErrorList(cout, m_test->compilerErrors(), " ", true, true, m_formatted); - cout << endl; - return Result::ParserError; - } - else - { - cout << outputMessages.str() << endl; - return Result::Failure; - } + cout << outputMessages.str() << endl; + return Result::Failure; } } -SyntaxTestTool::Request SyntaxTestTool::handleResponse(bool const _parserError) +SyntaxTestTool::Request SyntaxTestTool::handleResponse(bool const _exception) { - if (_parserError) + if (_exception) cout << "(e)dit/(s)kip/(q)uit? "; else cout << "(e)dit/(u)pdate expectations/(s)kip/(q)uit? "; @@ -169,7 +182,7 @@ SyntaxTestTool::Request SyntaxTestTool::handleResponse(bool const _parserError) cout << endl; return Request::Skip; case 'u': - if (_parserError) + if (_exception) break; else { @@ -178,7 +191,7 @@ SyntaxTestTool::Request SyntaxTestTool::handleResponse(bool const _parserError) file << m_test->source(); file << "// ----" << endl; if (!m_test->errorList().empty()) - m_test->printErrorList(file, m_test->errorList(), "// ", false, false, false); + m_test->printErrorList(file, m_test->errorList(), "// ", false); return Request::Rerun; } case 'e': @@ -231,8 +244,8 @@ SyntaxTestStats SyntaxTestTool::processPath( switch(result) { case Result::Failure: - case Result::ParserError: - switch(testTool.handleResponse(result == Result::ParserError)) + case Result::Exception: + switch(testTool.handleResponse(result == Result::Exception)) { case Request::Quit: return { successCount, runCount }; |