diff options
220 files changed, 4257 insertions, 1431 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1844b2ed..4ac56b43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ include(EthCcache) # Let's find our dependencies include(EthDependencies) include(jsoncpp) +include_directories(${JSONCPP_INCLUDE_DIR}) find_package(Threads) diff --git a/CODING_STYLE.md b/CODING_STYLE.md index 3244466b..f36503d0 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -49,21 +49,28 @@ cout << "some very long string that contains completely irrelevant text that tal ## 1. Namespaces 1. No `using namespace` declarations in header files. -2. All symbols should be declared in a namespace except for final applications. -3. Use anonymous namespaces for helpers whose scope is a cpp file only. -4. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore. +2. Use `using namespace std;` in cpp files, but avoid importing namespaces from boost and others. +3. All symbols should be declared in a namespace except for final applications. +4. Use anonymous namespaces for helpers whose scope is a cpp file only. +5. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore. -Yes: +Only in the header: ```cpp #include <cassert> +namespace myNamespace +{ std::tuple<float, float> meanAndSigma(std::vector<float> const& _v); +} ``` -No: +Only in the cpp file: ```cpp #include <cassert> using namespace std; -tuple<float, float> meanAndSigma(vector<float> const& _v); +tuple<float, float> myNamespace::meanAndSigma(vector<float> const& _v) +{ + // ... +} ``` ## 2. Preprocessor diff --git a/Changelog.md b/Changelog.md index faeb628b..6f6f672c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,22 @@ ### 0.4.24 (unreleased) Features: - + * Remove deprecated ``constant`` as function state modifier from documentation and tests (but still leave it as a valid feature). + * Build System: Update internal dependency of jsoncpp to 1.8.4, which introduces more strictness and reduces memory usage. + * Code Generator: Use native shift instructions on target Constantinople. + * Control Flow Graph: Add Control Flow Graph as analysis structure. + * Control Flow Graph: Warn about returning uninitialized storage pointers. + * Gas Estimator: Only explore paths with higher gas costs. This reduces accuracy but greatly improves the speed of gas estimation. + * Optimizer: Remove unnecessary masking of the result of known short instructions (``ADDRESS``, ``CALLER``, ``ORIGIN`` and ``COINBASE``). + * Parser: Display nicer error messages by showing the actual tokens and not internal names. + * Parser: Use the entire location of the token instead of only its starting position as source location for parser errors. + * Type Checker: Deprecate the ``years`` unit denomination and raise a warning for it (or an error as experimental 0.5.0 feature). + * Type Checker: Make literals (without explicit type casting) an error for tight packing as experimental 0.5.0 feature. + * Type Checker: Warn about wildcard tuple assignments (this will turn into an error with version 0.5.0). Bugfixes: - + * Type Checker: Show proper error when trying to ``emit`` a non-event. + * Type Checker: Warn about empty tuple components (this will turn into an error with version 0.5.0). ### 0.4.23 (2018-04-19) diff --git a/appveyor.yml b/appveyor.yml index 5fd85482..81ec711d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,10 +31,12 @@ branches: only: - release - develop -os: Visual Studio 2015 configuration: - RelWithDebInfo environment: + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 # This is used for pushing to solidity-test-bytecodes priv_key: secure: yYGwg4rhCdHfwuv2mFjaNEDwAx3IKUbp0D5fMGpaKefnfk+BiMS5bqSHRiOj91PZ91P9pUk2Vu+eNuS4hTFCf1zFGfrOhlJ4Ij0xSyU5m/LQr590Mo+f7W94Xc8ubgo6j2hp9qH/szTqTzmAkmxKO5TLlWjVzVny2t/s5o5UprLS1/MdzDNLjpVNXR03oKfdWUV9a2l6+PejXCbqyUCagh6BByZqeAPbDcil6eAfxu4EPX83Fuurof+KqFzIWycBG5qK1pTipn2pxiA0QKuUrD8y8VNL0S23NTgxoxSp7nPVMd3K0qRSzPM5lrqS7Z8i3evkVwPbuhu0gSiV08jGVahH2snQ3JGYsH2D4KmVn/xiVBeJ0lRplYlfZF0GUu7iJ+DDxi6wBPhW9A25/NyD/mx7Ub2dLheyWi8AjdSCzhfRD+4We8FQQeHRo3Q0kAohFmlCXdXhrcwOOloId8r6xYwg+hWxHTt2Oe9CKwXfmiPjgl/Gd6lYgLpyyfJ8drQ6tjO/pybLEa10v74qYNdVW5LaLIsRUM9Jm/FDVTrOGYtPndi87mF+/tBJIaXXNz0EMl5xvsKW0SBfUMV49zoDDKZZgWyO9U/cfViEUi7Sdn9QLsBWLZfSgBQNkq3WGZVKPq58OxEWT9dUghQHlSVh2qWF/NUx0TRBjiJl9JM56ENTMD00y18eDcXNCeLLVYB+R1axabUPdXivrO+BrWQK94IWxKEJ+YYN8WVJWAO5T/EBDKwgiXGneePwJ75WP7XCLtuYxqjC+CeW3xBVCzCEeZB/VVBvt7fhmtcoeZZ6tAS10h0yY5WWZ/EUVorj+c/FrMm7Nlpcrd1p4hciffePSLVg+yvy9/xTuM9trYWMgj4xcDQbYsaeItHO2Z3EiUoCgNdUw6rONiNwS/XBApWhCcklWm0/g62h2gOa7/hnKG6p2omQzYw+cOzWbF9+DBzoTSXXZXqbUshVee+CD+iYJKleGYSdbMdM89HW4HyskHk6HgM1ggE8CsgD1pMhXtqLTYZBlvsZCBkHPkD9NhGD2DtrNOmJOW8xwkL2/Il6roDF4n856XNdsjvd++rvQoKr58SkyApCJeCo3sfVres0W22g+7If2b2kWC4/DphrFkeaceFzJOctBUrwstvQBXIVOcadU978A3E7jvTaMR4JL9kC/iPOUVNjNRNM/gNvTlf3CIyMMszFeftjEBGnCZaSpht2RtNapRQQb6QPkOP88nufQVZq/TP1ECmvdTUWJ7kSnAupu6u8oH2x2IIm/KKeIwSYU5rGxjRb36DwgXCHcwfRYo3VNorwTeZGj4q1TSM9PuvgzNg//gKZW6VRa+HdNm/40ZGpDsOrr55tOBqfpq9k5RmevqW/OMZS3xUuArKdYLQY75t9eWcbHSgFN2ZY1KEdyEEvVKgs6Q4lEnSSulGxroRxTU5BOoA0V4tCeCUoSPD3FB93WsO9fBPzNsqOuBtDdIkApefzc1pT38uKpmVfggKUsoWUdqMXAWqCDWr2uw9EE900RJpEY6mIEWhkcro5LAMwaqByOGpqFFUkH+UWTC102eVHEmjxKpC6c6cSzoKKU6Ckd+jVRFO7TvmVe1MKCwjXj8lcAfAM2gQ+XehtrQdIBhAmCrnzurfz2u9tKVdpiADC1ig+kMs1/HX2713LYVXzDKdk+duQ94SVtGv9F2Iv+KN5oq4UFgll6VGt7GHsJOrYYf/wrOfB09IkpmjNygvcpmmSdcXXF8ulDD6KHTGEGUlFwLOpEwKx+zX2ZvviStHhN8KsoTKSVSueDmSSI63HdTS7FxfrHJc1yAzsdqEN5g5eV/z2Fn34qy64mdFSAZMF5zsbWZYFpc9ef3llF5aRcuD90JWT2VC7rB2jeGEtiwGkDlqKzxqRvJk06wTK6+n5RncN66bDaksulOPJMAR/bRW7dinV8T6yIvybuhqDetxJQP6eyAnW4xr1YxIAG4BXGZV6XAPTgOG2oGvMdncxkcLQHXVu07x39ySqP/m2MBxn0zF3DmaqrSPIRMhS8gG3d/23Jux3YHDEOBHjdJSdwqs5F5+QBFPV2rmJnpcSoW4d3M119XI20L914c62R7wY4e6+qmi3ydQU9g6p8psZgaE3TuMsyzX3k4C30nC/3gWT+zl253NjZwfbzIdHu5LWNDY9kEHtKzLP @@ -62,11 +64,12 @@ install: before_build: - if not exist build mkdir build - cd build - - cmake -G "Visual Studio 14 2015 Win64" .. -DTESTS=On + - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2015" ( cmake -G "Visual Studio 14 2015 Win64" .. -DTESTS=On ) + else ( cmake -G "Visual Studio 15 2017 Win64" .. -DTESTS=On ) build_script: - msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal - cd %APPVEYOR_BUILD_FOLDER% - - scripts\release.bat %CONFIGURATION% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" ( scripts\release.bat %CONFIGURATION% 2017 ) - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short test_script: @@ -4,12 +4,38 @@ defaults: filters: tags: only: /.*/ + - setup_prerelease_commit_hash: &setup_prerelease_commit_hash + name: Store commit hash and prerelease + command: | + if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi + echo -n "$CIRCLE_SHA1" > commit_hash.txt + - run_build: &run_build + name: Build + command: | + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo + make -j4 + - run_tests: &run_tests + name: Tests + command: scripts/tests.sh --junit_report test_results + - solc_artifact: &solc_artifact + path: build/solc/solc + destination: solc + - all_artifacts: &all_artifacts + root: build + paths: + - solc/solc + - test/soltest + - test/tools/solfuzzer version: 2 jobs: build_emscripten: docker: - image: trzeci/emscripten:sdk-tag-1.37.21-64bit + environment: + TERM: xterm steps: - checkout - restore_cache: @@ -42,6 +68,8 @@ jobs: test_emscripten_solcjs: docker: - image: trzeci/emscripten:sdk-tag-1.37.21-64bit + environment: + TERM: xterm steps: - checkout - attach_workspace: @@ -66,6 +94,8 @@ jobs: test_emscripten_external: docker: - image: trzeci/emscripten:sdk-tag-1.37.21-64bit + environment: + TERM: xterm steps: - checkout - attach_workspace: @@ -87,9 +117,11 @@ jobs: command: | . /usr/local/nvm/nvm.sh test/externalTests.sh /tmp/workspace/soljson.js || test/externalTests.sh /tmp/workspace/soljson.js - build_x86: + build_x86_linux: docker: - image: buildpack-deps:artful + environment: + TERM: xterm steps: - checkout - run: @@ -97,31 +129,37 @@ jobs: command: | apt-get -qq update apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libz3-dev + - run: *setup_prerelease_commit_hash + - run: *run_build + - store_artifacts: *solc_artifact + - persist_to_workspace: *all_artifacts + + build_x86_mac: + macos: + xcode: "9.0" + environment: + TERM: xterm + steps: + - checkout - run: - name: Store commit hash and prerelease - command: | - if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi - echo -n "$CIRCLE_SHA1" > commit_hash.txt - - run: - name: Build + name: Install build dependencies command: | - mkdir -p build - cd build - cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo - make -j4 - - store_artifacts: - path: build/solc/solc - destination: solc - - persist_to_workspace: - root: build - paths: - - solc/solc - - test/soltest - - test/tools/solfuzzer + brew update + brew upgrade + brew unlink python + brew install z3 + brew install boost + brew install cmake + - run: *setup_prerelease_commit_hash + - run: *run_build + - store_artifacts: *solc_artifact + - persist_to_workspace: *all_artifacts - test_x86: + test_x86_linux: docker: - image: buildpack-deps:artful + environment: + TERM: xterm steps: - checkout - attach_workspace: @@ -132,9 +170,28 @@ jobs: apt-get -qq update apt-get -qy install libz3-dev libleveldb1v5 - run: mkdir -p test_results + - run: *run_tests + - store_test_results: + path: test_results/ + + test_x86_mac: + macos: + xcode: "9.0" + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build - run: - name: Tests - command: scripts/tests.sh --junit_report test_results + name: Install dependencies + command: | + brew update + brew upgrade + brew unlink python + brew install z3 + - run: mkdir -p test_results + - run: *run_tests - store_test_results: path: test_results/ @@ -148,11 +205,7 @@ jobs: command: | apt-get -qq update apt-get -qy install python-sphinx - - run: - name: Store commit hash and prerelease - command: | - if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi - echo -n "$CIRCLE_SHA1" > commit_hash.txt + - run: *setup_prerelease_commit_hash - run: name: Build documentation command: ./scripts/docs.sh @@ -173,9 +226,14 @@ workflows: <<: *build_on_tags requires: - build_emscripten - - build_x86: *build_on_tags - - test_x86: + - build_x86_linux: *build_on_tags + - build_x86_mac: *build_on_tags + - test_x86_linux: + <<: *build_on_tags + requires: + - build_x86_linux + - test_x86_mac: <<: *build_on_tags requires: - - build_x86 + - build_x86_mac - docs: *build_on_tags diff --git a/cmake/jsoncpp.cmake b/cmake/jsoncpp.cmake index 6ddf4c74..3d6b37ed 100644 --- a/cmake/jsoncpp.cmake +++ b/cmake/jsoncpp.cmake @@ -6,17 +6,15 @@ else() set(JSONCPP_CMAKE_COMMAND ${CMAKE_COMMAND}) endif() -# Disable implicit fallthrough warning in jsoncpp for gcc >= 7 until the upstream handles it properly -if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0) - set(JSONCCP_EXTRA_FLAGS -Wno-implicit-fallthrough) -else() - set(JSONCCP_EXTRA_FLAGS "") -endif() - +include(GNUInstallDirs) set(prefix "${CMAKE_BINARY_DIR}/deps") -set(JSONCPP_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}jsoncpp${CMAKE_STATIC_LIBRARY_SUFFIX}") +set(JSONCPP_LIBRARY "${prefix}/${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}jsoncpp${CMAKE_STATIC_LIBRARY_SUFFIX}") set(JSONCPP_INCLUDE_DIR "${prefix}/include") +if(NOT MSVC) + set(JSONCPP_EXTRA_FLAGS "-std=c++11") +endif() + set(byproducts "") if(CMAKE_VERSION VERSION_GREATER 3.1) set(byproducts BUILD_BYPRODUCTS "${JSONCPP_LIBRARY}") @@ -25,9 +23,9 @@ endif() ExternalProject_Add(jsoncpp-project PREFIX "${prefix}" DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads" - DOWNLOAD_NAME jsoncpp-1.7.7.tar.gz - URL https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz - URL_HASH SHA256=087640ebcf7fbcfe8e2717a0b9528fff89c52fcf69fa2a18cc2b538008098f97 + DOWNLOAD_NAME jsoncpp-1.8.4.tar.gz + URL https://github.com/open-source-parsers/jsoncpp/archive/1.8.4.tar.gz + URL_HASH SHA256=c49deac9e0933bcb7044f08516861a2d560988540b23de2ac1ad443b219afdb6 CMAKE_COMMAND ${JSONCPP_CMAKE_COMMAND} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} @@ -36,7 +34,7 @@ ExternalProject_Add(jsoncpp-project -DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS} -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_PKGCONFIG_SUPPORT=OFF - -DCMAKE_CXX_FLAGS=${JSONCCP_EXTRA_FLAGS} + -DCMAKE_CXX_FLAGS=${JSONCPP_EXTRA_FLAGS} # Overwrite build and install commands to force Release build on MSVC. BUILD_COMMAND cmake --build <BINARY_DIR> --config Release INSTALL_COMMAND cmake --build <BINARY_DIR> --config Release --target install diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 98301fdc..8591a07f 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -62,7 +62,7 @@ The following elementary types exist: The following (fixed-size) array type exists: -- ``<type>[M]``: a fixed-length array of ``M`` elements, ``M > 0``, of the given type. +- ``<type>[M]``: a fixed-length array of ``M`` elements, ``M >= 0``, of the given type. The following non-fixed-size types exist: @@ -77,7 +77,7 @@ of them inside parentheses, separated by commas: - ``(T1,T2,...,Tn)``: tuple consisting of the types ``T1``, ..., ``Tn``, ``n >= 0`` -It is possible to form tuples of tuples, arrays of tuples and so on. +It is possible to form tuples of tuples, arrays of tuples and so on. It is also possible to form zero-tuples (where ``n == 0``). .. note:: Solidity supports all the types presented above with the same names with the exception of tuples. The ABI tuple type is utilised for encoding Solidity ``structs``. @@ -101,8 +101,8 @@ We distinguish static and dynamic types. Static types are encoded in-place and d * ``bytes`` * ``string`` * ``T[]`` for any ``T`` -* ``T[k]`` for any dynamic ``T`` and any ``k > 0`` -* ``(T1,...,Tk)`` if any ``Ti`` is dynamic for ``1 <= i <= k`` +* ``T[k]`` for any dynamic ``T`` and any ``k >= 0`` +* ``(T1,...,Tk)`` if ``Ti`` is dynamic for some ``1 <= i <= k`` All other types are called "static". @@ -117,16 +117,16 @@ on the type of ``X`` being - ``(T1,...,Tk)`` for ``k >= 0`` and any types ``T1``, ..., ``Tk`` - ``enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))`` + ``enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))`` - where ``X(i)`` is the ``ith`` component of the value, and + where ``X = (X(1), ..., X(k))`` and ``head`` and ``tail`` are defined for ``Ti`` being a static type as ``head(X(i)) = enc(X(i))`` and ``tail(X(i)) = ""`` (the empty string) and as - ``head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1))))`` + ``head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)) ))`` ``tail(X(i)) = enc(X(i))`` otherwise, i.e. if ``Ti`` is a dynamic type. @@ -144,7 +144,7 @@ on the type of ``X`` being - ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``): - ``enc(X) = enc(k) enc([X[1], ..., X[k]])`` + ``enc(X) = enc(k) enc([X[0], ..., X[k-1]])`` i.e. it is encoded as if it were an array of static size ``k``, prefixed with the number of elements. diff --git a/docs/assembly.rst b/docs/assembly.rst index 978e71e3..443cb7da 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -418,6 +418,9 @@ changes during the call, and thus references to local variables will be wrong. Labels ------ +.. note:: + Labels are deprecated. Please use functions, loops, if or switch statements instead. + Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses which can change easily. Solidity inline assembly provides labels to make the use of jumps easier. Note that labels are a low-level feature and it is possible to write @@ -519,6 +522,10 @@ is performed by replacing the variable's value on the stack by the new value. =: v // instruction style assignment, puts the result of sload(10) into v } +.. note:: + Instruction-style assignment is deprecated. + + If -- @@ -693,9 +700,9 @@ the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like ``7 y x add mul`` because in the first form, it is much easier to see which operand is used for which opcode. -The second goal is achieved by introducing a desugaring phase that only removes -the higher level constructs in a very regular way and still allows inspecting -the generated low-level assembly code. The only non-local operation performed +The second goal is achieved by compiling the +higher level constructs to bytecode in a very regular way. +The only non-local operation performed by the assembler is name lookup of user-defined identifiers (functions, variables, ...), which follow very simple and regular scoping rules and cleanup of local variables from the stack. @@ -716,8 +723,6 @@ keep track of the current so-called stack height. Since all local variables are removed at the end of a block, the stack height before and after the block should be the same. If this is not the case, a warning is issued. -Why do we use higher-level constructs like ``switch``, ``for`` and functions: - Using ``switch``, ``for`` and functions, it should be possible to write complex code without using ``jump`` or ``jumpi`` manually. This makes it much easier to analyze the control flow, which allows for improved formal @@ -726,13 +731,11 @@ verification and optimization. Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. The position of all local variables on the stack needs to be known, otherwise neither references to local variables nor removing local variables automatically -from the stack at the end of a block will work properly. The desugaring -mechanism correctly inserts operations at unreachable blocks that adjust the -stack height properly in case of jumps that do not have a continuing control flow. +from the stack at the end of a block will work properly. Example: -We will follow an example compilation from Solidity to desugared assembly. +We will follow an example compilation from Solidity to assembly. We consider the runtime bytecode of the following Solidity program:: pragma solidity ^0.4.16; @@ -772,99 +775,9 @@ The following assembly will be generated:: } } -After the desugaring phase it looks as follows:: - - { - mstore(0x40, 0x60) - { - let $0 := div(calldataload(0), exp(2, 226)) - jumpi($case1, eq($0, 0xb3de648b)) - jump($caseDefault) - $case1: - { - // the function call - we put return label and arguments on the stack - $ret1 calldataload(4) jump(f) - // This is unreachable code. Opcodes are added that mirror the - // effect of the function on the stack height: Arguments are - // removed and return values are introduced. - pop pop - let r := 0 - $ret1: // the actual return point - $ret2 0x20 jump($allocate) - pop pop let ret := 0 - $ret2: - mstore(ret, r) - return(ret, 0x20) - // although it is useless, the jump is automatically inserted, - // since the desugaring process is a purely syntactic operation that - // does not analyze control-flow - jump($endswitch) - } - $caseDefault: - { - revert(0, 0) - jump($endswitch) - } - $endswitch: - } - jump($afterFunction) - allocate: - { - // we jump over the unreachable code that introduces the function arguments - jump($start) - let $retpos := 0 let size := 0 - $start: - // output variables live in the same scope as the arguments and is - // actually allocated. - let pos := 0 - { - pos := mload(0x40) - mstore(0x40, add(pos, size)) - } - // This code replaces the arguments by the return values and jumps back. - swap1 pop swap1 jump - // Again unreachable code that corrects stack height. - 0 0 - } - f: - { - jump($start) - let $retpos := 0 let x := 0 - $start: - let y := 0 - { - let i := 0 - $for_begin: - jumpi($for_end, iszero(lt(i, x))) - { - y := mul(2, y) - } - $for_continue: - { i := add(i, 1) } - jump($for_begin) - $for_end: - } // Here, a pop instruction will be inserted for i - swap1 pop swap1 jump - 0 0 - } - $afterFunction: - stop - } - - -Assembly happens in four stages: -1. Parsing -2. Desugaring (removes switch, for and functions) -3. Opcode stream generation -4. Bytecode generation - -We will specify steps one to three in a pseudo-formal way. More formal -specifications will follow. - - -Parsing / Grammar ------------------ +Assembly Grammar +---------------- The tasks of the parser are the following: @@ -922,160 +835,3 @@ Grammar:: StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ - - -Desugaring ----------- - -An AST transformation removes for, switch and function constructs. The result -is still parseable by the same parser, but it will not use certain constructs. -If jumpdests are added that are only jumped to and not continued at, information -about the stack content is added, unless no local variables of outer scopes are -accessed or the stack height is the same as for the previous instruction. - -Pseudocode:: - - desugar item: AST -> AST = - match item { - AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) -> - <name>: - { - jump($<name>_start) - let $retPC := 0 let argn := 0 ... let arg1 := 0 - $<name>_start: - let ret1 := 0 ... let retm := 0 - { desugar(body) } - swap and pop items so that only ret1, ... retm, $retPC are left on the stack - jump - 0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC - } - AssemblyFor('for' { init } condition post body) -> - { - init // cannot be its own block because we want variable scope to extend into the body - // find I such that there are no labels $forI_* - $forI_begin: - jumpi($forI_end, iszero(condition)) - { body } - $forI_continue: - { post } - jump($forI_begin) - $forI_end: - } - 'break' -> - { - // find nearest enclosing scope with label $forI_end - pop all local variables that are defined at the current point - but not at $forI_end - jump($forI_end) - 0 (as many as variables were removed above) - } - 'continue' -> - { - // find nearest enclosing scope with label $forI_continue - pop all local variables that are defined at the current point - but not at $forI_continue - jump($forI_continue) - 0 (as many as variables were removed above) - } - AssemblySwitch(switch condition cases ( default: defaultBlock )? ) -> - { - // find I such that there is no $switchI* label or variable - let $switchI_value := condition - for each of cases match { - case val: -> jumpi($switchI_caseJ, eq($switchI_value, val)) - } - if default block present: -> - { defaultBlock jump($switchI_end) } - for each of cases match { - case val: { body } -> $switchI_caseJ: { body jump($switchI_end) } - } - $switchI_end: - } - FunctionalAssemblyExpression( identifier(arg1, arg2, ..., argn) ) -> - { - if identifier is function <name> with n args and m ret values -> - { - // find I such that $funcallI_* does not exist - $funcallI_return argn ... arg2 arg1 jump(<name>) - pop (n + 1 times) - if the current context is `let (id1, ..., idm) := f(...)` -> - let id1 := 0 ... let idm := 0 - $funcallI_return: - else -> - 0 (m times) - $funcallI_return: - turn the functional expression that leads to the function call - into a statement stream - } - else -> desugar(children of node) - } - default node -> - desugar(children of node) - } - -Opcode Stream Generation ------------------------- - -During opcode stream generation, we keep track of the current stack height -in a counter, -so that accessing stack variables by name is possible. The stack height is modified with every opcode -that modifies the stack and with every label that is annotated with a stack -adjustment. Every time a new -local variable is introduced, it is registered together with the current -stack height. If a variable is accessed (either for copying its value or for -assignment), the appropriate ``DUP`` or ``SWAP`` instruction is selected depending -on the difference between the current stack height and the -stack height at the point the variable was introduced. - -Pseudocode:: - - codegen item: AST -> opcode_stream = - match item { - AssemblyBlock({ items }) -> - join(codegen(item) for item in items) - if last generated opcode has continuing control flow: - POP for all local variables registered at the block (including variables - introduced by labels) - warn if the stack height at this point is not the same as at the start of the block - Identifier(id) -> - lookup id in the syntactic stack of blocks - match type of id - Local Variable -> - DUPi where i = 1 + stack_height - stack_height_of_identifier(id) - Label -> - // reference to be resolved during bytecode generation - PUSH<bytecode position of label> - SubAssembly -> - PUSH<bytecode position of subassembly data> - FunctionalAssemblyExpression(id ( arguments ) ) -> - join(codegen(arg) for arg in arguments.reversed()) - id (which has to be an opcode, might be a function name later) - AssemblyLocalDefinition(let (id1, ..., idn) := expr) -> - register identifiers id1, ..., idn as locals in current block at current stack height - codegen(expr) - assert that expr returns n items to the stack - FunctionalAssemblyAssignment((id1, ..., idn) := expr) -> - lookup id1, ..., idn in the syntactic stack of blocks, assert that they are variables - codegen(expr) - for j = n, ..., i: - SWAPi where i = 1 + stack_height - stack_height_of_identifier(idj) - POP - AssemblyAssignment(=: id) -> - look up id in the syntactic stack of blocks, assert that it is a variable - SWAPi where i = 1 + stack_height - stack_height_of_identifier(id) - POP - LabelDefinition(name:) -> - JUMPDEST - NumberLiteral(num) -> - PUSH<num interpreted as decimal and right-aligned> - HexLiteral(lit) -> - PUSH32<lit interpreted as hex and left-aligned> - StringLiteral(lit) -> - PUSH32<lit utf-8 encoded and left-aligned> - SubAssembly(assembly <name> block) -> - append codegen(block) at the end of the code - dataSize(<name>) -> - assert that <name> is a subassembly -> - PUSH32<size of code generated from subassembly <name>> - linkerSymbol(<lit>) -> - PUSH32<zeros> and append position to linker table - } diff --git a/docs/conf.py b/docs/conf.py index ca8c0fec..7e107f2a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,8 @@ import re # documentation root, use os.path.abspath to make it absolute, like shown here. def setup(sphinx): - sys.path.insert(0, os.path.abspath('./utils')) + thisdir = os.path.dirname(os.path.realpath(__file__)) + sys.path.insert(0, thisdir + '/utils') from SolidityLexer import SolidityLexer sphinx.add_lexer('Solidity', SolidityLexer()) @@ -50,7 +51,7 @@ master_doc = 'index' # General information about the project. project = 'Solidity' -copyright = '2016-2017, Ethereum' +copyright = '2016-2018, Ethereum' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/contracts.rst b/docs/contracts.rst index a1f2895c..487e80ae 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -24,8 +24,8 @@ Creating contracts programatically on Ethereum is best done via using the JavaSc As of today it has a method called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract>`_ to facilitate contract creation. -When a contract is created, its constructor (a function with the same -name as the contract) is executed once. +When a contract is created, its constructor (a function declared with the +``constructor`` keyword) is executed once. A constructor is optional. Only one constructor is allowed, and this means overloading is not supported. @@ -473,7 +473,7 @@ The following statements are considered modifying the state: } .. note:: - ``constant`` on functions is an alias to ``view``, but this is deprecated and is planned to be dropped in version 0.5.0. + ``constant`` on functions is an alias to ``view``, but this is deprecated and will be dropped in version 0.5.0. .. note:: Getter methods are marked ``view``. @@ -841,10 +841,10 @@ Details are given in the following example. :: - pragma solidity ^0.4.16; + pragma solidity ^0.4.22; contract owned { - function owned() { owner = msg.sender; } + constructor() { owner = msg.sender; } address owner; } @@ -875,7 +875,7 @@ Details are given in the following example. // also a base class of `mortal`, yet there is only a single // instance of `owned` (as for virtual inheritance in C++). contract named is owned, mortal { - function named(bytes32 name) { + constructor(bytes32 name) { Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); NameReg(config.lookup(1)).register(name); } @@ -913,10 +913,10 @@ Note that above, we call ``mortal.kill()`` to "forward" the destruction request. The way this is done is problematic, as seen in the following example:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.22; contract owned { - function owned() public { owner = msg.sender; } + constructor() public { owner = msg.sender; } address owner; } @@ -942,10 +942,10 @@ derived override, but this function will bypass ``Base1.kill``, basically because it does not even know about ``Base1``. The way around this is to use ``super``:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.22; contract owned { - function owned() public { owner = msg.sender; } + constructor() public { owner = msg.sender; } address owner; } @@ -982,7 +982,7 @@ virtual method lookup. Constructors ============ -A constructor is an optional function declared with the ``constructor`` keyword which is executed upon contract creation. +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 {}``. @@ -1030,10 +1030,11 @@ A constructor set as ``internal`` causes the contract to be marked as :ref:`abst Arguments for Base Constructors =============================== -Derived contracts need to provide all arguments needed for -the base constructors. This can be done in two ways:: +The constructors of all the base contracts will be called following the +linearization rules explained below. If the base constructors have arguments, +derived contracts need to specify all of them. This can be done in two ways:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.22; contract Base { uint x; @@ -1059,6 +1060,9 @@ derived contract. Arguments have to be given either in the inheritance list or in modifier-style in the derived constuctor. Specifying arguments in both places is an error. +If a derived contract doesn't specify the arguments to all of its base +contracts' constructors, it will be abstract. + .. index:: ! inheritance;multiple, ! linearization, ! C3 linearization Multiple Inheritance and Linearization @@ -1066,12 +1070,15 @@ Multiple Inheritance and Linearization Languages that allow multiple inheritance have to deal with several problems. One is the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_. -Solidity follows the path of Python and uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_" +Solidity is similar to Python in that it uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_" to force a specific order in the DAG of base classes. This results in the desirable property of monotonicity but disallows some inheritance graphs. Especially, the order in which the base classes are given in the ``is`` directive is -important. In the following code, Solidity will give the +important: You have to list the direct base contracts +in the order from "most base-like" to "most derived". +Note that this order is different from the one used in Python. +In the following code, Solidity will give the error "Linearization of inheritance graph impossible". :: @@ -1089,9 +1096,6 @@ The reason for this is that ``C`` requests ``X`` to override ``A`` requests to override ``X``, which is a contradiction that cannot be resolved. -A simple rule to remember is to specify the base classes in -the order from "most base-like" to "most derived". - Inheriting Different Kinds of Members of the Same Name ====================================================== @@ -1139,7 +1143,7 @@ Example of a Function Type (a variable declaration, where the variable is of typ function(address) external returns (address) foo; -Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and +Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/Template_method_pattern>`_ and removing code duplication. .. index:: ! contract;interface, ! interface contract diff --git a/docs/control-structures.rst b/docs/control-structures.rst index f3c351dd..f18e1e10 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -293,12 +293,7 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di // Common trick to swap values -- does not work for non-value storage types. (x, y) = (y, x); // Components can be left out (also for variable declarations). - // If the tuple ends in an empty component, - // the rest of the values are discarded. - (data.length,) = f(); // Sets the length to 7 - // The same can be done on the left side. - // If the tuple begins in an empty component, the beginning values are discarded. - (,data[3]) = f(); // Sets data[3] to 2 + (data.length,,) = f(); // Sets the length to 7 // Components can only be left out at the left-hand-side of assignments, with // one exception: (x,) = (1,); @@ -307,6 +302,11 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di } } +.. note:: + Prior to version 0.4.24 it was possible to assign to tuples of smaller size, either + filling up on the left or on the right side (which ever was empty). This is + now deprecated, both sides have to have the same number of components. + Complications for Arrays and Structs ------------------------------------ diff --git a/docs/grammar.txt b/docs/grammar.txt index b4ca5ca9..565db9a4 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -16,7 +16,7 @@ ContractPart = StateVariableDeclaration | UsingForDeclaration InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )? -StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )? Identifier ('=' Expression)? ';' +StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )* Identifier ('=' Expression)? ';' UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';' StructDefinition = 'struct' Identifier '{' ( VariableDeclaration ';' (VariableDeclaration ';')* ) '}' diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 6726ded9..05ee0748 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -35,7 +35,7 @@ npm / Node.js ============= Use `npm` for a convenient and portable way to install `solcjs`, a Solidity compiler. The -`solcjs` program has less features than all options further down this page. Our +`solcjs` program has fewer features than all options further down this page. Our :ref:`commandline-compiler` documentation assumes you are using the full-featured compiler, `solc`. So if you install `solcjs` from `npm` then you will stop reading the documentation here and then continue to `solc-js <https://github.com/ethereum/solc-js>`_. @@ -203,19 +203,38 @@ Prerequisites - Windows You will need to install the following dependencies for Windows builds of Solidity: -+------------------------------+-------------------------------------------------------+ -| Software | Notes | -+==============================+=======================================================+ -| `Git for Windows`_ | Command-line tool for retrieving source from Github. | -+------------------------------+-------------------------------------------------------+ -| `CMake`_ | Cross-platform build file generator. | -+------------------------------+-------------------------------------------------------+ -| `Visual Studio 2015`_ | C++ compiler and dev environment. | -+------------------------------+-------------------------------------------------------+ ++-----------------------------------+-------------------------------------------------------+ +| Software | Notes | ++===================================+=======================================================+ +| `Git for Windows`_ | Command-line tool for retrieving source from Github. | ++-----------------------------------+-------------------------------------------------------+ +| `CMake`_ | Cross-platform build file generator. | ++-----------------------------------+-------------------------------------------------------+ +| `Visual Studio 2017 Build Tools`_ | C++ compiler | ++-----------------------------------+-------------------------------------------------------+ +| `Visual Studio 2017`_ (Optional) | C++ compiler and dev environment. | ++-----------------------------------+-------------------------------------------------------+ + +If you've already had one IDE and only need compiler and libraries, +you could install Visual Studio 2017 Build Tools. + +Visual Studio 2017 provides both IDE and necessary compiler and libraries. +So if you have not got an IDE and prefer to develop solidity, Visual Studio 2017 +may be an choice for you to get everything setup easily. + +Here is the list of components that should be installed +in Visual Studio 2017 Build Tools or Visual Studio 2017: + +* Visual Studio C++ core features +* VC++ 2017 v141 toolset (x86,x64) +* Windows Universal CRT SDK +* Windows 8.1 SDK +* C++/CLI support .. _Git for Windows: https://git-scm.com/download/win .. _CMake: https://cmake.org/download/ -.. _Visual Studio 2015: https://www.visualstudio.com/products/vs-2015-product-editions +.. _Visual Studio 2017: https://www.visualstudio.com/vs/ +.. _Visual Studio 2017 Build Tools: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017 External Dependencies @@ -263,7 +282,7 @@ And even for Windows: mkdir build cd build - cmake -G "Visual Studio 14 2015 Win64" .. + cmake -G "Visual Studio 15 2017 Win64" .. This latter set of instructions should result in the creation of **solidity.sln** in that build directory. Double-clicking on that file diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 84b1fff8..d1789c44 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -25,7 +25,7 @@ Storage storedData = x; } - function get() public constant returns (uint) { + function get() public view returns (uint) { return storedData; } } diff --git a/docs/julia.rst b/docs/julia.rst index 078bc55b..bc918117 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -306,12 +306,20 @@ Type Conversion Functions JULIA has no support for implicit type conversion and therefore functions exists to provide explicit conversion. When converting a larger type to a shorter type a runtime exception can occur in case of an overflow. -The following type conversion functions must be available: -- ``u32tobool(x:u32) -> y:bool`` -- ``booltou32(x:bool) -> y:u32`` -- ``u32tou64(x:u32) -> y:u64`` -- ``u64tou32(x:u64) -> y:u32`` -- etc. (TBD) +Truncating conversions are supported between the following types: + - ``bool`` + - ``u32`` + - ``u64`` + - ``u256`` + - ``s256`` + +For each of these a type conversion function exists having the prototype in the form of ``<input_type>to<output_type>(x:<input_type>) -> y:<output_type>``, +such as ``u32tobool(x:u32) -> y:bool``, ``u256tou32(x:u256) -> y:u32`` or ``s256tou256(x:s256) -> y:u256``. + +.. note:: + + ``u32tobool(x:u32) -> y:bool`` can be implemented as ``y := not(iszerou256(x))`` and + ``booltou32(x:bool) -> y:u32`` can be implemented as ``switch x case true:bool { y := 1:u32 } case false:bool { y := 0:u32 }`` Low-level Functions ------------------- @@ -319,6 +327,16 @@ Low-level Functions The following functions must be available: +---------------------------------------------------------------------------------------------------------------+ +| *Logic* | ++---------------------------------------------+-----------------------------------------------------------------+ +| not(x:bool) -> z:bool | logical not | ++---------------------------------------------+-----------------------------------------------------------------+ +| and(x:bool, y:bool) -> z:bool | logical and | ++---------------------------------------------+-----------------------------------------------------------------+ +| or(x:bool, y:bool) -> z:bool | logical or | ++---------------------------------------------+-----------------------------------------------------------------+ +| xor(x:bool, y:bool) -> z:bool | xor | ++---------------------------------------------+-----------------------------------------------------------------+ | *Arithmetics* | +---------------------------------------------+-----------------------------------------------------------------+ | addu256(x:u256, y:u256) -> z:u256 | x + y | @@ -343,15 +361,19 @@ The following functions must be available: +---------------------------------------------+-----------------------------------------------------------------+ | mulmodu256(x:u256, y:u256, m:u256) -> z:u256| (x * y) % m with arbitrary precision arithmetics | +---------------------------------------------+-----------------------------------------------------------------+ -| ltu256(x:u256, y:u256) -> z:bool | 1 if x < y, 0 otherwise | +| ltu256(x:u256, y:u256) -> z:bool | true if x < y, false otherwise | +---------------------------------------------+-----------------------------------------------------------------+ -| gtu256(x:u256, y:u256) -> z:bool | 1 if x > y, 0 otherwise | +| gtu256(x:u256, y:u256) -> z:bool | true if x > y, false otherwise | +---------------------------------------------+-----------------------------------------------------------------+ -| sltu256(x:s256, y:s256) -> z:bool | 1 if x < y, 0 otherwise, for signed numbers in two's complement | +| sltu256(x:s256, y:s256) -> z:bool | true if x < y, false otherwise | +| | (for signed numbers in two's complement) | +---------------------------------------------+-----------------------------------------------------------------+ -| sgtu256(x:s256, y:s256) -> z:bool | 1 if x > y, 0 otherwise, for signed numbers in two's complement | +| sgtu256(x:s256, y:s256) -> z:bool | true if x > y, false otherwise | +| | (for signed numbers in two's complement) | +---------------------------------------------+-----------------------------------------------------------------+ -| equ256(x:u256, y:u256) -> z:bool | 1 if x == y, 0 otherwise | +| equ256(x:u256, y:u256) -> z:bool | true if x == y, false otherwise | ++---------------------------------------------+-----------------------------------------------------------------+ +| iszerou256(x:u256) -> z:bool | true if x == 0, false otherwise | +---------------------------------------------+-----------------------------------------------------------------+ | notu256(x:u256) -> z:u256 | ~x, every bit of x is negated | +---------------------------------------------+-----------------------------------------------------------------+ @@ -405,10 +427,6 @@ The following functions must be available: | insize:u256, out:u256, | but also keep ``caller`` | | outsize:u256) -> r:u256 | and ``callvalue`` | +---------------------------------------------+-----------------------------------------------------------------+ -| stop() | stop execution, identical to return(0,0) | -| | Perhaps it would make sense retiring this as it equals to | -| | return(0,0). It can be an optimisation by the EVM backend. | -+---------------------------------------------+-----------------------------------------------------------------+ | abort() | abort (equals to invalid instruction on EVM) | +---------------------------------------------+-----------------------------------------------------------------+ | return(p:u256, s:u256) | end execution, return data mem[p..(p+s)) | @@ -473,6 +491,8 @@ The following functions must be available: +---------------------------------------------+-----------------------------------------------------------------+ | *Others* | +---------------------------------------------+-----------------------------------------------------------------+ +| discard(unused:bool) | discard value | ++---------------------------------------------+-----------------------------------------------------------------+ | discardu256(unused:u256) | discard value | +---------------------------------------------+-----------------------------------------------------------------+ | splitu256tou64(x:u256) -> (x1:u64, x2:u64, | split u256 to four u64's | @@ -481,7 +501,7 @@ The following functions must be available: | combineu64tou256(x1:u64, x2:u64, x3:u64, | combine four u64's into a single u256 | | x4:u64) -> (x:u256) | | +---------------------------------------------+-----------------------------------------------------------------+ -| sha3(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) | +| keccak256(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) | +---------------------------------------------+-----------------------------------------------------------------+ Backends diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..0607b1ef --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme>=0.3.1 diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 3e1c3a12..4133edb1 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -120,7 +120,7 @@ Gas Limit and Loops Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due to normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete -contract to be stalled at a certain point. This may not apply to ``constant`` functions that are only executed +contract to be stalled at a certain point. This may not apply to ``view`` functions that are only executed to read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operations and stall those. Please be explicit about such cases in the documentation of your contracts. diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 546767e4..f6038f7d 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -66,7 +66,7 @@ of votes. Proposal[] public proposals; /// Create a new ballot to choose one of `proposalNames`. - function Ballot(bytes32[] proposalNames) public { + constructor(bytes32[] proposalNames) public { chairperson = msg.sender; voters[chairperson].weight = 1; @@ -256,7 +256,7 @@ activate themselves. /// Create a simple auction with `_biddingTime` /// seconds bidding time on behalf of the /// beneficiary address `_beneficiary`. - function SimpleAuction( + constructor( uint _biddingTime, address _beneficiary ) public { @@ -418,7 +418,7 @@ high or low invalid bids. modifier onlyBefore(uint _time) { require(now < _time); _; } modifier onlyAfter(uint _time) { require(now > _time); _; } - function BlindAuction( + constructor( uint _biddingTime, uint _revealTime, address _beneficiary @@ -553,7 +553,7 @@ Safe Remote Purchase // Ensure that `msg.value` is an even number. // Division will truncate if it is an odd number. // Check via multiplication that it wasn't an odd number. - function Purchase() public payable { + constructor() public payable { seller = msg.sender; value = msg.value / 2; require((2 * value) == msg.value, "Value has to be even."); @@ -602,7 +602,7 @@ Safe Remote Purchase { emit Aborted(); state = State.Inactive; - seller.transfer(this.balance); + seller.transfer(address(this).balance); } /// Confirm the purchase as buyer. @@ -637,7 +637,7 @@ Safe Remote Purchase // block the refund - the withdraw pattern should be used. buyer.transfer(value); - seller.transfer(this.balance); + seller.transfer(address(this).balance); } } diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index d57f1703..7a6317eb 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -129,7 +129,7 @@ Structs are custom defined types that can group several variables (see Enum Types ========== -Enums can be used to create custom types with a finite set of values (see +Enums can be used to create custom types with a finite set of 'constant values' (see :ref:`enums` in types section). :: diff --git a/docs/style-guide.rst b/docs/style-guide.rst index ee1ea4bd..6b28f2ab 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -117,7 +117,7 @@ No:: Maximum Line Length =================== -Keeping lines under the `PEP 8 recommendation <https://www.python.org/dev/peps/pep-0008/#maximum-line-length>`_ of 79 (or 99) +Keeping lines under the `PEP 8 recommendation <https://www.python.org/dev/peps/pep-0008/#maximum-line-length>`_ to a maximum of 79 (or 99) characters helps readers easily parse the code. Wrapped lines should conform to the following guidelines. @@ -269,7 +269,7 @@ Functions should be grouped according to their visibility and ordered: - internal - private -Within a grouping, place the ``constant`` functions last. +Within a grouping, place the ``view`` and ``pure`` functions last. Yes:: @@ -285,7 +285,10 @@ Yes:: // External functions // ... - // External functions that are constant + // External functions that are view + // ... + + // External functions that are pure // ... // Public functions diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 51f7b9f3..a6f8ca87 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -31,6 +31,9 @@ because of `leap seconds <https://en.wikipedia.org/wiki/Leap_second>`_. Due to the fact that leap seconds cannot be predicted, an exact calendar library has to be updated by an external oracle. +.. note:: + The suffix ``years`` has been deprecated due to the reasons above. + These suffixes cannot be applied to variables. If you want to interpret some input variable in e.g. days, you can do it in the following way:: @@ -117,11 +120,11 @@ Error Handling -------------- ``assert(bool condition)``: - throws if the condition is not met - to be used for internal errors. + invalidates the transaction if the condition is not met - to be used for internal errors. ``require(bool condition)``: - throws if the condition is not met - to be used for errors in inputs or external components. + reverts if the condition is not met - to be used for errors in inputs or external components. ``require(bool condition, string message)``: - throws if the condition is not met - to be used for errors in inputs or external components. Also provides an error message. + reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message. ``revert()``: abort execution and revert state changes ``revert(string reason)``: diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index df30b6b4..1d7cb97b 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -14,7 +14,9 @@ Using the Commandline Compiler One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler. Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage. -If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``. +If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``. + +Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. By default, the optimizer will optimize the contract for 200 runs. If you want to optimize for initial contract deployment and get the smallest output, set it to ``--runs=1``. If you expect many transactions and don't care for higher deployment cost and output size, set ``--runs`` to a high number. The commandline compiler will automatically read imported files from the filesystem, but it is also possible to provide path redirects using ``prefix=path`` in the following way: @@ -96,10 +98,13 @@ Input Description { // Optional: Sorted list of remappings remappings: [ ":g/dir" ], - // Optional: Optimizer settings (enabled defaults to false) + // Optional: Optimizer settings optimizer: { + // disabled by default enabled: true, - runs: 500 + // Optimize for how many times you intend to run the code. + // Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage. + runs: 200 }, evmVersion: "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium or constantinople // Metadata settings (optional) diff --git a/libdevcore/JSON.cpp b/libdevcore/JSON.cpp index d99b3bc6..6317cc89 100644 --- a/libdevcore/JSON.cpp +++ b/libdevcore/JSON.cpp @@ -28,8 +28,8 @@ using namespace std; static_assert( - (JSONCPP_VERSION_MAJOR == 1) && (JSONCPP_VERSION_MINOR == 7) && (JSONCPP_VERSION_PATCH == 7), - "Unexpected jsoncpp version: " JSONCPP_VERSION_STRING ". Expecting 1.7.7." + (JSONCPP_VERSION_MAJOR == 1) && (JSONCPP_VERSION_MINOR == 8) && (JSONCPP_VERSION_PATCH == 4), + "Unexpected jsoncpp version: " JSONCPP_VERSION_STRING ". Expecting 1.8.4." ); namespace dev diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 367c6daa..1ed9b859 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -47,8 +47,8 @@ class Assembly public: Assembly() {} - AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } - AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } + AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } + AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } /// Returns a tag identified by the given name. Creates it if it does not yet exist. AssemblyItem namedTag(std::string const& _name); AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 5af618ff..a3a4d6b6 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -26,27 +26,35 @@ using namespace std; using namespace dev; using namespace dev::eth; +static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide"); + AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const { assertThrow(data() < (u256(1) << 64), Exception, "Tag already has subassembly set."); - assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + size_t tag = size_t(u256(data()) & 0xffffffffffffffffULL); AssemblyItem r = *this; r.m_type = PushTag; - r.setPushTagSubIdAndTag(_subId, size_t(data())); + r.setPushTagSubIdAndTag(_subId, tag); return r; } pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); - return make_pair(size_t((data()) / (u256(1) << 64)) - 1, size_t(data())); + u256 combined = u256(data()); + size_t subId = size_t((combined >> 64) - 1); + size_t tag = size_t(combined & 0xffffffffffffffffULL); + return make_pair(subId, tag); } void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); - setData(_tag + (u256(_subId + 1) << 64)); + u256 data = _tag; + if (_subId != size_t(-1)) + data |= (u256(_subId) + 1) << 64; + setData(data); } unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt index cfd59dff..86192c1b 100644 --- a/libevmasm/CMakeLists.txt +++ b/libevmasm/CMakeLists.txt @@ -2,4 +2,4 @@ file(GLOB sources "*.cpp") file(GLOB headers "*.h") add_library(evmasm ${sources} ${headers}) -target_link_libraries(evmasm PUBLIC jsoncpp devcore) +target_link_libraries(evmasm PUBLIC devcore) diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 9b60b26b..f0deb387 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -67,6 +67,7 @@ public: explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value): m_params(_params), m_value(_value) {} + virtual ~ConstantOptimisationMethod() = default; virtual bigint gasNeeded() const = 0; /// Executes the method, potentially appending to the assembly and returns a vector of /// assembly items the constant should be relpaced with in one sweep. diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index be788ddb..dc116f88 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -39,7 +39,7 @@ enum class Instruction: uint8_t { STOP = 0x00, ///< halts execution ADD, ///< addition operation - MUL, ///< mulitplication operation + MUL, ///< multiplication operation SUB, ///< subtraction operation DIV, ///< integer division operation SDIV, ///< signed integer division operation @@ -50,11 +50,11 @@ enum class Instruction: uint8_t EXP, ///< exponential operation SIGNEXTEND, ///< extend length of signed integer - LT = 0x10, ///< less-than comparision - GT, ///< greater-than comparision - SLT, ///< signed less-than comparision - SGT, ///< signed greater-than comparision - EQ, ///< equality comparision + LT = 0x10, ///< less-than comparison + GT, ///< greater-than comparison + SLT, ///< signed less-than comparison + SGT, ///< signed greater-than comparison + EQ, ///< equality comparison ISZERO, ///< simple not operator AND, ///< bitwise AND operation OR, ///< bitwise OR operation @@ -293,7 +293,7 @@ struct InstructionInfo /// Information on all the instructions. InstructionInfo instructionInfo(Instruction _inst); -/// check whether instructions exists +/// check whether instructions exists. bool isValidInstruction(Instruction _inst); /// Convert from string mnemonic to Instruction type. diff --git a/libevmasm/PathGasMeter.cpp b/libevmasm/PathGasMeter.cpp index 3fe682b7..cdadba76 100644 --- a/libevmasm/PathGasMeter.cpp +++ b/libevmasm/PathGasMeter.cpp @@ -43,7 +43,7 @@ GasMeter::GasConsumption PathGasMeter::estimateMax( auto path = unique_ptr<GasPath>(new GasPath()); path->index = _startIndex; path->state = _state->copy(); - m_queue.push_back(move(path)); + queue(move(path)); GasMeter::GasConsumption gas; while (!m_queue.empty() && !gas.isInfinite) @@ -51,12 +51,23 @@ GasMeter::GasConsumption PathGasMeter::estimateMax( return gas; } +void PathGasMeter::queue(std::unique_ptr<GasPath>&& _newPath) +{ + if ( + m_highestGasUsagePerJumpdest.count(_newPath->index) && + _newPath->gas < m_highestGasUsagePerJumpdest.at(_newPath->index) + ) + return; + m_highestGasUsagePerJumpdest[_newPath->index] = _newPath->gas; + m_queue[_newPath->index] = move(_newPath); +} + GasMeter::GasConsumption PathGasMeter::handleQueueItem() { assertThrow(!m_queue.empty(), OptimizerException, ""); - unique_ptr<GasPath> path = move(m_queue.back()); - m_queue.pop_back(); + unique_ptr<GasPath> path = move(m_queue.rbegin()->second); + m_queue.erase(--m_queue.end()); shared_ptr<KnownState> state = path->state; GasMeter meter(state, m_evmVersion, path->largestMemoryAccess); @@ -117,7 +128,7 @@ GasMeter::GasConsumption PathGasMeter::handleQueueItem() newPath->largestMemoryAccess = meter.largestMemoryAccess(); newPath->state = state->copy(); newPath->visitedJumpdests = path->visitedJumpdests; - m_queue.push_back(move(newPath)); + queue(move(newPath)); } if (branchStops) diff --git a/libevmasm/PathGasMeter.h b/libevmasm/PathGasMeter.h index 2527d7fb..9537b176 100644 --- a/libevmasm/PathGasMeter.h +++ b/libevmasm/PathGasMeter.h @@ -58,9 +58,17 @@ public: GasMeter::GasConsumption estimateMax(size_t _startIndex, std::shared_ptr<KnownState> const& _state); private: + /// Adds a new path item to the queue, but only if we do not already have + /// a higher gas usage at that point. + /// This is not exact as different state might influence higher gas costs at a later + /// point in time, but it greatly reduces computational overhead. + void queue(std::unique_ptr<GasPath>&& _newPath); GasMeter::GasConsumption handleQueueItem(); - std::vector<std::unique_ptr<GasPath>> m_queue; + /// Map of jumpdest -> gas path, so not really a queue. We only have one queued up + /// item per jumpdest, because of the behaviour of `queue` above. + std::map<size_t, std::unique_ptr<GasPath>> m_queue; + std::map<size_t, GasMeter::GasConsumption> m_highestGasUsagePerJumpdest; std::map<u256, size_t> m_tagPositions; AssemblyItems const& m_items; solidity::EVMVersion m_evmVersion; diff --git a/libevmasm/PeepholeOptimiser.h b/libevmasm/PeepholeOptimiser.h index a74cc8b3..a651143d 100644 --- a/libevmasm/PeepholeOptimiser.h +++ b/libevmasm/PeepholeOptimiser.h @@ -34,6 +34,7 @@ using AssemblyItems = std::vector<AssemblyItem>; class PeepholeOptimisationMethod { public: + virtual ~PeepholeOptimisationMethod() = default; virtual size_t windowSize() const; virtual bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out); }; @@ -42,6 +43,7 @@ class PeepholeOptimiser { public: explicit PeepholeOptimiser(AssemblyItems& _items): m_items(_items) {} + virtual ~PeepholeOptimiser() = default; bool optimise(); diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index abcf170c..2b7da01b 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -174,6 +174,26 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList( }); } + for (auto const& op: std::vector<Instruction>{ + Instruction::ADDRESS, + Instruction::CALLER, + Instruction::ORIGIN, + Instruction::COINBASE + }) + { + u256 const mask = (u256(1) << 160) - 1; + rules.push_back({ + {Instruction::AND, {{op, mask}}}, + [=]() -> Pattern { return op; }, + false + }); + rules.push_back({ + {Instruction::AND, {{mask, op}}}, + [=]() -> Pattern { return op; }, + false + }); + } + // Double negation of opcodes with boolean result for (auto const& op: std::vector<Instruction>{ Instruction::EQ, diff --git a/libjulia/Exceptions.h b/libjulia/Exceptions.h new file mode 100644 index 00000000..20ab6520 --- /dev/null +++ b/libjulia/Exceptions.h @@ -0,0 +1,35 @@ +/* + 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/>. +*/ +/** + * Exceptions in Julia. + */ + +#pragma once + +#include <libdevcore/Exceptions.h> +#include <libdevcore/Assertions.h> + +namespace dev +{ +namespace julia +{ + +struct IuliaException: virtual Exception {}; +struct OptimizerException: virtual IuliaException {}; + +} +} diff --git a/libjulia/optimiser/ASTCopier.cpp b/libjulia/optimiser/ASTCopier.cpp index 5c47be64..a8a1e30f 100644 --- a/libjulia/optimiser/ASTCopier.cpp +++ b/libjulia/optimiser/ASTCopier.cpp @@ -20,9 +20,9 @@ #include <libjulia/optimiser/ASTCopier.h> -#include <libsolidity/inlineasm/AsmData.h> +#include <libjulia/Exceptions.h> -#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/inlineasm/AsmData.h> #include <libdevcore/Common.h> @@ -30,10 +30,9 @@ using namespace std; using namespace dev; using namespace dev::julia; - Statement ASTCopier::operator()(Instruction const&) { - solAssert(false, "Invalid operation."); + assertThrow(false, OptimizerException, "Invalid operation."); return {}; } @@ -62,13 +61,13 @@ Statement ASTCopier::operator()(Assignment const& _assignment) Statement ASTCopier::operator()(StackAssignment const&) { - solAssert(false, "Invalid operation."); + assertThrow(false, OptimizerException, "Invalid operation."); return {}; } Statement ASTCopier::operator()(Label const&) { - solAssert(false, "Invalid operation."); + assertThrow(false, OptimizerException, "Invalid operation."); return {}; } diff --git a/libjulia/optimiser/ASTCopier.h b/libjulia/optimiser/ASTCopier.h index 36a1ced5..8681f2e0 100644 --- a/libjulia/optimiser/ASTCopier.h +++ b/libjulia/optimiser/ASTCopier.h @@ -37,6 +37,7 @@ namespace julia class ExpressionCopier: public boost::static_visitor<Expression> { public: + virtual ~ExpressionCopier() = default; virtual Expression operator()(Literal const& _literal) = 0; virtual Expression operator()(Identifier const& _identifier) = 0; virtual Expression operator()(FunctionalInstruction const& _instr) = 0; @@ -46,6 +47,7 @@ public: class StatementCopier: public boost::static_visitor<Statement> { public: + virtual ~StatementCopier() = default; virtual Statement operator()(ExpressionStatement const& _statement) = 0; virtual Statement operator()(Instruction const& _instruction) = 0; virtual Statement operator()(Label const& _label) = 0; @@ -66,6 +68,7 @@ public: class ASTCopier: public ExpressionCopier, public StatementCopier { public: + virtual ~ASTCopier() = default; virtual Expression operator()(Literal const& _literal) override; virtual Statement operator()(Instruction const& _instruction) override; virtual Expression operator()(Identifier const& _identifier) override; diff --git a/libjulia/optimiser/ASTWalker.cpp b/libjulia/optimiser/ASTWalker.cpp index 03444984..dc94cc60 100644 --- a/libjulia/optimiser/ASTWalker.cpp +++ b/libjulia/optimiser/ASTWalker.cpp @@ -22,8 +22,6 @@ #include <libsolidity/inlineasm/AsmData.h> -#include <libsolidity/interface/Exceptions.h> - #include <boost/range/adaptor/reversed.hpp> using namespace std; diff --git a/libjulia/optimiser/ASTWalker.h b/libjulia/optimiser/ASTWalker.h index 97891381..e1f0f5bd 100644 --- a/libjulia/optimiser/ASTWalker.h +++ b/libjulia/optimiser/ASTWalker.h @@ -22,7 +22,7 @@ #include <libjulia/ASTDataForward.h> -#include <libsolidity/interface/Exceptions.h> +#include <libjulia/Exceptions.h> #include <boost/variant.hpp> #include <boost/optional.hpp> @@ -42,14 +42,15 @@ namespace julia class ASTWalker: public boost::static_visitor<> { public: + virtual ~ASTWalker() = default; virtual void operator()(Literal const&) {} - virtual void operator()(Instruction const&) { solAssert(false, ""); } + virtual void operator()(Instruction const&) { assertThrow(false, OptimizerException, ""); } virtual void operator()(Identifier const&) {} virtual void operator()(FunctionalInstruction const& _instr); virtual void operator()(FunctionCall const& _funCall); virtual void operator()(ExpressionStatement const& _statement); - virtual void operator()(Label const&) { solAssert(false, ""); } - virtual void operator()(StackAssignment const&) { solAssert(false, ""); } + virtual void operator()(Label const&) { assertThrow(false, OptimizerException, ""); } + virtual void operator()(StackAssignment const&) { assertThrow(false, OptimizerException, ""); } virtual void operator()(Assignment const& _assignment); virtual void operator()(VariableDeclaration const& _varDecl); virtual void operator()(If const& _if); @@ -82,14 +83,15 @@ protected: class ASTModifier: public boost::static_visitor<> { public: + virtual ~ASTModifier() = default; virtual void operator()(Literal&) {} - virtual void operator()(Instruction&) { solAssert(false, ""); } + virtual void operator()(Instruction&) { assertThrow(false, OptimizerException, ""); } virtual void operator()(Identifier&) {} virtual void operator()(FunctionalInstruction& _instr); virtual void operator()(FunctionCall& _funCall); virtual void operator()(ExpressionStatement& _statement); - virtual void operator()(Label&) { solAssert(false, ""); } - virtual void operator()(StackAssignment&) { solAssert(false, ""); } + virtual void operator()(Label&) { assertThrow(false, OptimizerException, ""); } + virtual void operator()(StackAssignment&) { assertThrow(false, OptimizerException, ""); } virtual void operator()(Assignment& _assignment); virtual void operator()(VariableDeclaration& _varDecl); virtual void operator()(If& _if); diff --git a/libjulia/optimiser/CommonSubexpressionEliminator.cpp b/libjulia/optimiser/CommonSubexpressionEliminator.cpp index 229bd35e..3122280b 100644 --- a/libjulia/optimiser/CommonSubexpressionEliminator.cpp +++ b/libjulia/optimiser/CommonSubexpressionEliminator.cpp @@ -23,6 +23,7 @@ #include <libjulia/optimiser/Metrics.h> #include <libjulia/optimiser/SyntacticalEquality.h> +#include <libjulia/Exceptions.h> #include <libsolidity/inlineasm/AsmData.h> @@ -37,7 +38,7 @@ void CommonSubexpressionEliminator::visit(Expression& _e) // TODO this search rather inefficient. for (auto const& var: m_value) { - solAssert(var.second, ""); + assertThrow(var.second, OptimizerException, ""); if (SyntacticalEqualityChecker::equal(_e, *var.second)) { _e = Identifier{locationOf(_e), var.first}; diff --git a/libjulia/optimiser/DataFlowAnalyzer.cpp b/libjulia/optimiser/DataFlowAnalyzer.cpp index 56653393..25f0ffb4 100644 --- a/libjulia/optimiser/DataFlowAnalyzer.cpp +++ b/libjulia/optimiser/DataFlowAnalyzer.cpp @@ -23,11 +23,11 @@ #include <libjulia/optimiser/DataFlowAnalyzer.h> #include <libjulia/optimiser/NameCollector.h> +#include <libjulia/optimiser/Semantics.h> +#include <libjulia/Exceptions.h> #include <libsolidity/inlineasm/AsmData.h> -#include <libjulia/optimiser/Semantics.h> - #include <libdevcore/CommonData.h> #include <boost/range/adaptor/reversed.hpp> @@ -41,7 +41,7 @@ void DataFlowAnalyzer::operator()(Assignment& _assignment) set<string> names; for (auto const& var: _assignment.variableNames) names.insert(var.name); - solAssert(_assignment.value, ""); + assertThrow(_assignment.value, OptimizerException, ""); visit(*_assignment.value); handleAssignment(names, _assignment.value.get()); } @@ -120,7 +120,7 @@ void DataFlowAnalyzer::operator()(Block& _block) m_variableScopes.emplace_back(false); ASTModifier::operator()(_block); m_variableScopes.pop_back(); - solAssert(numScopes == m_variableScopes.size(), ""); + assertThrow(numScopes == m_variableScopes.size(), OptimizerException, ""); } void DataFlowAnalyzer::handleAssignment(set<string> const& _variables, Expression* _value) diff --git a/libjulia/optimiser/Disambiguator.cpp b/libjulia/optimiser/Disambiguator.cpp index df2984e5..687be9b9 100644 --- a/libjulia/optimiser/Disambiguator.cpp +++ b/libjulia/optimiser/Disambiguator.cpp @@ -20,11 +20,11 @@ #include <libjulia/optimiser/Disambiguator.h> +#include <libjulia/Exceptions.h> + #include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmScope.h> -#include <libsolidity/interface/Exceptions.h> - using namespace std; using namespace dev; using namespace dev::julia; @@ -34,21 +34,11 @@ using Scope = dev::solidity::assembly::Scope; string Disambiguator::translateIdentifier(string const& _originalName) { - solAssert(!m_scopes.empty() && m_scopes.back(), ""); + assertThrow(!m_scopes.empty() && m_scopes.back(), OptimizerException, ""); Scope::Identifier const* id = m_scopes.back()->lookup(_originalName); - solAssert(id, ""); + assertThrow(id, OptimizerException, ""); if (!m_translations.count(id)) - { - string translated = _originalName; - size_t suffix = 0; - while (m_usedNames.count(translated)) - { - suffix++; - translated = _originalName + "_" + std::to_string(suffix); - } - m_usedNames.insert(translated); - m_translations[id] = translated; - } + m_translations[id] = m_nameDispenser.newName(_originalName); return m_translations.at(id); } @@ -79,7 +69,7 @@ void Disambiguator::enterScopeInternal(Scope& _scope) void Disambiguator::leaveScopeInternal(Scope& _scope) { - solAssert(!m_scopes.empty(), ""); - solAssert(m_scopes.back() == &_scope, ""); + assertThrow(!m_scopes.empty(), OptimizerException, ""); + assertThrow(m_scopes.back() == &_scope, OptimizerException, ""); m_scopes.pop_back(); } diff --git a/libjulia/optimiser/Disambiguator.h b/libjulia/optimiser/Disambiguator.h index 18ffd157..6fc8a615 100644 --- a/libjulia/optimiser/Disambiguator.h +++ b/libjulia/optimiser/Disambiguator.h @@ -23,6 +23,7 @@ #include <libjulia/ASTDataForward.h> #include <libjulia/optimiser/ASTCopier.h> +#include <libjulia/optimiser/NameDispenser.h> #include <libsolidity/inlineasm/AsmAnalysisInfo.h> @@ -60,7 +61,7 @@ protected: std::vector<solidity::assembly::Scope*> m_scopes; std::map<void const*, std::string> m_translations; - std::set<std::string> m_usedNames; + NameDispenser m_nameDispenser; }; } diff --git a/libjulia/optimiser/ExpressionInliner.h b/libjulia/optimiser/ExpressionInliner.h index 10d7659c..3d24ef5d 100644 --- a/libjulia/optimiser/ExpressionInliner.h +++ b/libjulia/optimiser/ExpressionInliner.h @@ -23,8 +23,6 @@ #include <libjulia/ASTDataForward.h> -#include <libsolidity/interface/Exceptions.h> - #include <boost/variant.hpp> #include <boost/optional.hpp> diff --git a/libjulia/optimiser/ExpressionSimplifier.cpp b/libjulia/optimiser/ExpressionSimplifier.cpp index 3d471cb3..8bd6b1c7 100644 --- a/libjulia/optimiser/ExpressionSimplifier.cpp +++ b/libjulia/optimiser/ExpressionSimplifier.cpp @@ -25,8 +25,6 @@ #include <libsolidity/inlineasm/AsmData.h> -#include <libsolidity/interface/Exceptions.h> - #include <libdevcore/CommonData.h> using namespace std; diff --git a/libjulia/optimiser/FullInliner.cpp b/libjulia/optimiser/FullInliner.cpp new file mode 100644 index 00000000..e8776e23 --- /dev/null +++ b/libjulia/optimiser/FullInliner.cpp @@ -0,0 +1,259 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Optimiser component that performs function inlining for arbitrary functions. + */ + +#include <libjulia/optimiser/FullInliner.h> + +#include <libjulia/optimiser/ASTCopier.h> +#include <libjulia/optimiser/ASTWalker.h> +#include <libjulia/optimiser/NameCollector.h> +#include <libjulia/optimiser/Semantics.h> +#include <libjulia/Exceptions.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libdevcore/CommonData.h> + +#include <boost/range/adaptor/reversed.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + +FullInliner::FullInliner(Block& _ast): + m_ast(_ast) +{ + assertThrow(m_ast.statements.size() >= 1, OptimizerException, ""); + assertThrow(m_ast.statements.front().type() == typeid(Block), OptimizerException, ""); + m_nameDispenser.m_usedNames = NameCollector(m_ast).names(); + + for (size_t i = 1; i < m_ast.statements.size(); ++i) + { + assertThrow(m_ast.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, ""); + FunctionDefinition& fun = boost::get<FunctionDefinition>(m_ast.statements.at(i)); + m_functions[fun.name] = &fun; + m_functionsToVisit.insert(&fun); + } +} + +void FullInliner::run() +{ + assertThrow(m_ast.statements[0].type() == typeid(Block), OptimizerException, ""); + InlineModifier(*this, m_nameDispenser, "").visit(m_ast.statements[0]); + while (!m_functionsToVisit.empty()) + handleFunction(**m_functionsToVisit.begin()); +} + +void FullInliner::handleFunction(FunctionDefinition& _fun) +{ + if (!m_functionsToVisit.count(&_fun)) + return; + m_functionsToVisit.erase(&_fun); + (InlineModifier(*this, m_nameDispenser, _fun.name))(_fun.body); +} + +void InlineModifier::operator()(FunctionalInstruction& _instruction) +{ + visitArguments(_instruction.arguments); +} + +void InlineModifier::operator()(FunctionCall&) +{ + assertThrow(false, OptimizerException, "Should be handled in visit() instead."); +} + +void InlineModifier::operator()(ForLoop& _loop) +{ + (*this)(_loop.pre); + // Do not visit the condition because we cannot inline there. + (*this)(_loop.post); + (*this)(_loop.body); +} + +void InlineModifier::operator()(Block& _block) +{ + // This is only used if needed to minimize the number of move operations. + vector<Statement> modifiedStatements; + for (size_t i = 0; i < _block.statements.size(); ++i) + { + visit(_block.statements.at(i)); + if (!m_statementsToPrefix.empty()) + { + if (modifiedStatements.empty()) + std::move( + _block.statements.begin(), + _block.statements.begin() + i, + back_inserter(modifiedStatements) + ); + modifiedStatements += std::move(m_statementsToPrefix); + m_statementsToPrefix.clear(); + } + if (!modifiedStatements.empty()) + modifiedStatements.emplace_back(std::move(_block.statements[i])); + } + if (!modifiedStatements.empty()) + _block.statements = std::move(modifiedStatements); +} + +void InlineModifier::visit(Expression& _expression) +{ + if (_expression.type() != typeid(FunctionCall)) + return ASTModifier::visit(_expression); + + FunctionCall& funCall = boost::get<FunctionCall>(_expression); + FunctionDefinition& fun = m_driver.function(funCall.functionName.name); + + m_driver.handleFunction(fun); + + // TODO: Insert good heuristic here. Perhaps implement that inside the driver. + bool doInline = funCall.functionName.name != m_currentFunction; + + if (fun.returnVariables.size() > 1) + doInline = false; + + { + vector<string> argNames; + vector<string> argTypes; + for (auto const& arg: fun.parameters) + { + argNames.push_back(fun.name + "_" + arg.name); + argTypes.push_back(arg.type); + } + visitArguments(funCall.arguments, argNames, argTypes, doInline); + } + + if (!doInline) + return; + + map<string, string> variableReplacements; + for (size_t i = 0; i < funCall.arguments.size(); ++i) + variableReplacements[fun.parameters[i].name] = boost::get<Identifier>(funCall.arguments[i]).name; + if (fun.returnVariables.empty()) + _expression = noop(funCall.location); + else + { + string returnVariable = fun.returnVariables[0].name; + variableReplacements[returnVariable] = newName(fun.name + "_" + returnVariable); + + m_statementsToPrefix.emplace_back(VariableDeclaration{ + funCall.location, + {{funCall.location, variableReplacements[returnVariable], fun.returnVariables[0].type}}, + {} + }); + _expression = Identifier{funCall.location, variableReplacements[returnVariable]}; + } + m_statementsToPrefix.emplace_back(BodyCopier(m_nameDispenser, fun.name + "_", variableReplacements)(fun.body)); +} + +void InlineModifier::visit(Statement& _statement) +{ + ASTModifier::visit(_statement); + // Replace pop(0) expression statemets (and others) by empty blocks. + if (_statement.type() == typeid(ExpressionStatement)) + { + ExpressionStatement& expSt = boost::get<ExpressionStatement>(_statement); + if (expSt.expression.type() == typeid(FunctionalInstruction)) + { + FunctionalInstruction& funInstr = boost::get<FunctionalInstruction>(expSt.expression); + if (funInstr.instruction == solidity::Instruction::POP) + if (MovableChecker(funInstr.arguments.at(0)).movable()) + _statement = Block{expSt.location, {}}; + } + } +} + +void InlineModifier::visitArguments( + vector<Expression>& _arguments, + vector<string> const& _nameHints, + vector<string> const& _types, + bool _moveToFront +) +{ + // If one of the elements moves parts to the front, all other elements right of it + // also have to be moved to the front to keep the order of evaluation. + vector<Statement> prefix; + for (size_t i = 0; i < _arguments.size(); ++i) + { + auto& arg = _arguments[i]; + // TODO optimize vector operations, check that it actually moves + auto internalPrefix = visitRecursively(arg); + if (!internalPrefix.empty()) + { + _moveToFront = true; + // We go through the arguments left to right, so we have to invert + // the prefixes. + prefix = std::move(internalPrefix) + std::move(prefix); + } + else if (_moveToFront) + { + auto location = locationOf(arg); + string var = newName(i < _nameHints.size() ? _nameHints[i] : ""); + prefix.emplace(prefix.begin(), VariableDeclaration{ + location, + {{TypedName{location, var, i < _types.size() ? _types[i] : ""}}}, + make_shared<Expression>(std::move(arg)) + }); + arg = Identifier{location, var}; + } + } + m_statementsToPrefix += std::move(prefix); +} + +vector<Statement> InlineModifier::visitRecursively(Expression& _expression) +{ + vector<Statement> saved; + saved.swap(m_statementsToPrefix); + visit(_expression); + saved.swap(m_statementsToPrefix); + return saved; +} + +string InlineModifier::newName(string const& _prefix) +{ + return m_nameDispenser.newName(_prefix); +} + +Expression InlineModifier::noop(SourceLocation const& _location) +{ + return FunctionalInstruction{_location, solidity::Instruction::POP, { + Literal{_location, assembly::LiteralKind::Number, "0", ""} + }}; +} + +Statement BodyCopier::operator()(VariableDeclaration const& _varDecl) +{ + for (auto const& var: _varDecl.variables) + m_variableReplacements[var.name] = m_nameDispenser.newName(m_varNamePrefix + var.name); + return ASTCopier::operator()(_varDecl); +} + +Statement BodyCopier::operator()(FunctionDefinition const& _funDef) +{ + assertThrow(false, OptimizerException, "Function hoisting has to be done before function inlining."); + return _funDef; +} + +string BodyCopier::translateIdentifier(string const& _name) +{ + if (m_variableReplacements.count(_name)) + return m_variableReplacements.at(_name); + else + return _name; +} diff --git a/libjulia/optimiser/FullInliner.h b/libjulia/optimiser/FullInliner.h new file mode 100644 index 00000000..ff9e6854 --- /dev/null +++ b/libjulia/optimiser/FullInliner.h @@ -0,0 +1,179 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Optimiser component that performs function inlining for arbitrary functions. + */ +#pragma once + +#include <libjulia/ASTDataForward.h> + +#include <libjulia/optimiser/ASTCopier.h> +#include <libjulia/optimiser/ASTWalker.h> +#include <libjulia/optimiser/NameDispenser.h> +#include <libjulia/Exceptions.h> + +#include <libevmasm/SourceLocation.h> + +#include <boost/variant.hpp> +#include <boost/optional.hpp> + +#include <set> + +namespace dev +{ +namespace julia +{ + +class NameCollector; + + +/** + * Optimiser component that modifies an AST in place, inlining arbitrary functions. + * + * Code of the form + * + * function f(a, b) -> c { ... } + * h(g(x(...), f(arg1(...), arg2(...)), y(...)), z(...)) + * + * is transformed into + * + * function f(a, b) -> c { ... } + * + * let z1 := z(...) let y1 := y(...) let a2 := arg2(...) let a1 := arg1(...) + * let c1 := 0 + * { code of f, with replacements: a -> a1, b -> a2, c -> c1, d -> d1 } + * h(g(x(...), c1, y1), z1) + * + * No temporary variable is created for expressions that are "movable" + * (i.e. they are "pure", have no side-effects and also do not depend on other code + * that might have side-effects). + * + * This component can only be used on sources with unique names and with hoisted functions, + * i.e. the root node has to be a block that itself contains a single block followed by all + * function definitions. + */ +class FullInliner: public ASTModifier +{ +public: + explicit FullInliner(Block& _ast); + + void run(); + + /// Perform inlining operations inside the given function. + void handleFunction(FunctionDefinition& _function); + + FunctionDefinition& function(std::string _name) { return *m_functions.at(_name); } + +private: + /// The AST to be modified. The root block itself will not be modified, because + /// we store pointers to functions. + Block& m_ast; + std::map<std::string, FunctionDefinition*> m_functions; + std::set<FunctionDefinition*> m_functionsToVisit; + NameDispenser m_nameDispenser; +}; + +/** + * Class that walks the AST of a block that does not contain function definitions and perform + * the actual code modifications. + */ +class InlineModifier: public ASTModifier +{ +public: + InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, std::string _functionName): + m_currentFunction(std::move(_functionName)), + m_driver(_driver), + m_nameDispenser(_nameDispenser) + { } + ~InlineModifier() + { + assertThrow(m_statementsToPrefix.empty(), OptimizerException, ""); + } + + virtual void operator()(FunctionalInstruction&) override; + virtual void operator()(FunctionCall&) override; + virtual void operator()(ForLoop&) override; + virtual void operator()(Block& _block) override; + + using ASTModifier::visit; + virtual void visit(Expression& _expression) override; + virtual void visit(Statement& _st) override; + +private: + + /// Visits a list of expressions (usually an argument list to a function call) and tries + /// to inline them. If one of them is inlined, all right of it have to be moved to the front + /// (to keep the order of evaluation). If @a _moveToFront is true, all elements are moved + /// to the front. @a _nameHints and @_types are used for the newly created variables, but + /// both can be empty. + void visitArguments( + std::vector<Expression>& _arguments, + std::vector<std::string> const& _nameHints = {}, + std::vector<std::string> const& _types = {}, + bool _moveToFront = false + ); + + /// Visits an expression, but saves and restores the current statements to prefix and returns + /// the statements that should be prefixed for @a _expression. + std::vector<Statement> visitRecursively(Expression& _expression); + + std::string newName(std::string const& _prefix); + + /// @returns an expression returning nothing. + Expression noop(SourceLocation const& _location); + + /// List of statements that should go in front of the currently visited AST element, + /// at the statement level. + std::vector<Statement> m_statementsToPrefix; + std::string m_currentFunction; + FullInliner& m_driver; + NameDispenser& m_nameDispenser; +}; + +/** + * Creates a copy of a block that is supposed to be the body of a function. + * Applies replacements to referenced variables and creates new names for + * variable declarations. + */ +class BodyCopier: public ASTCopier +{ +public: + BodyCopier( + NameDispenser& _nameDispenser, + std::string const& _varNamePrefix, + std::map<std::string, std::string> const& _variableReplacements + ): + m_nameDispenser(_nameDispenser), + m_varNamePrefix(_varNamePrefix), + m_variableReplacements(_variableReplacements) + {} + + using ASTCopier::operator (); + + virtual Statement operator()(VariableDeclaration const& _varDecl) override; + virtual Statement operator()(FunctionDefinition const& _funDef) override; + + virtual std::string translateIdentifier(std::string const& _name) override; + + NameDispenser& m_nameDispenser; + std::string const& m_varNamePrefix; + std::map<std::string, std::string> m_variableReplacements; +}; + + +} +} diff --git a/libjulia/optimiser/FunctionGrouper.cpp b/libjulia/optimiser/FunctionGrouper.cpp index cc40bc46..f1e99e6b 100644 --- a/libjulia/optimiser/FunctionGrouper.cpp +++ b/libjulia/optimiser/FunctionGrouper.cpp @@ -23,8 +23,6 @@ #include <libsolidity/inlineasm/AsmData.h> -#include <libsolidity/interface/Exceptions.h> - #include <boost/range/algorithm_ext/erase.hpp> using namespace std; diff --git a/libjulia/optimiser/FunctionHoister.cpp b/libjulia/optimiser/FunctionHoister.cpp index 63f6edb9..98fc714c 100644 --- a/libjulia/optimiser/FunctionHoister.cpp +++ b/libjulia/optimiser/FunctionHoister.cpp @@ -25,8 +25,6 @@ #include <libsolidity/inlineasm/AsmData.h> -#include <libsolidity/interface/Exceptions.h> - #include <libdevcore/CommonData.h> using namespace std; diff --git a/libjulia/optimiser/InlinableExpressionFunctionFinder.cpp b/libjulia/optimiser/InlinableExpressionFunctionFinder.cpp index 2097e091..e237063d 100644 --- a/libjulia/optimiser/InlinableExpressionFunctionFinder.cpp +++ b/libjulia/optimiser/InlinableExpressionFunctionFinder.cpp @@ -20,9 +20,9 @@ #include <libjulia/optimiser/InlinableExpressionFunctionFinder.h> -#include <libsolidity/inlineasm/AsmData.h> +#include <libjulia/optimiser/Utilities.h> -#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/inlineasm/AsmData.h> using namespace std; using namespace dev; @@ -56,7 +56,7 @@ void InlinableExpressionFunctionFinder::operator()(FunctionDefinition const& _fu // We cannot overwrite previous settings, because this function definition // would not be valid here if we were searching inside a functionally inlinable // function body. - solAssert(m_disallowedIdentifiers.empty() && !m_foundDisallowedIdentifier, ""); + assertThrow(m_disallowedIdentifiers.empty() && !m_foundDisallowedIdentifier, OptimizerException, ""); m_disallowedIdentifiers = set<string>{retVariable, _function.name}; boost::apply_visitor(*this, *assignment.value); if (!m_foundDisallowedIdentifier) diff --git a/libjulia/optimiser/InlinableExpressionFunctionFinder.h b/libjulia/optimiser/InlinableExpressionFunctionFinder.h index 36cb557a..d11160d7 100644 --- a/libjulia/optimiser/InlinableExpressionFunctionFinder.h +++ b/libjulia/optimiser/InlinableExpressionFunctionFinder.h @@ -23,8 +23,6 @@ #include <libjulia/ASTDataForward.h> #include <libjulia/optimiser/ASTWalker.h> -#include <libsolidity/interface/Exceptions.h> - #include <set> namespace dev diff --git a/libjulia/optimiser/MainFunction.cpp b/libjulia/optimiser/MainFunction.cpp new file mode 100644 index 00000000..bcd2f178 --- /dev/null +++ b/libjulia/optimiser/MainFunction.cpp @@ -0,0 +1,54 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Changes the topmost block to be a function with a specific name ("main") which has no + * inputs nor outputs. + */ + +#include <libjulia/optimiser/MainFunction.h> + +#include <libjulia/optimiser/NameCollector.h> +#include <libjulia/Exceptions.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libdevcore/CommonData.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + +void MainFunction::operator()(Block& _block) +{ + assertThrow(_block.statements.size() >= 1, OptimizerException, ""); + assertThrow(_block.statements[0].type() == typeid(Block), OptimizerException, ""); + for (size_t i = 1; i < _block.statements.size(); ++i) + assertThrow(_block.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, ""); + /// @todo this should handle scopes properly and instead of an assertion it should rename the conflicting function + assertThrow(NameCollector(_block).names().count("main") == 0, OptimizerException, ""); + + Block& block = boost::get<Block>(_block.statements[0]); + FunctionDefinition main{ + block.location, + "main", + {}, + {}, + std::move(block) + }; + _block.statements[0] = std::move(main); +} diff --git a/libjulia/optimiser/MainFunction.h b/libjulia/optimiser/MainFunction.h new file mode 100644 index 00000000..7201d89a --- /dev/null +++ b/libjulia/optimiser/MainFunction.h @@ -0,0 +1,41 @@ +/* + 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/>. +*/ +/** + * Changes the topmost block to be a function with a specific name ("main") which has no + * inputs nor outputs. + */ + +#pragma once + +#include <libjulia/ASTDataForward.h> + +namespace dev +{ +namespace julia +{ + +/** + * Prerequisites: Function Grouper + */ +class MainFunction +{ +public: + void operator()(Block& _block); +}; + +} +} diff --git a/libjulia/optimiser/NameCollector.cpp b/libjulia/optimiser/NameCollector.cpp index 510ee289..c0d0b707 100644 --- a/libjulia/optimiser/NameCollector.cpp +++ b/libjulia/optimiser/NameCollector.cpp @@ -35,7 +35,6 @@ void NameCollector::operator()(VariableDeclaration const& _varDecl) void NameCollector::operator ()(FunctionDefinition const& _funDef) { m_names.insert(_funDef.name); - m_functions[_funDef.name] = &_funDef; for (auto const arg: _funDef.parameters) m_names.insert(arg.name); for (auto const ret: _funDef.returnVariables) diff --git a/libjulia/optimiser/NameCollector.h b/libjulia/optimiser/NameCollector.h index 2d4a1d4b..29856172 100644 --- a/libjulia/optimiser/NameCollector.h +++ b/libjulia/optimiser/NameCollector.h @@ -37,15 +37,18 @@ namespace julia class NameCollector: public ASTWalker { public: + explicit NameCollector(Block const& _block) + { + (*this)(_block); + } + using ASTWalker::operator (); virtual void operator()(VariableDeclaration const& _varDecl) override; virtual void operator()(FunctionDefinition const& _funDef) override; - std::set<std::string> const& names() const { return m_names; } - std::map<std::string, FunctionDefinition const*> const& functions() const { return m_functions; } + std::set<std::string> names() const { return m_names; } private: std::set<std::string> m_names; - std::map<std::string, FunctionDefinition const*> m_functions; }; /** diff --git a/libjulia/optimiser/NameDispenser.cpp b/libjulia/optimiser/NameDispenser.cpp new file mode 100644 index 00000000..e4f0e4f6 --- /dev/null +++ b/libjulia/optimiser/NameDispenser.cpp @@ -0,0 +1,38 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Optimiser component that can create new unique names. + */ + +#include <libjulia/optimiser/NameDispenser.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; + +string NameDispenser::newName(string const& _prefix) +{ + string name = _prefix; + size_t suffix = 0; + while (name.empty() || m_usedNames.count(name)) + { + suffix++; + name = _prefix + "_" + std::to_string(suffix); + } + m_usedNames.insert(name); + return name; +} diff --git a/libjulia/optimiser/NameDispenser.h b/libjulia/optimiser/NameDispenser.h new file mode 100644 index 00000000..91c43d54 --- /dev/null +++ b/libjulia/optimiser/NameDispenser.h @@ -0,0 +1,37 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Optimiser component that can create new unique names. + */ +#pragma once + +#include <set> +#include <string> + +namespace dev +{ +namespace julia +{ + +struct NameDispenser +{ + std::string newName(std::string const& _prefix); + std::set<std::string> m_usedNames; +}; + +} +} diff --git a/libjulia/optimiser/README.md b/libjulia/optimiser/README.md index e7134440..e8aa777a 100644 --- a/libjulia/optimiser/README.md +++ b/libjulia/optimiser/README.md @@ -87,3 +87,12 @@ simple rules like ``x + 0 == x`` to simplify expressions. ## Ineffective Statement Remover This step removes statements that have no side-effects. + +## WebAssembly specific + +### Main Function + +Changes the topmost block to be a function with a specific name ("main") which has no +inputs nor outputs. + +Depends on the Function Grouper. diff --git a/libjulia/optimiser/Rematerialiser.cpp b/libjulia/optimiser/Rematerialiser.cpp index eaa75e33..392099fb 100644 --- a/libjulia/optimiser/Rematerialiser.cpp +++ b/libjulia/optimiser/Rematerialiser.cpp @@ -22,6 +22,7 @@ #include <libjulia/optimiser/Metrics.h> #include <libjulia/optimiser/ASTCopier.h> +#include <libjulia/Exceptions.h> #include <libsolidity/inlineasm/AsmData.h> @@ -44,7 +45,7 @@ void Rematerialiser::visit(Expression& _e) expressionValid = false; break; } - solAssert(m_value.at(name), ""); + assertThrow(m_value.at(name), OptimizerException, ""); auto const& value = *m_value.at(name); if (expressionValid && CodeSize::codeSize(value) <= 7) _e = (ASTCopier{}).translate(value); diff --git a/libjulia/optimiser/Semantics.cpp b/libjulia/optimiser/Semantics.cpp index 92728c46..f28925a4 100644 --- a/libjulia/optimiser/Semantics.cpp +++ b/libjulia/optimiser/Semantics.cpp @@ -20,6 +20,8 @@ #include <libjulia/optimiser/Semantics.h> +#include <libjulia/Exceptions.h> + #include <libsolidity/inlineasm/AsmData.h> #include <libevmasm/SemanticInformation.h> @@ -56,5 +58,5 @@ void MovableChecker::operator()(FunctionCall const&) void MovableChecker::visit(Statement const&) { - solAssert(false, "Movability for statement requested."); + assertThrow(false, OptimizerException, "Movability for statement requested."); } diff --git a/libjulia/optimiser/SyntacticalEquality.cpp b/libjulia/optimiser/SyntacticalEquality.cpp index 2b90b091..c497336d 100644 --- a/libjulia/optimiser/SyntacticalEquality.cpp +++ b/libjulia/optimiser/SyntacticalEquality.cpp @@ -20,8 +20,9 @@ #include <libjulia/optimiser/SyntacticalEquality.h> +#include <libjulia/Exceptions.h> + #include <libsolidity/inlineasm/AsmData.h> -#include <libsolidity/interface/Exceptions.h> #include <libdevcore/CommonData.h> @@ -62,7 +63,7 @@ bool SyntacticalEqualityChecker::equal(Expression const& _e1, Expression const& } else { - solAssert(false, "Invlid expression"); + assertThrow(false, OptimizerException, "Invalid expression"); } return false; } diff --git a/libjulia/optimiser/UnusedPruner.cpp b/libjulia/optimiser/UnusedPruner.cpp index 50038431..54e8fd6e 100644 --- a/libjulia/optimiser/UnusedPruner.cpp +++ b/libjulia/optimiser/UnusedPruner.cpp @@ -23,6 +23,7 @@ #include <libjulia/optimiser/NameCollector.h> #include <libjulia/optimiser/Semantics.h> #include <libjulia/optimiser/Utilities.h> +#include <libjulia/Exceptions.h> #include <libsolidity/inlineasm/AsmData.h> @@ -108,8 +109,8 @@ void UnusedPruner::subtractReferences(map<string, size_t> const& _subtrahend) { for (auto const& ref: _subtrahend) { - solAssert(m_references.count(ref.first), ""); - solAssert(m_references.at(ref.first) >= ref.second, ""); + assertThrow(m_references.count(ref.first), OptimizerException, ""); + assertThrow(m_references.at(ref.first) >= ref.second, OptimizerException, ""); m_references[ref.first] -= ref.second; m_shouldRunAgain = true; } diff --git a/libjulia/optimiser/Utilities.h b/libjulia/optimiser/Utilities.h index e3b4b087..88ba3f47 100644 --- a/libjulia/optimiser/Utilities.h +++ b/libjulia/optimiser/Utilities.h @@ -22,16 +22,11 @@ #include <libjulia/ASTDataForward.h> -#include <libdevcore/Exceptions.h> - namespace dev { namespace julia { -struct IuliaException: virtual Exception {}; -struct OptimizerException: virtual IuliaException {}; - /// Removes statements that are just empty blocks (non-recursive). void removeEmptyBlocks(Block& _block); diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 97b01c83..0bdec4b4 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -28,7 +28,7 @@ else() endif() add_library(solidity ${sources} ${headers}) -target_link_libraries(solidity PUBLIC evmasm devcore) +target_link_libraries(solidity PUBLIC evmasm devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) if (${Z3_FOUND}) target_link_libraries(solidity PUBLIC ${Z3_LIBRARY}) diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp new file mode 100644 index 00000000..6edf7986 --- /dev/null +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -0,0 +1,156 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/analysis/ControlFlowAnalyzer.h> + +using namespace std; +using namespace dev::solidity; + +bool ControlFlowAnalyzer::analyze(ASTNode const& _astRoot) +{ + _astRoot.accept(*this); + return Error::containsOnlyWarnings(m_errorReporter.errors()); +} + +bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function) +{ + auto const& functionFlow = m_cfg.functionFlow(_function); + checkUnassignedStorageReturnValues(_function, functionFlow.entry, functionFlow.exit); + return false; +} + +set<VariableDeclaration const*> ControlFlowAnalyzer::variablesAssignedInNode(CFGNode const *node) +{ + set<VariableDeclaration const*> result; + for (auto expression: node->block.expressions) + { + if (auto const* assignment = dynamic_cast<Assignment const*>(expression)) + { + stack<Expression const*> expressions; + expressions.push(&assignment->leftHandSide()); + while (!expressions.empty()) + { + Expression const* expression = expressions.top(); + expressions.pop(); + + if (auto const *tuple = dynamic_cast<TupleExpression const*>(expression)) + for (auto const& component: tuple->components()) + expressions.push(component.get()); + else if (auto const* identifier = dynamic_cast<Identifier const*>(expression)) + if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>( + identifier->annotation().referencedDeclaration + )) + result.insert(variableDeclaration); + } + } + } + return result; +} + +void ControlFlowAnalyzer::checkUnassignedStorageReturnValues( + FunctionDefinition const& _function, + CFGNode const* _functionEntry, + CFGNode const* _functionExit +) const +{ + if (_function.returnParameterList()->parameters().empty()) + return; + + map<CFGNode const*, set<VariableDeclaration const*>> unassigned; + + { + auto& unassignedAtFunctionEntry = unassigned[_functionEntry]; + for (auto const& returnParameter: _function.returnParameterList()->parameters()) + if (returnParameter->type()->dataStoredIn(DataLocation::Storage)) + unassignedAtFunctionEntry.insert(returnParameter.get()); + } + + stack<CFGNode const*> nodesToTraverse; + nodesToTraverse.push(_functionEntry); + + // walk all paths from entry with maximal set of unassigned return values + while (!nodesToTraverse.empty()) + { + auto node = nodesToTraverse.top(); + nodesToTraverse.pop(); + + auto& unassignedAtNode = unassigned[node]; + + if (node->block.returnStatement != nullptr) + if (node->block.returnStatement->expression()) + unassignedAtNode.clear(); + if (!unassignedAtNode.empty()) + { + // kill all return values to which a value is assigned + for (auto const* variableDeclaration: variablesAssignedInNode(node)) + unassignedAtNode.erase(variableDeclaration); + + // kill all return values referenced in inline assembly + // a reference is enough, checking whether there actually was an assignment might be overkill + for (auto assembly: node->block.inlineAssemblyStatements) + for (auto const& ref: assembly->annotation().externalReferences) + if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration)) + unassignedAtNode.erase(variableDeclaration); + } + + for (auto const& exit: node->exits) + { + auto& unassignedAtExit = unassigned[exit]; + auto oldSize = unassignedAtExit.size(); + unassignedAtExit.insert(unassignedAtNode.begin(), unassignedAtNode.end()); + // (re)traverse an exit, if we are on a path with new unassigned return values to consider + // this will terminate, since there is only a finite number of unassigned return values + if (unassignedAtExit.size() > oldSize) + nodesToTraverse.push(exit); + } + } + + if (!unassigned[_functionExit].empty()) + { + vector<VariableDeclaration const*> unassignedOrdered( + unassigned[_functionExit].begin(), + unassigned[_functionExit].end() + ); + sort( + unassignedOrdered.begin(), + unassignedOrdered.end(), + [](VariableDeclaration const* lhs, VariableDeclaration const* rhs) -> bool { + return lhs->id() < rhs->id(); + } + ); + for (auto const* returnVal: unassignedOrdered) + { + SecondarySourceLocation ssl; + for (CFGNode* lastNodeBeforeExit: _functionExit->entries) + if (unassigned[lastNodeBeforeExit].count(returnVal)) + { + if (!!lastNodeBeforeExit->block.returnStatement) + ssl.append("Problematic return:", lastNodeBeforeExit->block.returnStatement->location()); + else + ssl.append("Problematic end of function:", _function.location()); + } + + m_errorReporter.warning( + returnVal->location(), + "This variable is of storage pointer type and might be returned without assignment. " + "This can cause storage corruption. Assign the variable (potentially from itself) " + "to remove this warning.", + ssl + ); + } + } +} diff --git a/libsolidity/analysis/ControlFlowAnalyzer.h b/libsolidity/analysis/ControlFlowAnalyzer.h new file mode 100644 index 00000000..43e13fb6 --- /dev/null +++ b/libsolidity/analysis/ControlFlowAnalyzer.h @@ -0,0 +1,52 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/analysis/ControlFlowGraph.h> + +#include <set> + +namespace dev +{ +namespace solidity +{ + +class ControlFlowAnalyzer: private ASTConstVisitor +{ +public: + explicit ControlFlowAnalyzer(CFG const& _cfg, ErrorReporter& _errorReporter): + m_cfg(_cfg), m_errorReporter(_errorReporter) {} + + bool analyze(ASTNode const& _astRoot); + + virtual bool visit(FunctionDefinition const& _function) override; + +private: + static std::set<VariableDeclaration const*> variablesAssignedInNode(CFGNode const *node); + void checkUnassignedStorageReturnValues( + FunctionDefinition const& _function, + CFGNode const* _functionEntry, + CFGNode const* _functionExit + ) const; + + CFG const& m_cfg; + ErrorReporter& m_errorReporter; +}; + +} +} diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp new file mode 100644 index 00000000..35d7687c --- /dev/null +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -0,0 +1,370 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/analysis/ControlFlowBuilder.h> + +using namespace dev; +using namespace solidity; +using namespace std; + +ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow): + m_nodeContainer(_nodeContainer), m_currentFunctionFlow(_functionFlow), m_currentNode(_functionFlow.entry) +{ +} + +unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow( + CFG::NodeContainer& _nodeContainer, + FunctionDefinition const& _function +) +{ + auto functionFlow = unique_ptr<FunctionFlow>(new FunctionFlow()); + functionFlow->entry = _nodeContainer.newNode(); + functionFlow->exit = _nodeContainer.newNode(); + functionFlow->revert = _nodeContainer.newNode(); + ControlFlowBuilder builder(_nodeContainer, *functionFlow); + builder.appendControlFlow(_function); + connect(builder.m_currentNode, functionFlow->exit); + return functionFlow; +} + + +unique_ptr<ModifierFlow> ControlFlowBuilder::createModifierFlow( + CFG::NodeContainer& _nodeContainer, + ModifierDefinition const& _modifier +) +{ + auto modifierFlow = unique_ptr<ModifierFlow>(new ModifierFlow()); + modifierFlow->entry = _nodeContainer.newNode(); + modifierFlow->exit = _nodeContainer.newNode(); + modifierFlow->revert = _nodeContainer.newNode(); + modifierFlow->placeholderEntry = _nodeContainer.newNode(); + modifierFlow->placeholderExit = _nodeContainer.newNode(); + ControlFlowBuilder builder(_nodeContainer, *modifierFlow); + builder.appendControlFlow(_modifier); + connect(builder.m_currentNode, modifierFlow->exit); + return modifierFlow; +} + +bool ControlFlowBuilder::visit(BinaryOperation const& _operation) +{ + solAssert(!!m_currentNode, ""); + + switch(_operation.getOperator()) + { + case Token::Or: + case Token::And: + { + appendControlFlow(_operation.leftExpression()); + + auto nodes = splitFlow<2>(); + nodes[0] = createFlow(nodes[0], _operation.rightExpression()); + mergeFlow(nodes, nodes[1]); + + return false; + } + default: + break; + } + return ASTConstVisitor::visit(_operation); +} + +bool ControlFlowBuilder::visit(Conditional const& _conditional) +{ + solAssert(!!m_currentNode, ""); + + _conditional.condition().accept(*this); + + auto nodes = splitFlow<2>(); + + nodes[0] = createFlow(nodes[0], _conditional.trueExpression()); + nodes[1] = createFlow(nodes[1], _conditional.falseExpression()); + + mergeFlow(nodes); + + return false; +} + +bool ControlFlowBuilder::visit(IfStatement const& _ifStatement) +{ + solAssert(!!m_currentNode, ""); + + _ifStatement.condition().accept(*this); + + auto nodes = splitFlow<2>(); + nodes[0] = createFlow(nodes[0], _ifStatement.trueStatement()); + + if (_ifStatement.falseStatement()) + { + nodes[1] = createFlow(nodes[1], *_ifStatement.falseStatement()); + mergeFlow(nodes); + } + else + mergeFlow(nodes, nodes[1]); + + return false; +} + +bool ControlFlowBuilder::visit(ForStatement const& _forStatement) +{ + solAssert(!!m_currentNode, ""); + + if (_forStatement.initializationExpression()) + _forStatement.initializationExpression()->accept(*this); + + auto condition = createLabelHere(); + + if (_forStatement.condition()) + appendControlFlow(*_forStatement.condition()); + + auto loopExpression = newLabel(); + auto nodes = splitFlow<2>(); + auto afterFor = nodes[1]; + m_currentNode = nodes[0]; + + { + BreakContinueScope scope(*this, afterFor, loopExpression); + appendControlFlow(_forStatement.body()); + } + + placeAndConnectLabel(loopExpression); + + if (auto expression = _forStatement.loopExpression()) + appendControlFlow(*expression); + + connect(m_currentNode, condition); + m_currentNode = afterFor; + + return false; +} + +bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement) +{ + solAssert(!!m_currentNode, ""); + + if (_whileStatement.isDoWhile()) + { + auto afterWhile = newLabel(); + auto whileBody = createLabelHere(); + + { + // Note that "continue" in this case currently indeed jumps to whileBody + // and not to the condition. This is inconsistent with JavaScript and C and + // therefore a bug. This will be fixed in the future (planned for 0.5.0) + // and the Control Flow Graph will have to be adjusted accordingly. + BreakContinueScope scope(*this, afterWhile, whileBody); + appendControlFlow(_whileStatement.body()); + } + appendControlFlow(_whileStatement.condition()); + + connect(m_currentNode, whileBody); + placeAndConnectLabel(afterWhile); + } + else + { + auto whileCondition = createLabelHere(); + + appendControlFlow(_whileStatement.condition()); + + auto nodes = splitFlow<2>(); + + auto whileBody = nodes[0]; + auto afterWhile = nodes[1]; + + m_currentNode = whileBody; + { + BreakContinueScope scope(*this, afterWhile, whileCondition); + appendControlFlow(_whileStatement.body()); + } + + connect(m_currentNode, whileCondition); + + m_currentNode = afterWhile; + } + + + return false; +} + +bool ControlFlowBuilder::visit(Break const&) +{ + solAssert(!!m_currentNode, ""); + solAssert(!!m_breakJump, ""); + connect(m_currentNode, m_breakJump); + m_currentNode = newLabel(); + return false; +} + +bool ControlFlowBuilder::visit(Continue const&) +{ + solAssert(!!m_currentNode, ""); + solAssert(!!m_continueJump, ""); + connect(m_currentNode, m_continueJump); + m_currentNode = newLabel(); + return false; +} + +bool ControlFlowBuilder::visit(Throw const&) +{ + solAssert(!!m_currentNode, ""); + solAssert(!!m_currentFunctionFlow.revert, ""); + connect(m_currentNode, m_currentFunctionFlow.revert); + m_currentNode = newLabel(); + return false; +} + +bool ControlFlowBuilder::visit(Block const&) +{ + solAssert(!!m_currentNode, ""); + createLabelHere(); + return true; +} + +void ControlFlowBuilder::endVisit(Block const&) +{ + solAssert(!!m_currentNode, ""); + createLabelHere(); +} + +bool ControlFlowBuilder::visit(Return const& _return) +{ + solAssert(!!m_currentNode, ""); + solAssert(!!m_currentFunctionFlow.exit, ""); + solAssert(!m_currentNode->block.returnStatement, ""); + m_currentNode->block.returnStatement = &_return; + connect(m_currentNode, m_currentFunctionFlow.exit); + m_currentNode = newLabel(); + return true; +} + + +bool ControlFlowBuilder::visit(PlaceholderStatement const&) +{ + solAssert(!!m_currentNode, ""); + auto modifierFlow = dynamic_cast<ModifierFlow const*>(&m_currentFunctionFlow); + solAssert(!!modifierFlow, ""); + + connect(m_currentNode, modifierFlow->placeholderEntry); + + m_currentNode = newLabel(); + + connect(modifierFlow->placeholderExit, m_currentNode); + return false; +} + +bool ControlFlowBuilder::visitNode(ASTNode const& node) +{ + solAssert(!!m_currentNode, ""); + if (auto const* expression = dynamic_cast<Expression const*>(&node)) + m_currentNode->block.expressions.emplace_back(expression); + else if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(&node)) + m_currentNode->block.variableDeclarations.emplace_back(variableDeclaration); + else if (auto const* assembly = dynamic_cast<InlineAssembly const*>(&node)) + m_currentNode->block.inlineAssemblyStatements.emplace_back(assembly); + + return true; +} + +bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) +{ + solAssert(!!m_currentNode, ""); + solAssert(!!_functionCall.expression().annotation().type, ""); + + if (auto functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type)) + switch (functionType->kind()) + { + case FunctionType::Kind::Revert: + solAssert(!!m_currentFunctionFlow.revert, ""); + _functionCall.expression().accept(*this); + ASTNode::listAccept(_functionCall.arguments(), *this); + connect(m_currentNode, m_currentFunctionFlow.revert); + m_currentNode = newLabel(); + return false; + case FunctionType::Kind::Require: + case FunctionType::Kind::Assert: + { + solAssert(!!m_currentFunctionFlow.revert, ""); + _functionCall.expression().accept(*this); + ASTNode::listAccept(_functionCall.arguments(), *this); + connect(m_currentNode, m_currentFunctionFlow.revert); + auto nextNode = newLabel(); + connect(m_currentNode, nextNode); + m_currentNode = nextNode; + return false; + } + default: + break; + } + return ASTConstVisitor::visit(_functionCall); +} + +void ControlFlowBuilder::appendControlFlow(ASTNode const& _node) +{ + _node.accept(*this); +} + +CFGNode* ControlFlowBuilder::createFlow(CFGNode* _entry, ASTNode const& _node) +{ + auto oldCurrentNode = m_currentNode; + m_currentNode = _entry; + appendControlFlow(_node); + auto endNode = m_currentNode; + m_currentNode = oldCurrentNode; + return endNode; +} + +void ControlFlowBuilder::connect(CFGNode* _from, CFGNode* _to) +{ + solAssert(_from, ""); + solAssert(_to, ""); + _from->exits.push_back(_to); + _to->entries.push_back(_from); +} + +CFGNode* ControlFlowBuilder::newLabel() +{ + return m_nodeContainer.newNode(); +} + +CFGNode* ControlFlowBuilder::createLabelHere() +{ + auto label = m_nodeContainer.newNode(); + connect(m_currentNode, label); + m_currentNode = label; + return label; +} + +void ControlFlowBuilder::placeAndConnectLabel(CFGNode* _node) +{ + connect(m_currentNode, _node); + m_currentNode = _node; +} + +ControlFlowBuilder::BreakContinueScope::BreakContinueScope( + ControlFlowBuilder& _parser, + CFGNode* _breakJump, + CFGNode* _continueJump +): m_parser(_parser), m_origBreakJump(_parser.m_breakJump), m_origContinueJump(_parser.m_continueJump) +{ + m_parser.m_breakJump = _breakJump; + m_parser.m_continueJump = _continueJump; +} + +ControlFlowBuilder::BreakContinueScope::~BreakContinueScope() +{ + m_parser.m_breakJump = m_origBreakJump; + m_parser.m_continueJump = m_origContinueJump; +} diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h new file mode 100644 index 00000000..e9d96e5f --- /dev/null +++ b/libsolidity/analysis/ControlFlowBuilder.h @@ -0,0 +1,143 @@ +/* + 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 <libsolidity/analysis/ControlFlowGraph.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/ast/ASTVisitor.h> + +#include <array> +#include <memory> + +namespace dev { +namespace solidity { + +/** Helper class that builds the control flow of a function or modifier. + * Modifiers are not yet applied to the functions. This is done in a second + * step in the CFG class. + */ +class ControlFlowBuilder: private ASTConstVisitor +{ +public: + static std::unique_ptr<FunctionFlow> createFunctionFlow( + CFG::NodeContainer& _nodeContainer, + FunctionDefinition const& _function + ); + static std::unique_ptr<ModifierFlow> createModifierFlow( + CFG::NodeContainer& _nodeContainer, + ModifierDefinition const& _modifier + ); + +private: + explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow); + + virtual bool visit(BinaryOperation const& _operation) override; + virtual bool visit(Conditional const& _conditional) override; + virtual bool visit(IfStatement const& _ifStatement) override; + virtual bool visit(ForStatement const& _forStatement) override; + virtual bool visit(WhileStatement const& _whileStatement) override; + virtual bool visit(Break const&) override; + virtual bool visit(Continue const&) override; + virtual bool visit(Throw const&) override; + virtual bool visit(Block const&) override; + virtual void endVisit(Block const&) override; + virtual bool visit(Return const& _return) override; + virtual bool visit(PlaceholderStatement const&) override; + virtual bool visit(FunctionCall const& _functionCall) override; + + + /// Appends the control flow of @a _node to the current control flow. + void appendControlFlow(ASTNode const& _node); + + /// Starts at @a _entry and parses the control flow of @a _node. + /// @returns The node at which the parsed control flow ends. + /// m_currentNode is not affected (it is saved and restored). + CFGNode* createFlow(CFGNode* _entry, ASTNode const& _node); + + /// Creates an arc from @a _from to @a _to. + static void connect(CFGNode* _from, CFGNode* _to); + + +protected: + virtual bool visitNode(ASTNode const& node) override; + +private: + + /// Splits the control flow starting at the current node into n paths. + /// m_currentNode is set to nullptr and has to be set manually or + /// using mergeFlow later. + template<size_t n> + std::array<CFGNode*, n> splitFlow() + { + std::array<CFGNode*, n> result; + for (auto& node: result) + { + node = m_nodeContainer.newNode(); + connect(m_currentNode, node); + } + m_currentNode = nullptr; + return result; + } + + /// Merges the control flow of @a _nodes to @a _endNode. + /// If @a _endNode is nullptr, a new node is creates and used as end node. + /// Sets the merge destination as current node. + /// Note: @a _endNode may be one of the nodes in @a _nodes. + template<size_t n> + void mergeFlow(std::array<CFGNode*, n> const& _nodes, CFGNode* _endNode = nullptr) + { + CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode; + for (auto& node: _nodes) + if (node != mergeDestination) + connect(node, mergeDestination); + m_currentNode = mergeDestination; + } + + CFGNode* newLabel(); + CFGNode* createLabelHere(); + void placeAndConnectLabel(CFGNode *_node); + + CFG::NodeContainer& m_nodeContainer; + + /// The control flow of the function that is currently parsed. + /// Note: this can also be a ModifierFlow + FunctionFlow const& m_currentFunctionFlow; + + CFGNode* m_currentNode = nullptr; + + /// The current jump destination of break Statements. + CFGNode* m_breakJump = nullptr; + /// The current jump destination of continue Statements. + CFGNode* m_continueJump = nullptr; + + /// Helper class that replaces the break and continue jump destinations for the + /// current scope and restores the originals at the end of the scope. + class BreakContinueScope + { + public: + BreakContinueScope(ControlFlowBuilder& _parser, CFGNode* _breakJump, CFGNode* _continueJump); + ~BreakContinueScope(); + private: + ControlFlowBuilder& m_parser; + CFGNode* m_origBreakJump; + CFGNode* m_origContinueJump; + }; +}; + +} +} diff --git a/libsolidity/analysis/ControlFlowGraph.cpp b/libsolidity/analysis/ControlFlowGraph.cpp new file mode 100644 index 00000000..9b3da0eb --- /dev/null +++ b/libsolidity/analysis/ControlFlowGraph.cpp @@ -0,0 +1,136 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/analysis/ControlFlowGraph.h> +#include <libsolidity/analysis/ControlFlowBuilder.h> + +#include <boost/range/adaptor/reversed.hpp> + +#include <algorithm> + +using namespace std; +using namespace dev::solidity; + +bool CFG::constructFlow(ASTNode const& _astRoot) +{ + _astRoot.accept(*this); + applyModifiers(); + return Error::containsOnlyWarnings(m_errorReporter.errors()); +} + + +bool CFG::visit(ModifierDefinition const& _modifier) +{ + m_modifierControlFlow[&_modifier] = ControlFlowBuilder::createModifierFlow(m_nodeContainer, _modifier); + return false; +} + +bool CFG::visit(FunctionDefinition const& _function) +{ + m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function); + return false; +} + +FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const +{ + solAssert(m_functionControlFlow.count(&_function), ""); + return *m_functionControlFlow.find(&_function)->second; +} + +CFGNode* CFG::NodeContainer::newNode() +{ + m_nodes.emplace_back(new CFGNode()); + return m_nodes.back().get(); +} + +void CFG::applyModifiers() +{ + for (auto const& function: m_functionControlFlow) + { + for (auto const& modifierInvocation: boost::adaptors::reverse(function.first->modifiers())) + { + if (auto modifierDefinition = dynamic_cast<ModifierDefinition const*>( + modifierInvocation->name()->annotation().referencedDeclaration + )) + { + solAssert(m_modifierControlFlow.count(modifierDefinition), ""); + applyModifierFlowToFunctionFlow(*m_modifierControlFlow[modifierDefinition], function.second.get()); + } + } + } +} + +void CFG::applyModifierFlowToFunctionFlow( + ModifierFlow const& _modifierFlow, + FunctionFlow* _functionFlow +) +{ + solAssert(!!_functionFlow, ""); + + map<CFGNode*, CFGNode*> copySrcToCopyDst; + + // inherit the revert node of the function + copySrcToCopyDst[_modifierFlow.revert] = _functionFlow->revert; + + // replace the placeholder nodes by the function entry and exit + copySrcToCopyDst[_modifierFlow.placeholderEntry] = _functionFlow->entry; + copySrcToCopyDst[_modifierFlow.placeholderExit] = _functionFlow->exit; + + stack<CFGNode*> nodesToCopy; + nodesToCopy.push(_modifierFlow.entry); + + // map the modifier entry to a new node that will become the new function entry + copySrcToCopyDst[_modifierFlow.entry] = m_nodeContainer.newNode(); + + while (!nodesToCopy.empty()) + { + CFGNode* copySrcNode = nodesToCopy.top(); + nodesToCopy.pop(); + + solAssert(copySrcToCopyDst.count(copySrcNode), ""); + + CFGNode* copyDstNode = copySrcToCopyDst[copySrcNode]; + + copyDstNode->block = copySrcNode->block; + for (auto const& entry: copySrcNode->entries) + { + if (!copySrcToCopyDst.count(entry)) + { + copySrcToCopyDst[entry] = m_nodeContainer.newNode(); + nodesToCopy.push(entry); + } + copyDstNode->entries.emplace_back(copySrcToCopyDst[entry]); + } + for (auto const& exit: copySrcNode->exits) + { + if (!copySrcToCopyDst.count(exit)) + { + copySrcToCopyDst[exit] = m_nodeContainer.newNode(); + nodesToCopy.push(exit); + } + copyDstNode->exits.emplace_back(copySrcToCopyDst[exit]); + } + } + + // if the modifier control flow never reached its exit node, + // we need to create a new (disconnected) exit node now + if (!copySrcToCopyDst.count(_modifierFlow.exit)) + copySrcToCopyDst[_modifierFlow.exit] = m_nodeContainer.newNode(); + + _functionFlow->entry = copySrcToCopyDst[_modifierFlow.entry]; + _functionFlow->exit = copySrcToCopyDst[_modifierFlow.exit]; +}
\ No newline at end of file diff --git a/libsolidity/analysis/ControlFlowGraph.h b/libsolidity/analysis/ControlFlowGraph.h new file mode 100644 index 00000000..c646e4f1 --- /dev/null +++ b/libsolidity/analysis/ControlFlowGraph.h @@ -0,0 +1,148 @@ +/* + 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 <libsolidity/ast/AST.h> +#include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/interface/ErrorReporter.h> + +#include <map> +#include <memory> +#include <stack> +#include <vector> + +namespace dev +{ +namespace solidity +{ + +/** Basic Control Flow Block. + * Basic block of control flow. Consists of a set of (unordered) AST nodes + * for which control flow is always linear. A basic control flow block + * encompasses at most one scope. Reverts are considered to break the control + * flow. + * @todo Handle function calls correctly. So far function calls are not considered + * to change the control flow. + */ +struct ControlFlowBlock +{ + /// All variable declarations inside this control flow block. + std::vector<VariableDeclaration const*> variableDeclarations; + /// All expressions inside this control flow block (this includes all subexpressions!). + std::vector<Expression const*> expressions; + /// All inline assembly statements inside in this control flow block. + std::vector<InlineAssembly const*> inlineAssemblyStatements; + /// If control flow returns in this node, the return statement is stored in returnStatement, + /// otherwise returnStatement is nullptr. + Return const* returnStatement = nullptr; +}; + +/** Node of the Control Flow Graph. + * The control flow is a directed graph connecting control flow blocks. + * An arc between two nodes indicates that the control flow can possibly + * move from its start node to its end node during execution. + */ +struct CFGNode +{ + /// Entry nodes. All CFG nodes from which control flow may move into this node. + std::vector<CFGNode*> entries; + /// Exit nodes. All CFG nodes to which control flow may continue after this node. + std::vector<CFGNode*> exits; + + /// Control flow in the node. + ControlFlowBlock block; +}; + +/** Describes the control flow of a function. */ +struct FunctionFlow +{ + virtual ~FunctionFlow() {} + /// Entry node. Control flow of the function starts here. + /// This node is empty and does not have any entries. + CFGNode* entry = nullptr; + /// Exit node. All non-reverting control flow of the function ends here. + /// This node is empty and does not have any exits, but may have multiple entries + /// (e.g. all return statements of the function). + CFGNode* exit = nullptr; + /// Revert node. Control flow of the function in case of revert. + /// This node is empty does not have any exits, but may have multiple entries + /// (e.g. all assert, require, revert and throw statements). + CFGNode* revert = nullptr; +}; + +/** Describes the control flow of a modifier. + * Every placeholder breaks the control flow. The node preceding the + * placeholder is assigned placeholderEntry as exit and the node + * following the placeholder is assigned placeholderExit as entry. + */ +struct ModifierFlow: FunctionFlow +{ + /// Control flow leading towards a placeholder exit in placeholderEntry. + CFGNode* placeholderEntry = nullptr; + /// Control flow coming from a placeholder enter from placeholderExit. + CFGNode* placeholderExit = nullptr; +}; + +class CFG: private ASTConstVisitor +{ +public: + explicit CFG(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + + bool constructFlow(ASTNode const& _astRoot); + + virtual bool visit(ModifierDefinition const& _modifier) override; + virtual bool visit(FunctionDefinition const& _function) override; + + FunctionFlow const& functionFlow(FunctionDefinition const& _function) const; + + class NodeContainer + { + public: + CFGNode* newNode(); + private: + std::vector<std::unique_ptr<CFGNode>> m_nodes; + }; +private: + /// Initially the control flow for all functions *ignoring* modifiers and for + /// all modifiers is constructed. Afterwards the control flow of functions + /// is adjusted by applying all modifiers. + void applyModifiers(); + + /// Creates a copy of the modifier flow @a _modifierFlow, while replacing the + /// placeholder entry and exit with the function entry and exit, as well as + /// replacing the modifier revert node with the function's revert node. + /// The resulting control flow is the new function flow with the modifier applied. + /// @a _functionFlow is updated in-place. + void applyModifierFlowToFunctionFlow( + ModifierFlow const& _modifierFlow, + FunctionFlow* _functionFlow + ); + + ErrorReporter& m_errorReporter; + + /// Node container. + /// All nodes allocated during the construction of the control flow graph + /// are owned by the CFG class and stored in this container. + NodeContainer m_nodeContainer; + + std::map<FunctionDefinition const*, std::unique_ptr<FunctionFlow>> m_functionControlFlow; + std::map<ModifierDefinition const*, std::unique_ptr<ModifierFlow>> m_modifierControlFlow; +}; + +} +} diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 72d29762..a222bdf0 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1067,6 +1067,7 @@ void TypeChecker::endVisit(EmitStatement const& _emit) { if ( _emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall || + type(_emit.eventCall().expression())->category() != Type::Category::Function || dynamic_cast<FunctionType const&>(*type(_emit.eventCall().expression())).kind() != FunctionType::Kind::Event ) m_errorReporter.typeError(_emit.eventCall().expression().location(), "Expression has to be an event invocation."); @@ -1075,6 +1076,7 @@ void TypeChecker::endVisit(EmitStatement const& _emit) bool TypeChecker::visit(VariableDeclarationStatement const& _statement) { + bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); if (!_statement.initialValue()) { // No initial value is only permitted for single variables with specified type. @@ -1091,7 +1093,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (varDecl.referenceLocation() == VariableDeclaration::Location::Default) errorText += " Did you mean '<type> memory " + varDecl.name() + "'?"; solAssert(m_scope, ""); - if (m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) + if (v050) m_errorReporter.declarationError(varDecl.location(), errorText); else m_errorReporter.warning(varDecl.location(), errorText); @@ -1131,12 +1133,33 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) ") in value for variable assignment (0) needed" ); } - else if (valueTypes.size() != variables.size() && !variables.front() && !variables.back()) - m_errorReporter.fatalTypeError( - _statement.location(), - "Wildcard both at beginning and end of variable declaration list is only allowed " - "if the number of components is equal." - ); + else if (valueTypes.size() != variables.size()) + { + if (v050) + m_errorReporter.fatalTypeError( + _statement.location(), + "Different number of components on the left hand side (" + + toString(variables.size()) + + ") than on the right hand side (" + + toString(valueTypes.size()) + + ")." + ); + else if (!variables.front() && !variables.back()) + m_errorReporter.fatalTypeError( + _statement.location(), + "Wildcard both at beginning and end of variable declaration list is only allowed " + "if the number of components is equal." + ); + else + m_errorReporter.warning( + _statement.location(), + "Different number of components on the left hand side (" + + toString(variables.size()) + + ") than on the right hand side (" + + toString(valueTypes.size()) + + ")." + ); + } size_t minNumValues = variables.size(); if (!variables.empty() && (!variables.back() || !variables.front())) --minNumValues; @@ -1200,8 +1223,9 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) string extension; if (auto type = dynamic_cast<IntegerType const*>(var.annotation().type.get())) { - int numBits = type->numBits(); + unsigned numBits = type->numBits(); bool isSigned = type->isSigned(); + solAssert(numBits > 0, ""); string minValue; string maxValue; if (isSigned) @@ -1333,6 +1357,7 @@ bool TypeChecker::visit(Conditional const& _conditional) bool TypeChecker::visit(Assignment const& _assignment) { + bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); requireLValue(_assignment.leftHandSide()); TypePointer t = type(_assignment.leftHandSide()); _assignment.annotation().type = t; @@ -1345,11 +1370,29 @@ bool TypeChecker::visit(Assignment const& _assignment) ); // Sequenced assignments of tuples is not valid, make the result a "void" type. _assignment.annotation().type = make_shared<TupleType>(); + expectType(_assignment.rightHandSide(), *tupleType); // expectType does not cause fatal errors, so we have to check again here. - if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get())) + if (TupleType const* rhsType = dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get())) + { checkDoubleStorageAssignment(_assignment); + // @todo For 0.5.0, this code shoud move to TupleType::isImplicitlyConvertibleTo, + // but we cannot do it right now. + if (rhsType->components().size() != tupleType->components().size()) + { + string message = + "Different number of components on the left hand side (" + + toString(tupleType->components().size()) + + ") than on the right hand side (" + + toString(rhsType->components().size()) + + ")."; + if (v050) + m_errorReporter.typeError(_assignment.location(), message); + else + m_errorReporter.warning(_assignment.location(), message); + } + } } else if (t->category() == Type::Category::Mapping) { @@ -1406,8 +1449,10 @@ bool TypeChecker::visit(TupleExpression const& _tuple) } else { + bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); bool isPure = true; TypePointer inlineArrayType; + for (size_t i = 0; i < components.size(); ++i) { // Outside of an lvalue-context, the only situation where a component can be empty is (x,). @@ -1418,6 +1463,17 @@ bool TypeChecker::visit(TupleExpression const& _tuple) components[i]->accept(*this); types.push_back(type(*components[i])); + if (types[i]->category() == Type::Category::Tuple) + if (dynamic_cast<TupleType const&>(*types[i]).components().empty()) + { + if (_tuple.isInlineArray()) + m_errorReporter.fatalTypeError(components[i]->location(), "Array component cannot be empty."); + if (v050) + m_errorReporter.fatalTypeError(components[i]->location(), "Tuple component cannot be empty."); + else + m_errorReporter.warning(components[i]->location(), "Tuple component cannot be empty."); + } + // Note: code generation will visit each of the expression even if they are not assigned from. if (types[i]->category() == Type::Category::RationalNumber && components.size() > 1) if (!dynamic_cast<RationalNumberType const&>(*types[i]).mobileType()) @@ -1648,12 +1704,22 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) else _functionCall.annotation().type = make_shared<TupleType>(returnTypes); + bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression())) { + string msg; if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::SHA3) - m_errorReporter.warning(_functionCall.location(), "\"sha3\" has been deprecated in favour of \"keccak256\""); + msg = "\"sha3\" has been deprecated in favour of \"keccak256\""; else if (functionName->name() == "suicide" && functionType->kind() == FunctionType::Kind::Selfdestruct) - m_errorReporter.warning(_functionCall.location(), "\"suicide\" has been deprecated in favour of \"selfdestruct\""); + msg = "\"suicide\" has been deprecated in favour of \"selfdestruct\""; + if (!msg.empty()) + { + if (v050) + m_errorReporter.typeError(_functionCall.location(), msg); + else + m_errorReporter.warning(_functionCall.location(), msg); + } } if (!m_insideEmitStatement && functionType->kind() == FunctionType::Kind::Event) { @@ -1674,14 +1740,22 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) { /* If no mobile type is available an error will be raised elsewhere. */ if (literal->mobileType()) - m_errorReporter.warning( - arguments[i]->location(), - "The type of \"" + - argType->toString() + - "\" was inferred as " + - literal->mobileType()->toString() + - ". This is probably not desired. Use an explicit type to silence this warning." - ); + { + if (v050) + m_errorReporter.typeError( + arguments[i]->location(), + "Cannot perform packed encoding for a literal. Please convert it to an explicit type first." + ); + else + m_errorReporter.warning( + arguments[i]->location(), + "The type of \"" + + argType->toString() + + "\" was inferred as " + + literal->mobileType()->toString() + + ". This is probably not desired. Use an explicit type to silence this warning." + ); + } } } } @@ -2202,6 +2276,7 @@ void TypeChecker::endVisit(Literal const& _literal) "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals" ); } + if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None) { if (v050) @@ -2217,6 +2292,21 @@ void TypeChecker::endVisit(Literal const& _literal) "You can use an expression of the form \"0x1234 * 1 day\" instead." ); } + + if (_literal.subDenomination() == Literal::SubDenomination::Year) + { + if (v050) + m_errorReporter.typeError( + _literal.location(), + "Using \"years\" as a unit denomination is deprecated." + ); + else + m_errorReporter.warning( + _literal.location(), + "Using \"years\" as a unit denomination is deprecated." + ); + } + if (!_literal.annotation().type) _literal.annotation().type = Type::forLiteral(_literal); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index a53987bf..fa0d6921 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -146,6 +146,7 @@ private: class Scopable { public: + virtual ~Scopable() = default; /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. /// Available only after name and type resolution step. ASTNode const* scope() const { return m_scope; } @@ -307,6 +308,7 @@ private: class VariableScope { public: + virtual ~VariableScope() = default; void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } std::vector<VariableDeclaration const*> const& localVariables() const { return m_localVariables; } @@ -320,6 +322,7 @@ private: class Documented { public: + virtual ~Documented() = default; explicit Documented(ASTPointer<ASTString> const& _documentation): m_documentation(_documentation) {} /// @return A shared pointer of an ASTString. @@ -336,6 +339,7 @@ protected: class ImplementationOptional { public: + virtual ~ImplementationOptional() = default; explicit ImplementationOptional(bool _implemented): m_implemented(_implemented) {} /// @return whether this node is fully implemented or not diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index b1389f0f..6c0ce6f8 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -43,6 +43,7 @@ namespace solidity class ASTVisitor { public: + virtual ~ASTVisitor() = default; virtual bool visit(SourceUnit& _node) { return visitNode(_node); } virtual bool visit(PragmaDirective& _node) { return visitNode(_node); } virtual bool visit(ImportDirective& _node) { return visitNode(_node); } @@ -147,6 +148,7 @@ protected: class ASTConstVisitor { public: + virtual ~ASTConstVisitor() = default; virtual bool visit(SourceUnit const& _node) { return visitNode(_node); } virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); } virtual bool visit(ImportDirective const& _node) { return visitNode(_node); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 425e5045..dc548538 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -425,14 +425,14 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT } -IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): +IntegerType::IntegerType(unsigned _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) { if (isAddress()) solAssert(m_bits == 160, ""); solAssert( m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0, - "Invalid bit number for integer type: " + dev::toString(_bits) + "Invalid bit number for integer type: " + dev::toString(m_bits) ); } @@ -584,7 +584,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons { if (isAddress()) return { - {"balance", make_shared<IntegerType >(256)}, + {"balance", make_shared<IntegerType>(256)}, {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, StateMutability::Payable)}, {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, StateMutability::Payable)}, {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)}, @@ -595,13 +595,12 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons return MemberList::MemberMap(); } -FixedPointType::FixedPointType(int _totalBits, int _fractionalDigits, FixedPointType::Modifier _modifier): +FixedPointType::FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, FixedPointType::Modifier _modifier): m_totalBits(_totalBits), m_fractionalDigits(_fractionalDigits), m_modifier(_modifier) { solAssert( - 8 <= m_totalBits && m_totalBits <= 256 && m_totalBits % 8 == 0 && - 0 <= m_fractionalDigits && m_fractionalDigits <= 80, - "Invalid bit number(s) for fixed type: " + + 8 <= m_totalBits && m_totalBits <= 256 && m_totalBits % 8 == 0 && m_fractionalDigits <= 80, + "Invalid bit number(s) for fixed type: " + dev::toString(_totalBits) + "x" + dev::toString(_fractionalDigits) ); } @@ -627,8 +626,7 @@ bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || - _convertTo.category() == Category::Integer || - _convertTo.category() == Category::FixedBytes; + (_convertTo.category() == Category::Integer && !dynamic_cast<IntegerType const&>(_convertTo).isAddress()); } TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const @@ -682,13 +680,7 @@ bigint FixedPointType::minIntegerValue() const TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - if ( - _other->category() != Category::RationalNumber && - _other->category() != category() && - _other->category() != Category::Integer - ) - return TypePointer(); - auto commonType = Type::commonType(shared_from_this(), _other); //might be fixed point or integer + auto commonType = Type::commonType(shared_from_this(), _other); if (!commonType) return TypePointer(); @@ -696,19 +688,16 @@ TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePoi // All fixed types can be compared if (Token::isCompareOp(_operator)) return commonType; - if (Token::isBitOp(_operator) || Token::isBooleanOp(_operator)) + if (Token::isBitOp(_operator) || Token::isBooleanOp(_operator) || _operator == Token::Exp) return TypePointer(); - if (auto fixType = dynamic_pointer_cast<FixedPointType const>(commonType)) - { - if (Token::Exp == _operator) - return TypePointer(); - } - else if (auto intType = dynamic_pointer_cast<IntegerType const>(commonType)) - if (intType->isAddress()) - return TypePointer(); return commonType; } +std::shared_ptr<IntegerType> FixedPointType::asIntegerType() const +{ + return make_shared<IntegerType>(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned); +} + tuple<bool, rational> RationalNumberType::parseRational(string const& _value) { rational value; @@ -860,7 +849,7 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const if (isFractional()) return false; IntegerType const& targetType = dynamic_cast<IntegerType const&>(_convertTo); - int forSignBit = (targetType.isSigned() ? 1 : 0); + unsigned forSignBit = (targetType.isSigned() ? 1 : 0); if (m_value > rational(0)) { if (m_value.numerator() <= (u256(-1) >> (256 - targetType.numBits() + forSignBit))) @@ -1148,7 +1137,7 @@ u256 RationalNumberType::literalValue(Literal const*) const auto fixed = fixedPointType(); solAssert(fixed, ""); int fractionalDigits = fixed->fractionalDigits(); - shiftedValue = (m_value.numerator() / m_value.denominator()) * pow(bigint(10), fractionalDigits); + shiftedValue = m_value.numerator() * pow(bigint(10), fractionalDigits) / m_value.denominator(); } // we ignore the literal and hope that the type was correctly determined @@ -1274,17 +1263,12 @@ bool StringLiteralType::isValidUTF8() const return dev::validateUTF8(m_value); } -shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal) +FixedBytesType::FixedBytesType(unsigned _bytes): m_bytes(_bytes) { - if (_literal.length() <= 32) - return make_shared<FixedBytesType>(_literal.length()); - return shared_ptr<FixedBytesType>(); -} - -FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes) -{ - solAssert(m_bytes >= 0 && m_bytes <= 32, - "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes)); + solAssert( + m_bytes > 0 && m_bytes <= 32, + "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes) + ); } bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 345f84a1..6defacfc 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -138,6 +138,7 @@ private: class Type: private boost::noncopyable, public std::enable_shared_from_this<Type> { public: + virtual ~Type() = default; enum class Category { Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, @@ -318,7 +319,7 @@ public: }; virtual Category category() const override { return Category::Integer; } - explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned); + explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned); virtual std::string richIdentifier() const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; @@ -341,7 +342,7 @@ public: virtual TypePointer encodingType() const override { return shared_from_this(); } virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } - int numBits() const { return m_bits; } + unsigned numBits() const { return m_bits; } bool isAddress() const { return m_modifier == Modifier::Address; } bool isSigned() const { return m_modifier == Modifier::Signed; } @@ -349,7 +350,7 @@ public: bigint maxValue() const; private: - int m_bits; + unsigned m_bits; Modifier m_modifier; }; @@ -365,7 +366,7 @@ public: }; virtual Category category() const override { return Category::FixedPoint; } - explicit FixedPointType(int _totalBits, int _fractionalDigits, Modifier _modifier = Modifier::Unsigned); + explicit FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, Modifier _modifier = Modifier::Unsigned); virtual std::string richIdentifier() const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; @@ -385,9 +386,9 @@ public: virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } /// Number of bits used for this type in total. - int numBits() const { return m_totalBits; } + unsigned numBits() const { return m_totalBits; } /// Number of decimal digits after the radix point. - int fractionalDigits() const { return m_fractionalDigits; } + unsigned fractionalDigits() const { return m_fractionalDigits; } bool isSigned() const { return m_modifier == Modifier::Signed; } /// @returns the largest integer value this type con hold. Note that this is not the /// largest value in general. @@ -396,9 +397,12 @@ public: /// smallest value in general. bigint minIntegerValue() const; + /// @returns the smallest integer type that can hold this type with fractional parts shifted to integers. + std::shared_ptr<IntegerType> asIntegerType() const; + private: - int m_totalBits; - int m_fractionalDigits; + unsigned m_totalBits; + unsigned m_fractionalDigits; Modifier m_modifier; }; @@ -502,11 +506,7 @@ class FixedBytesType: public Type public: virtual Category category() const override { return Category::FixedBytes; } - /// @returns the smallest bytes type for the given literal or an empty pointer - /// if no type fits. - static std::shared_ptr<FixedBytesType> smallestTypeForLiteral(std::string const& _literal); - - explicit FixedBytesType(int _bytes); + explicit FixedBytesType(unsigned _bytes); virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; @@ -524,10 +524,10 @@ public: virtual TypePointer encodingType() const override { return shared_from_this(); } virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } - int numBytes() const { return m_bytes; } + unsigned numBytes() const { return m_bytes; } private: - int m_bytes; + unsigned m_bytes; }; /** diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 8e890854..3e3aa0ae 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -371,7 +371,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) if (toCategory == Type::Category::Integer) body = Whiskers("converted := <convert>(<shift>(value))") - ("shift", shiftRightFunction(256 - from.numBytes() * 8, false)) + ("shift", shiftRightFunction(256 - from.numBytes() * 8)) ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) .render(); else @@ -458,8 +458,8 @@ string ABIFunctions::splitExternalFunctionIdFunction() } )") ("functionName", functionName) - ("shr32", shiftRightFunction(32, false)) - ("shr64", shiftRightFunction(64, false)) + ("shr32", shiftRightFunction(32)) + ("shr64", shiftRightFunction(64)) .render(); }); } @@ -831,7 +831,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("encodeToMemoryFun", encodeToMemoryFun); std::vector<std::map<std::string, std::string>> items(itemsPerSlot); for (size_t i = 0; i < itemsPerSlot; ++i) - items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8, false); + items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8); templ("items", items); return templ.render(); } @@ -927,7 +927,7 @@ string ABIFunctions::abiEncodingFunctionStruct( } else memberTempl("preprocess", ""); - memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8, false) + "(slotValue)"); + memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8) + "(slotValue)"); } else { @@ -1401,37 +1401,75 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) string ABIFunctions::shiftLeftFunction(size_t _numBits) { + solAssert(_numBits < 256, ""); + string functionName = "shift_left_" + to_string(_numBits); - return createFunction(functionName, [&]() { - solAssert(_numBits < 256, ""); - return - Whiskers(R"( - function <functionName>(value) -> newValue { - newValue := mul(value, <multiplier>) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); + if (m_evmVersion.hasBitwiseShifting()) + { + return createFunction(functionName, [&]() { + return + Whiskers(R"( + function <functionName>(value) -> newValue { + newValue := shl(<numBits>, value) + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + .render(); + }); + } + else + { + return createFunction(functionName, [&]() { + return + Whiskers(R"( + function <functionName>(value) -> newValue { + newValue := mul(value, <multiplier>) + } + )") + ("functionName", functionName) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); + } } -string ABIFunctions::shiftRightFunction(size_t _numBits, bool _signed) +string ABIFunctions::shiftRightFunction(size_t _numBits) { - string functionName = "shift_right_" + to_string(_numBits) + (_signed ? "_signed" : "_unsigned"); - return createFunction(functionName, [&]() { - solAssert(_numBits < 256, ""); - return - Whiskers(R"( - function <functionName>(value) -> newValue { - newValue := <div>(value, <multiplier>) - } - )") - ("functionName", functionName) - ("div", _signed ? "sdiv" : "div") - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); + solAssert(_numBits < 256, ""); + + // Note that if this is extended with signed shifts, + // the opcodes SAR and SDIV behave differently with regards to rounding! + + string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; + if (m_evmVersion.hasBitwiseShifting()) + { + return createFunction(functionName, [&]() { + return + Whiskers(R"( + function <functionName>(value) -> newValue { + newValue := shr(<numBits>, value) + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + .render(); + }); + } + else + { + return createFunction(functionName, [&]() { + return + Whiskers(R"( + function <functionName>(value) -> newValue { + newValue := div(value, <multiplier>) + } + )") + ("functionName", functionName) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); + } } string ABIFunctions::roundUpFunction() diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 2b582e84..db4d40f5 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -22,6 +22,8 @@ #pragma once +#include <libsolidity/interface/EVMVersion.h> + #include <libsolidity/ast/ASTForward.h> #include <vector> @@ -48,6 +50,8 @@ using TypePointers = std::vector<TypePointer>; class ABIFunctions { public: + explicit ABIFunctions(EVMVersion _evmVersion = EVMVersion{}) : m_evmVersion(_evmVersion) {} + /// @returns name of an assembly function to ABI-encode values of @a _givenTypes /// into memory, converting the types to @a _targetTypes on the fly. /// Parameters are: <headStart> <value_n> ... <value_1>, i.e. @@ -191,7 +195,7 @@ private: std::string copyToMemoryFunction(bool _fromCalldata); std::string shiftLeftFunction(size_t _numBits); - std::string shiftRightFunction(size_t _numBits, bool _signed); + std::string shiftRightFunction(size_t _numBits); /// @returns the name of a function that rounds its input to the next multiple /// of 32 or the input if it is a multiple of 32. std::string roundUpFunction(); @@ -225,6 +229,8 @@ private: /// Map from function name to code for a multi-use function. std::map<std::string, std::string> m_requestedFunctions; + + EVMVersion m_evmVersion; }; } diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 098472f7..5776b5d1 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -55,7 +55,8 @@ public: explicit CompilerContext(EVMVersion _evmVersion = EVMVersion{}, CompilerContext* _runtimeContext = nullptr): m_asm(std::make_shared<eth::Assembly>()), m_evmVersion(_evmVersion), - m_runtimeContext(_runtimeContext) + m_runtimeContext(_runtimeContext), + m_abiFunctions(m_evmVersion) { if (m_runtimeContext) m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 4af7d905..a39e799c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -110,7 +110,7 @@ void CompilerUtils::loadFromMemoryDynamic( bool _padToWordBoundaries, bool _keepUpdatedMemoryOffset ) -{ +{ if (_keepUpdatedMemoryOffset) m_context << Instruction::DUP1; @@ -396,7 +396,7 @@ void CompilerUtils::encodeToMemory( // leave end_of_mem as dyn head pointer m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; dynPointers++; - solAssert((argSize + dynPointers) < 16, "Stack too deep, try using less variables."); + solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables."); } else { @@ -599,15 +599,15 @@ void CompilerUtils::splitExternalFunctionType(bool _leftAligned) if (_leftAligned) { m_context << Instruction::DUP1; - rightShiftNumberOnStack(64 + 32, false); + rightShiftNumberOnStack(64 + 32); // <input> <address> m_context << Instruction::SWAP1; - rightShiftNumberOnStack(64, false); + rightShiftNumberOnStack(64); } else { m_context << Instruction::DUP1; - rightShiftNumberOnStack(32, false); + rightShiftNumberOnStack(32); m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; } m_context << u256(0xffffffffUL) << Instruction::AND; @@ -675,7 +675,7 @@ void CompilerUtils::convertType( // conversion from bytes to integer. no need to clean the high bit // only to shift right because of opposite alignment IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); - rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8, false); + rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8); if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8) convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded); } @@ -688,7 +688,7 @@ void CompilerUtils::convertType( m_context << Instruction::POP << u256(0); else if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded) { - int bytes = min(typeOnStack.numBytes(), targetType.numBytes()); + unsigned bytes = min(typeOnStack.numBytes(), targetType.numBytes()); m_context << ((u256(1) << (256 - bytes * 8)) - 1); m_context << Instruction::NOT << Instruction::AND; } @@ -741,7 +741,7 @@ void CompilerUtils::convertType( else if (targetTypeCategory == Type::Category::FixedPoint) { solAssert( - stackTypeCategory == Type::Category::Integer || + stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::RationalNumber || stackTypeCategory == Type::Category::FixedPoint, "Invalid conversion to FixedMxNType requested." @@ -796,7 +796,7 @@ void CompilerUtils::convertType( bytesConstRef data(value); if (targetTypeCategory == Type::Category::FixedBytes) { - int const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes(); + unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes(); solAssert(data.size() <= 32, ""); m_context << (h256::Arith(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes)))); } @@ -1242,7 +1242,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda bool leftAligned = _type.category() == Type::Category::FixedBytes; // add leading or trailing zeros by dividing/multiplying depending on alignment int shiftFactor = (32 - numBytes) * 8; - rightShiftNumberOnStack(shiftFactor, false); + rightShiftNumberOnStack(shiftFactor); if (leftAligned) leftShiftNumberOnStack(shiftFactor); } @@ -1265,13 +1265,20 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) void CompilerUtils::leftShiftNumberOnStack(unsigned _bits) { solAssert(_bits < 256, ""); - m_context << (u256(1) << _bits) << Instruction::MUL; + if (m_context.evmVersion().hasBitwiseShifting()) + m_context << _bits << Instruction::SHL; + else + m_context << (u256(1) << _bits) << Instruction::MUL; } -void CompilerUtils::rightShiftNumberOnStack(unsigned _bits, bool _isSigned) +void CompilerUtils::rightShiftNumberOnStack(unsigned _bits) { solAssert(_bits < 256, ""); - m_context << (u256(1) << _bits) << Instruction::SWAP1 << (_isSigned ? Instruction::SDIV : Instruction::DIV); + // NOTE: If we add signed right shift, SAR rounds differently than SDIV + if (m_context.evmVersion().hasBitwiseShifting()) + m_context << _bits << Instruction::SHR; + else + m_context << (u256(1) << _bits) << Instruction::SWAP1 << Instruction::DIV; } unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 476a7559..8e3a8a5d 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -254,7 +254,7 @@ public: /// Helper function to shift top value on the stack to the right. /// Stack pre: <value> <shift_by_bits> /// Stack post: <shifted_value> - void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false); + void rightShiftNumberOnStack(unsigned _bits); /// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type. void computeHashStatic(); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 3cf46a9d..a8222e21 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -548,7 +548,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (m_context.runtimeContext()) // We have a runtime context, so we need the creation part. - utils().rightShiftNumberOnStack(32, false); + utils().rightShiftNumberOnStack(32); else // Extract the runtime part. m_context << ((u256(1) << 32) - 1) << Instruction::AND; @@ -1706,13 +1706,23 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co m_context.appendConditionalInvalid(); } + m_context << Instruction::SWAP1; + // stack: value_to_shift shift_amount + switch (_operator) { case Token::SHL: - m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::MUL; + if (m_context.evmVersion().hasBitwiseShifting()) + m_context << Instruction::SHL; + else + m_context << u256(2) << Instruction::EXP << Instruction::MUL; break; case Token::SAR: - m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV); + // NOTE: SAR rounds differently than SDIV + if (m_context.evmVersion().hasBitwiseShifting() && !c_valueSigned) + m_context << Instruction::SHR; + else + m_context << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV); break; case Token::SHR: default: diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index e19cf41e..77684683 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -267,7 +267,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc else if (m_dataType->category() == Type::Category::FixedBytes) { solAssert(_sourceType.category() == Type::Category::FixedBytes, "source not fixed bytes"); - CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes(), false); + CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes()); } else { diff --git a/libsolidity/codegen/LValue.h b/libsolidity/codegen/LValue.h index f8b68362..c576f9de 100644 --- a/libsolidity/codegen/LValue.h +++ b/libsolidity/codegen/LValue.h @@ -49,6 +49,7 @@ protected: m_context(_compilerContext), m_dataType(_dataType) {} public: + virtual ~LValue() {} /// @returns the number of stack slots occupied by the lvalue reference virtual unsigned sizeOnStack() const { return 1; } /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 777e57c3..c4dee22d 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -77,7 +77,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) m_interface->reset(); m_variables.clear(); m_pathConditions.clear(); - m_conditionalExecutionHappened = false; + m_loopExecutionHappened = false; initializeLocalVariables(_function); return true; } @@ -132,6 +132,7 @@ bool SMTChecker::visit(WhileStatement const& _node) visitBranch(_node.body(), expr(_node.condition())); } + m_loopExecutionHappened = true; resetVariables(touchedVariables); return false; @@ -171,7 +172,7 @@ bool SMTChecker::visit(ForStatement const& _node) m_interface->pop(); - m_conditionalExecutionHappened = true; + m_loopExecutionHappened = true; std::swap(sequenceCountersStart, m_variables); resetVariables(touchedVariables); @@ -548,7 +549,6 @@ SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _s if (_condition) popPathCondition(); - m_conditionalExecutionHappened = true; std::swap(m_variables, beforeVars); return beforeVars; @@ -591,10 +591,10 @@ void SMTChecker::checkCondition( vector<string> values; tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); - string conditionalComment; - if (m_conditionalExecutionHappened) - conditionalComment = - "\nNote that some information is erased after conditional execution of parts of the code.\n" + string loopComment; + if (m_loopExecutionHappened) + loopComment = + "\nNote that some information is erased after the execution of loops.\n" "You can re-introduce information using require()."; switch (result) { @@ -611,13 +611,13 @@ void SMTChecker::checkCondition( } else message << "."; - m_errorReporter.warning(_location, message.str() + conditionalComment); + m_errorReporter.warning(_location, message.str() + loopComment); break; } case smt::CheckResult::UNSATISFIABLE: break; case smt::CheckResult::UNKNOWN: - m_errorReporter.warning(_location, _description + " might happen here." + conditionalComment); + m_errorReporter.warning(_location, _description + " might happen here." + loopComment); break; case smt::CheckResult::ERROR: m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 7e7996cf..fd54fb5c 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -160,7 +160,7 @@ private: std::shared_ptr<smt::SolverInterface> m_interface; std::shared_ptr<VariableUsage> m_variableUsage; - bool m_conditionalExecutionHappened = false; + bool m_loopExecutionHappened = false; std::map<Expression const*, smt::Expression> m_expressions; std::map<Declaration const*, SSAVariable> m_variables; std::vector<smt::Expression> m_pathConditions; diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index e127bb55..16796684 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -193,6 +193,7 @@ DEV_SIMPLE_EXCEPTION(SolverError); class SolverInterface { public: + virtual ~SolverInterface() = default; virtual void reset() = 0; virtual void push() = 0; diff --git a/libsolidity/formal/SymbolicVariable.h b/libsolidity/formal/SymbolicVariable.h index e4e4ea8d..e29ded26 100644 --- a/libsolidity/formal/SymbolicVariable.h +++ b/libsolidity/formal/SymbolicVariable.h @@ -40,6 +40,7 @@ public: Declaration const& _decl, smt::SolverInterface& _interface ); + virtual ~SymbolicVariable() = default; smt::Expression operator()(int _seq) const { diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index abf7ddf2..9f505889 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -54,6 +54,7 @@ bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::operator()(Label const& _label) { + solAssert(!_label.name.empty(), ""); checkLooseFeature( _label.location, "The use of labels is deprecated. Please use \"if\", \"switch\", \"for\" or function calls instead." @@ -107,6 +108,7 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal) bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) { + solAssert(!_identifier.name.empty(), ""); size_t numErrorsBefore = m_errorReporter.errors().size(); bool success = true; if (m_currentScope->lookup(_identifier.name, Scope::Visitor( @@ -208,6 +210,7 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) { + solAssert(_assignment.value, ""); int const expectedItems = _assignment.variableNames.size(); solAssert(expectedItems >= 1, ""); int const stackHeight = m_stackHeight; @@ -259,6 +262,7 @@ bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) { + solAssert(!_funDef.name.empty(), ""); Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); solAssert(virtualBlock, ""); Scope& varScope = scope(virtualBlock); @@ -280,6 +284,7 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) { + solAssert(!_funCall.functionName.name.empty(), ""); bool success = true; size_t arguments = 0; size_t returns = 0; @@ -349,6 +354,8 @@ bool AsmAnalyzer::operator()(If const& _if) bool AsmAnalyzer::operator()(Switch const& _switch) { + solAssert(_switch.expression, ""); + bool success = true; if (!expectExpression(*_switch.expression)) @@ -391,6 +398,8 @@ bool AsmAnalyzer::operator()(Switch const& _switch) bool AsmAnalyzer::operator()(assembly::ForLoop const& _for) { + solAssert(_for.condition, ""); + Scope* originalScope = m_currentScope; bool success = true; @@ -478,6 +487,7 @@ bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation con bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize) { + solAssert(!_variable.name.empty(), ""); bool success = true; size_t numErrorsBefore = m_errorReporter.errors().size(); size_t variableSize(-1); diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 7f618e07..d3b0808b 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -276,7 +276,7 @@ assembly::Expression Parser::parseExpression() int args = instructionInfo(instr.instruction).args; if (args > 0 && currentToken() != Token::LParen) fatalParserError(string( - "Expected token \"(\" (\"" + + "Expected '(' (instruction \"" + instructionNames().at(instr.instruction) + "\" expects " + boost::lexical_cast<string>(args) + @@ -504,7 +504,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) /// check for premature closing parentheses if (currentToken() == Token::RParen) fatalParserError(string( - "Expected expression (\"" + + "Expected expression (instruction \"" + instructionNames().at(instr) + "\" expects " + boost::lexical_cast<string>(args) + @@ -516,7 +516,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) { if (currentToken() != Token::Comma) fatalParserError(string( - "Expected comma (\"" + + "Expected ',' (instruction \"" + instructionNames().at(instr) + "\" expects " + boost::lexical_cast<string>(args) + @@ -529,7 +529,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) ret.location.end = endPosition(); if (currentToken() == Token::Comma) fatalParserError(string( - "Expected ')' (\"" + + "Expected ')' (instruction \"" + instructionNames().at(instr) + "\" expects " + boost::lexical_cast<string>(args) + diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 4ff14aa2..47dc30cf 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -29,6 +29,8 @@ #include <libsolidity/ast/AST.h> #include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Parser.h> +#include <libsolidity/analysis/ControlFlowAnalyzer.h> +#include <libsolidity/analysis/ControlFlowGraph.h> #include <libsolidity/analysis/GlobalContext.h> #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/analysis/TypeChecker.h> @@ -224,6 +226,22 @@ bool CompilerStack::analyze() if (noErrors) { + CFG cfg(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (!cfg.constructFlow(*source->ast)) + noErrors = false; + + if (noErrors) + { + ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter); + for (Source const* source: m_sourceOrder) + if (!controlFlowAnalyzer.analyze(*source->ast)) + noErrors = false; + } + } + + if (noErrors) + { StaticAnalyzer staticAnalyzer(m_errorReporter); for (Source const* source: m_sourceOrder) if (!staticAnalyzer.analyze(*source->ast)) diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp index a496cc21..a532f86e 100644 --- a/libsolidity/interface/GasEstimator.cpp +++ b/libsolidity/interface/GasEstimator.cpp @@ -136,13 +136,22 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( ExpressionClasses& classes = state->expressionClasses(); using Id = ExpressionClasses::Id; using Ids = vector<Id>; - // div(calldataload(0), 1 << 224) equals to hashValue Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(_signature))))); Id calldata = classes.find(Instruction::CALLDATALOAD, Ids{classes.find(u256(0))}); - classes.forceEqual(hashValue, Instruction::DIV, Ids{ - calldata, - classes.find(u256(1) << 224) - }); + if (!m_evmVersion.hasBitwiseShifting()) + // div(calldataload(0), 1 << 224) equals to hashValue + classes.forceEqual( + hashValue, + Instruction::DIV, + Ids{calldata, classes.find(u256(1) << 224)} + ); + else + // shr(0xe0, calldataload(0)) equals to hashValue + classes.forceEqual( + hashValue, + Instruction::SHR, + Ids{classes.find(u256(0xe0)), calldata} + ); // lt(calldatasize(), 4) equals to 0 (ignore the shortcut for fallback functions) classes.forceEqual( classes.find(u256(0)), diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index d058d556..d9588e5c 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -72,7 +72,7 @@ bool DocStringParser::parse(string const& _docString, ErrorReporter& _errorRepor auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end); if (tagNameEndPos == end) { - appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found"); + appendError("End of tag " + string(tagPos, tagNameEndPos) + " not found"); break; } diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 618a0896..d1be13a5 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -528,7 +528,7 @@ ASTPointer<EnumDefinition> Parser::parseEnumDefinition() break; expectToken(Token::Comma); if (m_scanner->currentToken() != Token::Identifier) - fatalParserError(string("Expected Identifier after ','")); + fatalParserError(string("Expected identifier after ','")); } if (members.size() == 0) parserError({"enum with no members is not allowed."}); @@ -1057,16 +1057,16 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const if (m_scanner->currentToken() != Token::Identifier) fatalParserError("Expected event name or path."); - vector<ASTPointer<PrimaryExpression>> path; + IndexAccessedPath iap; while (true) { - path.push_back(parseIdentifier()); + iap.path.push_back(parseIdentifier()); if (m_scanner->currentToken() != Token::Period) break; m_scanner->next(); }; - auto eventName = expressionFromIndexAccessStructure(path, {}); + auto eventName = expressionFromIndexAccessStructure(iap); expectToken(Token::LParen); vector<ASTPointer<Expression>> arguments; @@ -1083,61 +1083,60 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const& _docString) { RecursionGuard recursionGuard(*this); + LookAheadInfo statementType; + IndexAccessedPath iap; + + tie(statementType, iap) = tryParseIndexAccessedPath(); + switch (statementType) + { + case LookAheadInfo::VariableDeclaration: + return parseVariableDeclarationStatement(_docString, typeNameFromIndexAccessStructure(iap)); + case LookAheadInfo::Expression: + return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(iap)); + default: + solAssert(false, ""); + } +} + +bool Parser::IndexAccessedPath::empty() const +{ + if (!indices.empty()) + { + solAssert(!path.empty(), ""); + } + return path.empty() && indices.empty(); +} + + +pair<Parser::LookAheadInfo, Parser::IndexAccessedPath> Parser::tryParseIndexAccessedPath() +{ // These two cases are very hard to distinguish: - // x[7 * 20 + 3] a; - x[7 * 20 + 3] = 9; + // x[7 * 20 + 3] a; and x[7 * 20 + 3] = 9; // In the first case, x is a type name, in the second it is the name of a variable. // As an extension, we can even have: // `x.y.z[1][2] a;` and `x.y.z[1][2] = 10;` // Where in the first, x.y.z leads to a type name where in the second, it accesses structs. - switch (peekStatementType()) + + auto statementType = peekStatementType(); + switch (statementType) { - case LookAheadInfo::VariableDeclarationStatement: - return parseVariableDeclarationStatement(_docString); - case LookAheadInfo::ExpressionStatement: - return parseExpressionStatement(_docString); + case LookAheadInfo::VariableDeclaration: + case LookAheadInfo::Expression: + return make_pair(statementType, IndexAccessedPath()); default: break; } + // At this point, we have 'Identifier "["' or 'Identifier "." Identifier' or 'ElementoryTypeName "["'. - // We parse '(Identifier ("." Identifier)* |ElementaryTypeName) ( "[" Expression "]" )+' + // We parse '(Identifier ("." Identifier)* |ElementaryTypeName) ( "[" Expression "]" )*' // until we can decide whether to hand this over to ExpressionStatement or create a // VariableDeclarationStatement out of it. - vector<ASTPointer<PrimaryExpression>> path; - bool startedWithElementary = false; - if (m_scanner->currentToken() == Token::Identifier) - path.push_back(parseIdentifier()); - else - { - startedWithElementary = true; - unsigned firstNum; - unsigned secondNum; - tie(firstNum, secondNum) = m_scanner->currentTokenInfo(); - ElementaryTypeNameToken elemToken(m_scanner->currentToken(), firstNum, secondNum); - path.push_back(ASTNodeFactory(*this).createNode<ElementaryTypeNameExpression>(elemToken)); - m_scanner->next(); - } - while (!startedWithElementary && m_scanner->currentToken() == Token::Period) - { - m_scanner->next(); - path.push_back(parseIdentifier()); - } - vector<pair<ASTPointer<Expression>, SourceLocation>> indices; - while (m_scanner->currentToken() == Token::LBrack) - { - expectToken(Token::LBrack); - ASTPointer<Expression> index; - if (m_scanner->currentToken() != Token::RBrack) - index = parseExpression(); - SourceLocation indexLocation = path.front()->location(); - indexLocation.end = endPosition(); - indices.push_back(make_pair(index, indexLocation)); - expectToken(Token::RBrack); - } + IndexAccessedPath iap = parseIndexAccessedPath(); if (m_scanner->currentToken() == Token::Identifier || Token::isLocationSpecifier(m_scanner->currentToken())) - return parseVariableDeclarationStatement(_docString, typeNameIndexAccessStructure(path, indices)); + return make_pair(LookAheadInfo::VariableDeclaration, move(iap)); else - return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(path, indices)); + return make_pair(LookAheadInfo::Expression, move(iap)); } ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStatement( @@ -1207,23 +1206,24 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme ASTPointer<ExpressionStatement> Parser::parseExpressionStatement( ASTPointer<ASTString> const& _docString, - ASTPointer<Expression> const& _lookAheadIndexAccessStructure + ASTPointer<Expression> const& _partialParserResult ) { RecursionGuard recursionGuard(*this); - ASTPointer<Expression> expression = parseExpression(_lookAheadIndexAccessStructure); + ASTPointer<Expression> expression = parseExpression(_partialParserResult); return ASTNodeFactory(*this, expression).createNode<ExpressionStatement>(_docString, expression); } ASTPointer<Expression> Parser::parseExpression( - ASTPointer<Expression> const& _lookAheadIndexAccessStructure + ASTPointer<Expression> const& _partiallyParsedExpression ) { RecursionGuard recursionGuard(*this); - ASTPointer<Expression> expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure); + ASTPointer<Expression> expression = parseBinaryExpression(4, _partiallyParsedExpression); if (Token::isAssignmentOp(m_scanner->currentToken())) { - Token::Value assignmentOperator = expectAssignmentOperator(); + Token::Value assignmentOperator = m_scanner->currentToken(); + m_scanner->next(); ASTPointer<Expression> rightHandSide = parseExpression(); ASTNodeFactory nodeFactory(*this, expression); nodeFactory.setEndPositionFromNode(rightHandSide); @@ -1245,11 +1245,11 @@ ASTPointer<Expression> Parser::parseExpression( ASTPointer<Expression> Parser::parseBinaryExpression( int _minPrecedence, - ASTPointer<Expression> const& _lookAheadIndexAccessStructure + ASTPointer<Expression> const& _partiallyParsedExpression ) { RecursionGuard recursionGuard(*this); - ASTPointer<Expression> expression = parseUnaryExpression(_lookAheadIndexAccessStructure); + ASTPointer<Expression> expression = parseUnaryExpression(_partiallyParsedExpression); ASTNodeFactory nodeFactory(*this, expression); int precedence = Token::precedence(m_scanner->currentToken()); for (; precedence >= _minPrecedence; --precedence) @@ -1265,14 +1265,14 @@ ASTPointer<Expression> Parser::parseBinaryExpression( } ASTPointer<Expression> Parser::parseUnaryExpression( - ASTPointer<Expression> const& _lookAheadIndexAccessStructure + ASTPointer<Expression> const& _partiallyParsedExpression ) { RecursionGuard recursionGuard(*this); - ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ? - ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this); + ASTNodeFactory nodeFactory = _partiallyParsedExpression ? + ASTNodeFactory(*this, _partiallyParsedExpression) : ASTNodeFactory(*this); Token::Value token = m_scanner->currentToken(); - if (!_lookAheadIndexAccessStructure && (Token::isUnaryOp(token) || Token::isCountOp(token))) + if (!_partiallyParsedExpression && (Token::isUnaryOp(token) || Token::isCountOp(token))) { // prefix expression m_scanner->next(); @@ -1283,7 +1283,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression( else { // potential postfix expression - ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_lookAheadIndexAccessStructure); + ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_partiallyParsedExpression); token = m_scanner->currentToken(); if (!Token::isCountOp(token)) return subExpression; @@ -1294,16 +1294,16 @@ ASTPointer<Expression> Parser::parseUnaryExpression( } ASTPointer<Expression> Parser::parseLeftHandSideExpression( - ASTPointer<Expression> const& _lookAheadIndexAccessStructure + ASTPointer<Expression> const& _partiallyParsedExpression ) { RecursionGuard recursionGuard(*this); - ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ? - ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this); + ASTNodeFactory nodeFactory = _partiallyParsedExpression ? + ASTNodeFactory(*this, _partiallyParsedExpression) : ASTNodeFactory(*this); ASTPointer<Expression> expression; - if (_lookAheadIndexAccessStructure) - expression = _lookAheadIndexAccessStructure; + if (_partiallyParsedExpression) + expression = _partiallyParsedExpression; else if (m_scanner->currentToken() == Token::New) { expectToken(Token::New); @@ -1517,44 +1517,79 @@ Parser::LookAheadInfo Parser::peekStatementType() const bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); if (token == Token::Mapping || token == Token::Function || token == Token::Var) - return LookAheadInfo::VariableDeclarationStatement; + return LookAheadInfo::VariableDeclaration; if (mightBeTypeName) { Token::Value next = m_scanner->peekNextToken(); if (next == Token::Identifier || Token::isLocationSpecifier(next)) - return LookAheadInfo::VariableDeclarationStatement; + return LookAheadInfo::VariableDeclaration; if (next == Token::LBrack || next == Token::Period) return LookAheadInfo::IndexAccessStructure; } - return LookAheadInfo::ExpressionStatement; + return LookAheadInfo::Expression; } -ASTPointer<TypeName> Parser::typeNameIndexAccessStructure( - vector<ASTPointer<PrimaryExpression>> const& _path, - vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices -) +Parser::IndexAccessedPath Parser::parseIndexAccessedPath() +{ + IndexAccessedPath iap; + if (m_scanner->currentToken() == Token::Identifier) + { + iap.path.push_back(parseIdentifier()); + while (m_scanner->currentToken() == Token::Period) + { + m_scanner->next(); + iap.path.push_back(parseIdentifier()); + } + } + else + { + unsigned firstNum; + unsigned secondNum; + tie(firstNum, secondNum) = m_scanner->currentTokenInfo(); + ElementaryTypeNameToken elemToken(m_scanner->currentToken(), firstNum, secondNum); + iap.path.push_back(ASTNodeFactory(*this).createNode<ElementaryTypeNameExpression>(elemToken)); + m_scanner->next(); + } + while (m_scanner->currentToken() == Token::LBrack) + { + expectToken(Token::LBrack); + ASTPointer<Expression> index; + if (m_scanner->currentToken() != Token::RBrack) + index = parseExpression(); + SourceLocation indexLocation = iap.path.front()->location(); + indexLocation.end = endPosition(); + iap.indices.push_back(make_pair(index, indexLocation)); + expectToken(Token::RBrack); + } + + return iap; +} + +ASTPointer<TypeName> Parser::typeNameFromIndexAccessStructure(Parser::IndexAccessedPath const& _iap) { - solAssert(!_path.empty(), ""); + if (_iap.empty()) + return {}; + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); - SourceLocation location = _path.front()->location(); - location.end = _path.back()->location().end; + SourceLocation location = _iap.path.front()->location(); + location.end = _iap.path.back()->location().end; nodeFactory.setLocation(location); ASTPointer<TypeName> type; - if (auto typeName = dynamic_cast<ElementaryTypeNameExpression const*>(_path.front().get())) + if (auto typeName = dynamic_cast<ElementaryTypeNameExpression const*>(_iap.path.front().get())) { - solAssert(_path.size() == 1, ""); + solAssert(_iap.path.size() == 1, ""); type = nodeFactory.createNode<ElementaryTypeName>(typeName->typeName()); } else { vector<ASTString> path; - for (auto const& el: _path) + for (auto const& el: _iap.path) path.push_back(dynamic_cast<Identifier const&>(*el).name()); type = nodeFactory.createNode<UserDefinedTypeName>(path); } - for (auto const& lengthExpression: _indices) + for (auto const& lengthExpression: _iap.indices) { nodeFactory.setLocation(lengthExpression.second); type = nodeFactory.createNode<ArrayTypeName>(type, lengthExpression.first); @@ -1563,26 +1598,27 @@ ASTPointer<TypeName> Parser::typeNameIndexAccessStructure( } ASTPointer<Expression> Parser::expressionFromIndexAccessStructure( - vector<ASTPointer<PrimaryExpression>> const& _path, - vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices + Parser::IndexAccessedPath const& _iap ) { - solAssert(!_path.empty(), ""); + if (_iap.empty()) + return {}; + RecursionGuard recursionGuard(*this); - ASTNodeFactory nodeFactory(*this, _path.front()); - ASTPointer<Expression> expression(_path.front()); - for (size_t i = 1; i < _path.size(); ++i) + ASTNodeFactory nodeFactory(*this, _iap.path.front()); + ASTPointer<Expression> expression(_iap.path.front()); + for (size_t i = 1; i < _iap.path.size(); ++i) { - SourceLocation location(_path.front()->location()); - location.end = _path[i]->location().end; + SourceLocation location(_iap.path.front()->location()); + location.end = _iap.path[i]->location().end; nodeFactory.setLocation(location); - Identifier const& identifier = dynamic_cast<Identifier const&>(*_path[i]); + Identifier const& identifier = dynamic_cast<Identifier const&>(*_iap.path[i]); expression = nodeFactory.createNode<MemberAccess>( expression, make_shared<ASTString>(identifier.name()) ); } - for (auto const& index: _indices) + for (auto const& index: _iap.indices) { nodeFactory.setLocation(index.second); expression = nodeFactory.createNode<IndexAccess>(expression, index.first); @@ -1598,40 +1634,10 @@ ASTPointer<ParameterList> Parser::createEmptyParameterList() return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>()); } -string Parser::currentTokenName() -{ - Token::Value token = m_scanner->currentToken(); - if (Token::isElementaryTypeName(token)) //for the sake of accuracy in reporting - { - ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); - return elemTypeName.toString(); - } - else - return Token::name(token); -} - -Token::Value Parser::expectAssignmentOperator() -{ - Token::Value op = m_scanner->currentToken(); - if (!Token::isAssignmentOp(op)) - fatalParserError( - string("Expected assignment operator, got '") + - currentTokenName() + - string("'") - ); - m_scanner->next(); - return op; -} - ASTPointer<ASTString> Parser::expectIdentifierToken() { - Token::Value id = m_scanner->currentToken(); - if (id != Token::Identifier) - fatalParserError( - string("Expected identifier, got '") + - currentTokenName() + - string("'") - ); + // do not advance on success + expectToken(Token::Identifier, false); return getLiteralAndAdvance(); } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index eb120a61..08653364 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -118,19 +118,19 @@ private: ); ASTPointer<ExpressionStatement> parseExpressionStatement( ASTPointer<ASTString> const& _docString, - ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>() + ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>() ); ASTPointer<Expression> parseExpression( - ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>() + ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>() ); ASTPointer<Expression> parseBinaryExpression(int _minPrecedence = 4, - ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>() + ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>() ); ASTPointer<Expression> parseUnaryExpression( - ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>() + ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>() ); ASTPointer<Expression> parseLeftHandSideExpression( - ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>() + ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>() ); ASTPointer<Expression> parsePrimaryExpression(); std::vector<ASTPointer<Expression>> parseFunctionCallListArguments(); @@ -143,26 +143,32 @@ private: /// Used as return value of @see peekStatementType. enum class LookAheadInfo { - IndexAccessStructure, VariableDeclarationStatement, ExpressionStatement + IndexAccessStructure, VariableDeclaration, Expression + }; + /// Structure that represents a.b.c[x][y][z]. Can be converted either to an expression + /// or to a type name. For this to be valid, path cannot be empty, but indices can be empty. + struct IndexAccessedPath + { + std::vector<ASTPointer<PrimaryExpression>> path; + std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> indices; + bool empty() const; }; + std::pair<LookAheadInfo, IndexAccessedPath> tryParseIndexAccessedPath(); /// Performs limited look-ahead to distinguish between variable declaration and expression statement. /// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to /// decide with constant look-ahead. LookAheadInfo peekStatementType() const; - /// @returns a typename parsed in look-ahead fashion from something like "a.b[8][2**70]". - ASTPointer<TypeName> typeNameIndexAccessStructure( - std::vector<ASTPointer<PrimaryExpression>> const& _path, - std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices - ); - /// @returns an expression parsed in look-ahead fashion from something like "a.b[8][2**70]". - ASTPointer<Expression> expressionFromIndexAccessStructure( - std::vector<ASTPointer<PrimaryExpression>> const& _path, - std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices - ); + /// @returns an IndexAccessedPath as a prestage to parsing a variable declaration (type name) + /// or an expression; + IndexAccessedPath parseIndexAccessedPath(); + /// @returns a typename parsed in look-ahead fashion from something like "a.b[8][2**70]", + /// or an empty pointer if an empty @a _pathAndIncides has been supplied. + ASTPointer<TypeName> typeNameFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices); + /// @returns an expression parsed in look-ahead fashion from something like "a.b[8][2**70]", + /// or an empty pointer if an empty @a _pathAndIncides has been supplied. + ASTPointer<Expression> expressionFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices); - std::string currentTokenName(); - Token::Value expectAssignmentOperator(); ASTPointer<ASTString> expectIdentifierToken(); ASTPointer<ASTString> getLiteralAndAdvance(); ///@} diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index 5b83c5bd..71133746 100644 --- a/libsolidity/parsing/ParserBase.cpp +++ b/libsolidity/parsing/ParserBase.cpp @@ -63,42 +63,32 @@ Token::Value ParserBase::advance() return m_scanner->next(); } -void ParserBase::expectToken(Token::Value _value) +void ParserBase::expectToken(Token::Value _value, bool _advance) { Token::Value tok = m_scanner->currentToken(); if (tok != _value) { - if (Token::isReservedKeyword(tok)) + auto tokenName = [this](Token::Value _token) { - fatalParserError( - string("Expected token ") + - string(Token::name(_value)) + - string(" got reserved keyword '") + - string(Token::name(tok)) + - string("'") - ); - } - else if (Token::isElementaryTypeName(tok)) //for the sake of accuracy in reporting - { - ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); - fatalParserError( - string("Expected token ") + - string(Token::name(_value)) + - string(" got '") + - elemTypeName.toString() + - string("'") - ); - } - else - fatalParserError( - string("Expected token ") + - string(Token::name(_value)) + - string(" got '") + - string(Token::name(m_scanner->currentToken())) + - string("'") - ); + if (_token == Token::Identifier) + return string("identifier"); + else if (_token == Token::EOS) + return string("end of source"); + else if (Token::isReservedKeyword(_token)) + return string("reserved keyword '") + Token::friendlyName(_token) + "'"; + else if (Token::isElementaryTypeName(_token)) //for the sake of accuracy in reporting + { + ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); + return string("'") + elemTypeName.toString() + "'"; + } + else + return string("'") + Token::friendlyName(_token) + "'"; + }; + + fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok)); } - m_scanner->next(); + if (_advance) + m_scanner->next(); } void ParserBase::increaseRecursionDepth() @@ -116,10 +106,10 @@ void ParserBase::decreaseRecursionDepth() void ParserBase::parserError(string const& _description) { - m_errorReporter.parserError(SourceLocation(position(), position(), sourceName()), _description); + m_errorReporter.parserError(SourceLocation(position(), endPosition(), sourceName()), _description); } void ParserBase::fatalParserError(string const& _description) { - m_errorReporter.fatalParserError(SourceLocation(position(), position(), sourceName()), _description); + m_errorReporter.fatalParserError(SourceLocation(position(), endPosition(), sourceName()), _description); } diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h index fd0de0d1..b28e1b1b 100644 --- a/libsolidity/parsing/ParserBase.h +++ b/libsolidity/parsing/ParserBase.h @@ -63,7 +63,7 @@ protected: ///@{ ///@name Helper functions /// If current token value is not _value, throw exception otherwise advance token. - void expectToken(Token::Value _value); + void expectToken(Token::Value _value, bool _advance = true); Token::Value currentToken() const; Token::Value peekNextToken() const; std::string currentLiteral() const; diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index 805fbf5d..4d7a7bc6 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -304,6 +304,17 @@ public: return m_string[tok]; } + static std::string friendlyName(Value tok) + { + char const* ret = toString(tok); + if (ret == nullptr) + { + ret = name(tok); + solAssert(ret != nullptr, ""); + } + return std::string(ret); + } + // @returns the precedence > 0 for binary and compare // operators; returns 0 otherwise. static int precedence(Value tok) diff --git a/lllc/CMakeLists.txt b/lllc/CMakeLists.txt index 5c480093..d6538ee2 100644 --- a/lllc/CMakeLists.txt +++ b/lllc/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(lllc main.cpp) -target_link_libraries(lllc PRIVATE lll) +target_link_libraries(lllc PRIVATE lll ${Boost_SYSTEM_LIBRARY}) if (INSTALL_LLLC) include(GNUInstallDirs) diff --git a/scripts/create_source_tarball.sh b/scripts/create_source_tarball.sh index 9e66799a..4e930707 100755 --- a/scripts/create_source_tarball.sh +++ b/scripts/create_source_tarball.sh @@ -32,7 +32,7 @@ REPO_ROOT="$(dirname "$0")"/.. fi # Add dependencies mkdir -p "$SOLDIR/deps/downloads/" 2>/dev/null || true - wget -O "$SOLDIR/deps/downloads/jsoncpp-1.7.7.tar.gz" https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz + wget -O "$SOLDIR/deps/downloads/jsoncpp-1.8.4.tar.gz" https://github.com/open-source-parsers/jsoncpp/archive/1.8.4.tar.gz mkdir -p "$REPO_ROOT/upload" tar czf "$REPO_ROOT/upload/solidity_$versionstring.tar.gz" -C "$TEMPDIR" "solidity_$versionstring" rm -r "$TEMPDIR" diff --git a/scripts/install_cmake.sh b/scripts/install_cmake.sh index 00d013b9..e334b2c9 100755 --- a/scripts/install_cmake.sh +++ b/scripts/install_cmake.sh @@ -18,6 +18,8 @@ esac BIN=$PREFIX/bin +PATH=$PREFIX/bin:$PATH + if test -f $BIN/cmake && ($BIN/cmake --version | grep -q "$VERSION"); then echo "CMake $VERSION already installed in $BIN" else diff --git a/scripts/release.bat b/scripts/release.bat index 54d85862..be95b35e 100644 --- a/scripts/release.bat +++ b/scripts/release.bat @@ -27,7 +27,19 @@ REM Copyright (c) 2016 solidity contributors. REM --------------------------------------------------------------------------- set CONFIGURATION=%1 +set VERSION=%2 + +IF "%VERSION%"=="2015" ( + set "DLLS=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" +) ELSE ( + + IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\14.13.26020\x86\Microsoft.VC141.CRT\" ( + set "DLLS=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\14.13.26020\x86\Microsoft.VC141.CRT\msvc*.dll" + ) ELSE ( + set "DLLS=C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Redist\MSVC\14.13.26020\x86\Microsoft.VC141.CRT\msvc*.dll" + ) +) 7z a solidity-windows.zip ^ .\build\solc\%CONFIGURATION%\solc.exe .\build\test\%CONFIGURATION%\soltest.exe ^ - "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" + "%DLLS%" diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 375b4d1b..b1601336 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -76,7 +76,7 @@ mv solidity solc # Fetch jsoncpp dependency mkdir -p ./solc/deps/downloads/ 2>/dev/null || true -wget -O ./solc/deps/downloads/jsoncpp-1.7.7.tar.gz https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz +wget -O ./solc/deps/downloads/jsoncpp-1.8.4.tar.gz https://github.com/open-source-parsers/jsoncpp/archive/1.8.4.tar.gz # Determine version cd solc diff --git a/scripts/tests.sh b/scripts/tests.sh index 38073bf3..d63c1fe4 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -30,6 +30,17 @@ set -e REPO_ROOT="$(dirname "$0")"/.. +IPC_ENABLED=true +if [[ "$OSTYPE" == "darwin"* ]] +then + SMT_FLAGS="--no-smt" + if [ "$CIRCLECI" ] + then + IPC_ENABLED=false + IPC_FLAGS="--no-ipc" + fi +fi + if [ "$1" = --junit_report ] then if [ -z "$2" ] @@ -98,8 +109,11 @@ function run_eth() sleep 2 } -download_eth -ETH_PID=$(run_eth /tmp/test) +if [ "$IPC_ENABLED" = true ]; +then + download_eth + ETH_PID=$(run_eth /tmp/test) +fi progress="--show-progress" if [ "$CIRCLECI" ] @@ -131,7 +145,7 @@ do log=--logger=JUNIT,test_suite,$log_directory/noopt_$vm.xml $testargs_no_opt fi fi - "$REPO_ROOT"/build/test/soltest $progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" --ipcpath /tmp/test/geth.ipc + "$REPO_ROOT"/build/test/soltest $progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $IPC_FLAGS --ipcpath /tmp/test/geth.ipc done done @@ -141,6 +155,9 @@ then exit 1 fi -pkill "$ETH_PID" || true -sleep 4 -pgrep "$ETH_PID" && pkill -9 "$ETH_PID" || true +if [ "$IPC_ENABLED" = true ] +then + pkill "$ETH_PID" || true + sleep 4 + pgrep "$ETH_PID" && pkill -9 "$ETH_PID" || true +fi diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index fd643429..f49ff5b2 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -40,6 +40,12 @@ if ! type git &>/dev/null; then apt-get -y install git-core fi +if ! type wget &>/dev/null; then + # We need wget to install cmake + apt-get update + apt-get -y install wget +fi + WORKSPACE=/root/project # Increase nodejs stack size @@ -67,6 +73,10 @@ rm -rf b2 libs doc tools more bin.v2 status ) echo -en 'travis_fold:end:compiling_boost\\r' +echo -en 'travis_fold:start:install_cmake.sh\\r' +source $WORKSPACE/scripts/install_cmake.sh +echo -en 'travis_fold:end:install_cmake.sh\\r' + # Build dependent components and solidity itself echo -en 'travis_fold:start:compiling_solidity\\r' cd $WORKSPACE diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index bd5e2eb1..1f04c68a 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -564,7 +564,8 @@ Allowed options)", ( g_argOptimizeRuns.c_str(), po::value<unsigned>()->value_name("n")->default_value(200), - "Estimated number of contract runs for optimizer tuning." + "Set for how many contract runs to optimize." + "Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage." ) (g_argPrettyJson.c_str(), "Output JSON in pretty format. Currently it only works with the combined JSON output.") ( diff --git a/std/StandardToken.sol b/std/StandardToken.sol index ca0658f2..c2fc3a66 100644 --- a/std/StandardToken.sol +++ b/std/StandardToken.sol @@ -13,11 +13,11 @@ contract StandardToken is Token { balance[_initialOwner] = _supply; } - function balanceOf(address _account) constant public returns (uint) { + function balanceOf(address _account) view public returns (uint) { return balance[_account]; } - function totalSupply() constant public returns (uint) { + function totalSupply() view public returns (uint) { return supply; } @@ -53,7 +53,7 @@ contract StandardToken is Token { return true; } - function allowance(address _owner, address _spender) constant public returns (uint256) { + function allowance(address _owner, address _spender) view public returns (uint256) { return m_allowance[_owner][_spender]; } } diff --git a/std/Token.sol b/std/Token.sol index 4b4eb71e..7348a8f5 100644 --- a/std/Token.sol +++ b/std/Token.sol @@ -4,10 +4,10 @@ contract Token { event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); - function totalSupply() constant public returns (uint256 supply); - function balanceOf(address _owner) constant public returns (uint256 balance); + function totalSupply() view public returns (uint256 supply); + function balanceOf(address _owner) view public returns (uint256 balance); function transfer(address _to, uint256 _value) public returns (bool success); function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); function approve(address _spender, uint256 _value) public returns (bool success); - function allowance(address _owner, address _spender) constant public returns (uint256 remaining); + function allowance(address _owner, address _spender) view public returns (uint256 remaining); } diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index ee8da322..4525cbf9 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -53,6 +53,7 @@ class ExecutionFramework public: ExecutionFramework(); + virtual ~ExecutionFramework() = default; virtual bytes const& compileAndRunWithoutCheck( std::string const& _sourceCode, diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 089be45d..4b399a14 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1072,6 +1072,75 @@ BOOST_AUTO_TEST_CASE(cse_sub_zero) }); } +BOOST_AUTO_TEST_CASE(cse_remove_unwanted_masking_of_address) +{ + vector<Instruction> ops{ + Instruction::ADDRESS, + Instruction::CALLER, + Instruction::ORIGIN, + Instruction::COINBASE + }; + for (auto const& op: ops) + { + checkCSE({ + u256("0xffffffffffffffffffffffffffffffffffffffff"), + op, + Instruction::AND + }, { + op + }); + + checkCSE({ + op, + u256("0xffffffffffffffffffffffffffffffffffffffff"), + Instruction::AND + }, { + op + }); + + // do not remove mask for other masking + checkCSE({ + u256(1234), + op, + Instruction::AND + }, { + op, + u256(1234), + Instruction::AND + }); + + checkCSE({ + op, + u256(1234), + Instruction::AND + }, { + u256(1234), + op, + Instruction::AND + }); + } + + // leave other opcodes untouched + checkCSE({ + u256("0xffffffffffffffffffffffffffffffffffffffff"), + Instruction::CALLVALUE, + Instruction::AND + }, { + Instruction::CALLVALUE, + u256("0xffffffffffffffffffffffffffffffffffffffff"), + Instruction::AND + }); + + checkCSE({ + Instruction::CALLVALUE, + u256("0xffffffffffffffffffffffffffffffffffffffff"), + Instruction::AND + }, { + u256("0xffffffffffffffffffffffffffffffffffffffff"), + Instruction::CALLVALUE, + Instruction::AND + }); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libjulia/Inliner.cpp b/test/libjulia/Inliner.cpp index 88b51f28..464dcd93 100644 --- a/test/libjulia/Inliner.cpp +++ b/test/libjulia/Inliner.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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/>. + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. */ /** * @date 2017 @@ -23,6 +23,9 @@ #include <libjulia/optimiser/ExpressionInliner.h> #include <libjulia/optimiser/InlinableExpressionFunctionFinder.h> +#include <libjulia/optimiser/FullInliner.h> +#include <libjulia/optimiser/FunctionHoister.h> +#include <libjulia/optimiser/FunctionGrouper.h> #include <libsolidity/inlineasm/AsmPrinter.h> @@ -58,8 +61,17 @@ string inlineFunctions(string const& _source, bool _julia = true) ExpressionInliner(ast).run(); return assembly::AsmPrinter(_julia)(ast); } +string fullInline(string const& _source, bool _julia = true) +{ + Block ast = disambiguate(_source, _julia); + (FunctionHoister{})(ast); + (FunctionGrouper{})(ast);\ + FullInliner(ast).run(); + return assembly::AsmPrinter(_julia)(ast); +} } + BOOST_AUTO_TEST_SUITE(IuliaInlinableFunctionFilter) BOOST_AUTO_TEST_CASE(smoke_test) @@ -197,3 +209,135 @@ BOOST_AUTO_TEST_CASE(double_recursive_calls) } BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(IuliaFullInliner) + +BOOST_AUTO_TEST_CASE(simple) +{ + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a) -> x { let r := mul(a, a) x := add(r, r) }" + "let y := add(f(sload(mload(2))), mload(7))" + "}", false), + format("{" + "{" + "let _1 := mload(7)" + "let f_a := sload(mload(2))" + "let f_x" + "{" + "let f_r := mul(f_a, f_a)" + "f_x := add(f_r, f_r)" + "}" + "let y := add(f_x, _1)" + "}" + "function f(a) -> x" + "{" + "let r := mul(a, a)" + "x := add(r, r)" + "}" + "}", false) + ); +} + +BOOST_AUTO_TEST_CASE(multi_fun) +{ + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a) -> x { x := add(a, a) }" + "function g(b, c) -> y { y := mul(mload(c), f(b)) }" + "let y := g(f(3), 7)" + "}", false), + format("{" + "{" + "let g_c := 7 " + "let f_a_1 := 3 " + "let f_x_1 " + "{ f_x_1 := add(f_a_1, f_a_1) } " + "let g_y " + "{" + "let g_f_a := f_x_1 " + "let g_f_x " + "{" + "g_f_x := add(g_f_a, g_f_a)" + "}" + "g_y := mul(mload(g_c), g_f_x)" + "}" + "let y_1 := g_y" + "}" + "function f(a) -> x" + "{" + "x := add(a, a)" + "}" + "function g(b, c) -> y" + "{" + "let f_a := b " + "let f_x " + "{" + "f_x := add(f_a, f_a)" + "}" + "y := mul(mload(c), f_x)" + "}" + "}", false) + ); +} + +BOOST_AUTO_TEST_CASE(move_up_rightwards_arguments) +{ + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a, b, c) -> x { x := add(a, b) x := mul(x, c) }" + "let y := add(mload(1), add(f(mload(2), mload(3), mload(4)), mload(5)))" + "}", false), + format("{" + "{" + "let _1 := mload(5)" + "let f_c := mload(4)" + "let f_b := mload(3)" + "let f_a := mload(2)" + "let f_x" + "{" + "f_x := add(f_a, f_b)" + "f_x := mul(f_x, f_c)" + "}" + "let y := add(mload(1), add(f_x, _1))" + "}" + "function f(a, b, c) -> x" + "{" + "x := add(a, b)" + "x := mul(x, c)" + "}" + "}", false) + ); +} + +BOOST_AUTO_TEST_CASE(pop_result) +{ + // This tests that `pop(r)` is removed. + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a) -> x { let r := mul(a, a) x := add(r, r) }" + "pop(add(f(7), 2))" + "}", false), + format("{" + "{" + "let _1 := 2 " + "let f_a := 7 " + "let f_x " + "{" + "let f_r := mul(f_a, f_a) " + "f_x := add(f_r, f_r)" + "}" + "{" + "}" + "}" + "function f(a) -> x" + "{" + "let r := mul(a, a) " + "x := add(r, r)" + "}" + "}", false) + ); +} + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libjulia/MainFunction.cpp b/test/libjulia/MainFunction.cpp new file mode 100644 index 00000000..c26b002d --- /dev/null +++ b/test/libjulia/MainFunction.cpp @@ -0,0 +1,87 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @date 2018 + * Unit tests for the Julia MainFunction transformation. + */ + +#include <test/libjulia/Common.h> + +#include <libjulia/optimiser/FunctionGrouper.h> +#include <libjulia/optimiser/MainFunction.h> + +#include <libsolidity/inlineasm/AsmPrinter.h> + +#include <boost/test/unit_test.hpp> + +using namespace std; +using namespace dev::julia; +using namespace dev::julia::test; +using namespace dev::solidity; + +#define CHECK(_original, _expectation)\ +do\ +{\ + assembly::AsmPrinter p(true);\ + Block b = disambiguate(_original);\ + (FunctionGrouper{})(b);\ + (MainFunction{})(b);\ + string result = p(b);\ + BOOST_CHECK_EQUAL(result, format(_expectation));\ +}\ +while(false) + +BOOST_AUTO_TEST_SUITE(JuliaMainFunction) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + CHECK("{ }", "{ function main() { } }"); +} + +BOOST_AUTO_TEST_CASE(single_fun) +{ + CHECK( + "{ let a:u256 function f() {} }", + "{ function main() { let a:u256 } function f() {} }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_fun_mixed) +{ + CHECK( + "{ let a:u256 function f() { let b:u256 } let c:u256 function g() { let d:u256 } let e:u256 }", + "{ function main() { let a:u256 let c:u256 let e:u256 } function f() { let b:u256 } function g() { let d:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(nested_fun) +{ + CHECK( + "{ let a:u256 function f() { let b:u256 function g() { let c:u256} let d:u256 } }", + "{ function main() { let a:u256 } function f() { let b:u256 function g() { let c:u256} let d:u256 } }" + ); +} + +BOOST_AUTO_TEST_CASE(empty_block) +{ + CHECK( + "{ let a:u256 { } function f() -> x:bool { let b:u256 := 4:u256 {} for {} f() {} {} } }", + "{ function main() { let a:u256 { } } function f() -> x:bool { let b:u256 := 4:u256 {} for {} f() {} {} } }" + ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index 9d66658e..96261dec 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -212,10 +212,10 @@ BOOST_AUTO_TEST_CASE(tokens_as_identifers) BOOST_AUTO_TEST_CASE(lacking_types) { - CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected token Identifier got 'Assign'"); - CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected token Colon got 'RBrace'"); - CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected token Colon got 'RParen'"); - CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected token Colon got 'LBrace'"); + CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected identifier but got '='"); + CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected ':' but got '}'"); + CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected ':' but got ')'"); + CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected ':' but got '{'"); } BOOST_AUTO_TEST_CASE(invalid_types) @@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(if_statement) BOOST_AUTO_TEST_CASE(if_statement_invalid) { CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); - CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected token LBrace"); + CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected '{' but got reserved keyword 'let'"); // TODO change this to an error once we check types. BOOST_CHECK(successParse("{ if 42:u256 { } }")); } diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h index 05490a42..a904617d 100644 --- a/test/libsolidity/AnalysisFramework.h +++ b/test/libsolidity/AnalysisFramework.h @@ -52,6 +52,7 @@ protected: bool _insertVersionPragma = true, bool _allowMultipleErrors = false ); + virtual ~AnalysisFramework() = default; SourceUnit const* parseAndAnalyse(std::string const& _source); bool success(std::string const& _source); diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 77ca363a..7b3df043 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -158,8 +158,9 @@ BOOST_AUTO_TEST_CASE(location_test) } )"; AssemblyItems items = compileContract(sourceCode); + bool hasShifts = dev::test::Options::get().evmVersion().hasBitwiseShifting(); vector<SourceLocation> locations = - vector<SourceLocation>(24, SourceLocation(2, 75, make_shared<string>(""))) + + vector<SourceLocation>(hasShifts ? 23 : 24, SourceLocation(2, 75, make_shared<string>(""))) + vector<SourceLocation>(2, SourceLocation(20, 72, make_shared<string>(""))) + vector<SourceLocation>(1, SourceLocation(8, 17, make_shared<string>("--CODEGEN--"))) + vector<SourceLocation>(3, SourceLocation(5, 7, make_shared<string>("--CODEGEN--"))) + @@ -172,8 +173,6 @@ BOOST_AUTO_TEST_CASE(location_test) vector<SourceLocation>(1, SourceLocation(65, 67, make_shared<string>(""))) + vector<SourceLocation>(2, SourceLocation(58, 67, make_shared<string>(""))) + vector<SourceLocation>(2, SourceLocation(20, 72, make_shared<string>(""))); - - checkAssemblyLocations(items, locations); } diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 0d66456c..f16d9abe 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -307,6 +307,46 @@ BOOST_AUTO_TEST_CASE(regular_functions_exclude_fallback) testCreationTimeGas(sourceCode); testRunTimeGas("x()", vector<bytes>{encodeArgs()}); } + +BOOST_AUTO_TEST_CASE(complex_control_flow) +{ + // This crashed the gas estimator previously (or took a very long time). + // Now we do not follow branches if they start out with lower gas costs than the ones + // we previously considered. This of course reduces accuracy. + char const* sourceCode = R"( + contract log { + function ln(int128 x) constant returns (int128 result) { + int128 t = x / 256; + int128 y = 5545177; + x = t; + t = x * 16; if (t <= 1000000) { x = t; y = y - 2772588; } + t = x * 4; if (t <= 1000000) { x = t; y = y - 1386294; } + t = x * 2; if (t <= 1000000) { x = t; y = y - 693147; } + t = x + x / 2; if (t <= 1000000) { x = t; y = y - 405465; } + t = x + x / 4; if (t <= 1000000) { x = t; y = y - 223144; } + t = x + x / 8; if (t <= 1000000) { x = t; y = y - 117783; } + t = x + x / 16; if (t <= 1000000) { x = t; y = y - 60624; } + t = x + x / 32; if (t <= 1000000) { x = t; y = y - 30771; } + t = x + x / 64; if (t <= 1000000) { x = t; y = y - 15504; } + t = x + x / 128; if (t <= 1000000) { x = t; y = y - 7782; } + t = x + x / 256; if (t <= 1000000) { x = t; y = y - 3898; } + t = x + x / 512; if (t <= 1000000) { x = t; y = y - 1951; } + t = x + x / 1024; if (t <= 1000000) { x = t; y = y - 976; } + t = x + x / 2048; if (t <= 1000000) { x = t; y = y - 488; } + t = x + x / 4096; if (t <= 1000000) { x = t; y = y - 244; } + t = x + x / 8192; if (t <= 1000000) { x = t; y = y - 122; } + t = x + x / 16384; if (t <= 1000000) { x = t; y = y - 61; } + t = x + x / 32768; if (t <= 1000000) { x = t; y = y - 31; } + t = x + x / 65536; if (t <= 1000000) { y = y - 15; } + return y; + } + } + )"; + testCreationTimeGas(sourceCode); + // max gas is used for small x + testRunTimeGas("ln(int128)", vector<bytes>{encodeArgs(0), encodeArgs(10), encodeArgs(105), encodeArgs(30000)}); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 0ced1792..181ca959 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -170,7 +170,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_AUTO_TEST_CASE(surplus_input) { - CHECK_PARSE_ERROR("{ } { }", ParserError, "Expected token EOS"); + CHECK_PARSE_ERROR("{ } { }", ParserError, "Expected end of source but got '{'"); } BOOST_AUTO_TEST_CASE(simple_instructions) @@ -246,7 +246,7 @@ BOOST_AUTO_TEST_CASE(functional) BOOST_AUTO_TEST_CASE(functional_partial) { - CHECK_PARSE_ERROR("{ let x := byte }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ let x := byte }", ParserError, "Expected '(' (instruction \"byte\" expects 2 arguments)"); } BOOST_AUTO_TEST_CASE(functional_partial_success) @@ -290,10 +290,10 @@ BOOST_AUTO_TEST_CASE(if_statement_scope) BOOST_AUTO_TEST_CASE(if_statement_invalid) { - CHECK_PARSE_ERROR("{ if mload {} }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ if mload {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)"); BOOST_CHECK("{ if calldatasize() {}"); CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); - CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace"); + CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected '{' but got reserved keyword 'let'"); } BOOST_AUTO_TEST_CASE(switch_statement) @@ -320,7 +320,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case) BOOST_AUTO_TEST_CASE(switch_invalid_expression) { CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); - CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)"); CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } @@ -341,7 +341,7 @@ BOOST_AUTO_TEST_CASE(switch_invalid_case) BOOST_AUTO_TEST_CASE(switch_invalid_body) { - CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected token LBrace got 'Identifier'"); + CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected '{' but got identifier"); } BOOST_AUTO_TEST_CASE(for_statement) @@ -353,10 +353,10 @@ BOOST_AUTO_TEST_CASE(for_statement) BOOST_AUTO_TEST_CASE(for_invalid_expression) { CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected."); - CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected token LBrace got 'Number'"); - CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected token LBrace got 'Number'"); - CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected token LBrace got 'Number'"); - CHECK_PARSE_ERROR("{ for {} mload {} {} }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected '{' but got 'Number'"); + CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected '{' but got 'Number'"); + CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected '{' but got 'Number'"); + CHECK_PARSE_ERROR("{ for {} mload {} {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)"); CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } @@ -437,13 +437,13 @@ BOOST_AUTO_TEST_CASE(invalid_tuple_assignment) BOOST_AUTO_TEST_CASE(instruction_too_few_arguments) { - CHECK_PARSE_ERROR("{ mul() }", ParserError, "Expected expression (\"mul\" expects 2 arguments)"); - CHECK_PARSE_ERROR("{ mul(1) }", ParserError, "Expected comma (\"mul\" expects 2 arguments)"); + CHECK_PARSE_ERROR("{ mul() }", ParserError, "Expected expression (instruction \"mul\" expects 2 arguments)"); + CHECK_PARSE_ERROR("{ mul(1) }", ParserError, "Expected ',' (instruction \"mul\" expects 2 arguments)"); } BOOST_AUTO_TEST_CASE(instruction_too_many_arguments) { - CHECK_PARSE_ERROR("{ mul(1, 2, 3) }", ParserError, "Expected ')' (\"mul\" expects 2 arguments)"); + CHECK_PARSE_ERROR("{ mul(1, 2, 3) }", ParserError, "Expected ')' (instruction \"mul\" expects 2 arguments)"); } BOOST_AUTO_TEST_CASE(recursion_depth) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 789b89d1..4b09d253 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2105,7 +2105,7 @@ BOOST_AUTO_TEST_CASE(packed_keccak256_complex_types) char const* sourceCode = R"( contract test { uint120[3] x; - function f() view returns (bytes32 hash1, bytes32 hash2, bytes32 hash3) { + function f() returns (bytes32 hash1, bytes32 hash2, bytes32 hash3) { uint120[] memory y = new uint120[](3); x[0] = y[0] = uint120(-2); x[1] = y[1] = uint120(-3); @@ -10122,6 +10122,23 @@ BOOST_AUTO_TEST_CASE(shift_right_assignment) ABI_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)), encodeArgs(u256(0))); } +BOOST_AUTO_TEST_CASE(shift_right_assignment_signed) +{ + char const* sourceCode = R"( + contract C { + function f(int a, int b) returns (int) { + a >>= b; + return a; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(0x4266), u256(0)), encodeArgs(u256(0x4266))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(0x4266), u256(8)), encodeArgs(u256(0x42))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(0x4266), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(0x4266), u256(17)), encodeArgs(u256(0))); +} + BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue) { char const* sourceCode = R"( @@ -10133,9 +10150,141 @@ BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue) )"; compileAndRun(sourceCode, 0, "C"); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(0)), encodeArgs(u256(-4266))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(4)), encodeArgs(u256(-266))); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(8)), encodeArgs(u256(-16))); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(16)), encodeArgs(u256(0))); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(17)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(0)), encodeArgs(u256(-4267))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(4)), encodeArgs(u256(-266))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(8)), encodeArgs(u256(-16))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(17)), encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_int8) +{ + char const* sourceCode = R"( + contract C { + function f(int8 a, int8 b) returns (int) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-66), u256(0)), encodeArgs(u256(-66))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-66), u256(1)), encodeArgs(u256(-33))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-66), u256(4)), encodeArgs(u256(-4))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-66), u256(8)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-66), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-66), u256(17)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-67), u256(0)), encodeArgs(u256(-67))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-67), u256(1)), encodeArgs(u256(-33))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-67), u256(4)), encodeArgs(u256(-4))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-67), u256(8)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-67), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(-67), u256(17)), encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int8) +{ + char const* sourceCode = R"( + contract C { + function f(int8 a, int8 b) returns (int8) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(0)), encodeArgs(u256(-103))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(1)), encodeArgs(u256(-51))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(2)), encodeArgs(u256(-25))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(4)), encodeArgs(u256(-6))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(8)), encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int16) +{ + char const* sourceCode = R"( + contract C { + function f(int16 a, int16 b) returns (int16) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(0)), encodeArgs(u256(-103))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(1)), encodeArgs(u256(-51))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(2)), encodeArgs(u256(-25))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(4)), encodeArgs(u256(-6))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(8)), encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int32) +{ + char const* sourceCode = R"( + contract C { + function f(int32 a, int32 b) returns (int32) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(0)), encodeArgs(u256(-103))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(1)), encodeArgs(u256(-51))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(2)), encodeArgs(u256(-25))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(4)), encodeArgs(u256(-6))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(8)), encodeArgs(u256(0))); +} + + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_int16) +{ + char const* sourceCode = R"( + contract C { + function f(int16 a, int16 b) returns (int) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4266), u256(0)), encodeArgs(u256(-4266))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4266), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4266), u256(4)), encodeArgs(u256(-266))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4266), u256(8)), encodeArgs(u256(-16))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4266), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4266), u256(17)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4267), u256(0)), encodeArgs(u256(-4267))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4267), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4267), u256(4)), encodeArgs(u256(-266))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4267), u256(8)), encodeArgs(u256(-16))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4267), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(-4267), u256(17)), encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_int32) +{ + char const* sourceCode = R"( + contract C { + function f(int32 a, int32 b) returns (int) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4266), u256(0)), encodeArgs(u256(-4266))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4266), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4266), u256(4)), encodeArgs(u256(-266))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4266), u256(8)), encodeArgs(u256(-16))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4266), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4266), u256(17)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4267), u256(0)), encodeArgs(u256(-4267))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4267), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4267), u256(4)), encodeArgs(u256(-266))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4267), u256(8)), encodeArgs(u256(-16))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4267), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(-4267), u256(17)), encodeArgs(u256(0))); } BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_assignment) @@ -10150,9 +10299,17 @@ BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_assignment) )"; compileAndRun(sourceCode, 0, "C"); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(0)), encodeArgs(u256(-4266))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(4)), encodeArgs(u256(-266))); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(8)), encodeArgs(u256(-16))); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(16)), encodeArgs(u256(0))); ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(17)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(0)), encodeArgs(u256(-4267))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(1)), encodeArgs(u256(-2133))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(4)), encodeArgs(u256(-266))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(8)), encodeArgs(u256(-16))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(16)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(int256,int256)", u256(-4267), u256(17)), encodeArgs(u256(0))); } BOOST_AUTO_TEST_CASE(shift_negative_rvalue) @@ -10720,9 +10877,9 @@ BOOST_AUTO_TEST_CASE(negative_stack_height) bool Aboolc; bool exists; } - function nredit(uint startindex) public constant returns(uint[500] CIDs, uint[500] dates, uint[500] RIDs, bool[500] Cboolas, uint[500] amounts){} - function return500InvoicesByDates(uint begindate, uint enddate, uint startindex) public constant returns(uint[500] AIDs, bool[500] Aboolas, uint[500] dates, bytes32[3][500] Abytesas, bytes32[3][500] bytesbs, bytes32[2][500] bytescs, uint[500] amounts, bool[500] Aboolbs, bool[500] Aboolcs){} - function return500PaymentsByDates(uint begindate, uint enddate, uint startindex) public constant returns(uint[500] BIDs, uint[500] dates, uint[500] RIDs, bool[500] Bboolas, bytes32[3][500] bytesbs,bytes32[2][500] bytescs, uint[500] amounts, bool[500] Bboolbs){} + function nredit(uint startindex) public pure returns(uint[500] CIDs, uint[500] dates, uint[500] RIDs, bool[500] Cboolas, uint[500] amounts){} + function return500InvoicesByDates(uint begindate, uint enddate, uint startindex) public view returns(uint[500] AIDs, bool[500] Aboolas, uint[500] dates, bytes32[3][500] Abytesas, bytes32[3][500] bytesbs, bytes32[2][500] bytescs, uint[500] amounts, bool[500] Aboolbs, bool[500] Aboolcs){} + function return500PaymentsByDates(uint begindate, uint enddate, uint startindex) public view returns(uint[500] BIDs, uint[500] dates, uint[500] RIDs, bool[500] Bboolas, bytes32[3][500] bytesbs,bytes32[2][500] bytescs, uint[500] amounts, bool[500] Bboolbs){} } )"; compileAndRun(sourceCode, 0, "C"); @@ -10908,13 +11065,13 @@ BOOST_AUTO_TEST_CASE(bare_call_invalid_address) char const* sourceCode = R"( contract C { /// Calling into non-existant account is successful (creates the account) - function f() external view returns (bool) { + function f() external returns (bool) { return address(0x4242).call(); } - function g() external view returns (bool) { + function g() external returns (bool) { return address(0x4242).callcode(); } - function h() external view returns (bool) { + function h() external returns (bool) { return address(0x4242).delegatecall(); } } @@ -10936,13 +11093,13 @@ BOOST_AUTO_TEST_CASE(delegatecall_return_value) function get() external view returns (uint) { return value; } - function get_delegated() external view returns (bool) { + function get_delegated() external returns (bool) { return this.delegatecall(bytes4(sha3("get()"))); } function assert0() external view { assert(value == 0); } - function assert0_delegated() external view returns (bool) { + function assert0_delegated() external returns (bool) { return this.delegatecall(bytes4(sha3("assert0()"))); } } @@ -11403,7 +11560,7 @@ BOOST_AUTO_TEST_CASE(abi_encode_v2) require(y[0] == "e"); } S s; - function f4() public view returns (bytes r) { + function f4() public returns (bytes r) { string memory x = "abc"; s.a = 7; s.b.push(2); @@ -11881,6 +12038,26 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constants_constantinople) BOOST_CHECK(callContractFunction("shr_3()") == encodeArgs(u256(1))); } +BOOST_AUTO_TEST_CASE(senders_balance) +{ + char const* sourceCode = R"( + contract C { + function f() public view returns (uint) { + return msg.sender.balance; + } + } + contract D { + C c = new C(); + constructor() payable { } + function f() public view returns (uint) { + return c.f(); + } + } + )"; + compileAndRun(sourceCode, 27, "D"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(27))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 97cf0410..5af67659 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -831,24 +831,6 @@ BOOST_AUTO_TEST_CASE(illegal_override_visibility) CHECK_ERROR(text, TypeError, "Overriding function visibility differs"); } -BOOST_AUTO_TEST_CASE(illegal_override_remove_constness) -{ - char const* text = R"( - contract B { function f() constant {} } - contract C is B { function f() public {} } - )"; - CHECK_ERROR(text, TypeError, "Overriding function changes state mutability from \"view\" to \"nonpayable\"."); -} - -BOOST_AUTO_TEST_CASE(illegal_override_add_constness) -{ - char const* text = R"( - contract B { function f() public {} } - contract C is B { function f() constant {} } - )"; - CHECK_ERROR(text, TypeError, "Overriding function changes state mutability from \"nonpayable\" to \"view\"."); -} - BOOST_AUTO_TEST_CASE(complex_inheritance) { char const* text = R"( @@ -993,19 +975,6 @@ BOOST_AUTO_TEST_CASE(private_state_variable) BOOST_CHECK_MESSAGE(function == nullptr, "Accessor function of an internal variable should not exist"); } -BOOST_AUTO_TEST_CASE(missing_state_variable) -{ - char const* text = R"( - contract Scope { - function getStateVar() constant public returns (uint stateVar) { - stateVar = Scope.stateVar; // should fail. - } - } - )"; - CHECK_ERROR(text, TypeError, "Member \"stateVar\" not found or not visible after argument-dependent lookup in type(contract Scope)"); -} - - BOOST_AUTO_TEST_CASE(base_class_state_variable_accessor) { // test for issue #1126 https://github.com/ethereum/cpp-ethereum/issues/1126 @@ -1119,17 +1088,6 @@ BOOST_AUTO_TEST_CASE(fallback_function_with_return_parameters) CHECK_ERROR(text, TypeError, "Fallback function cannot return values."); } -BOOST_AUTO_TEST_CASE(fallback_function_with_constant_modifier) -{ - char const* text = R"( - contract C { - uint x; - function() constant { x = 2; } - } - )"; - CHECK_ERROR(text, TypeError, "Fallback function must be payable or non-payable"); -} - BOOST_AUTO_TEST_CASE(fallback_function_twice) { char const* text = R"( @@ -2278,25 +2236,6 @@ BOOST_AUTO_TEST_CASE(explicit_conversion_from_decimal_to_bytesxx) CHECK_SUCCESS_NO_WARNINGS(text); } -BOOST_AUTO_TEST_CASE(combining_hex_and_denomination) -{ - char const* text = R"( - contract Foo { - uint constant x = 0x01 wei; - } - )"; - CHECK_WARNING(text, "Hexadecimal numbers with unit denominations are deprecated."); - - char const* textV050 = R"( - pragma experimental "v0.5.0"; - - contract Foo { - uint constant x = 0x01 wei; - } - )"; - CHECK_ERROR(textV050, TypeError, "Hexadecimal numbers cannot be used with unit denominations."); -} - BOOST_AUTO_TEST_CASE(assigning_value_to_const_variable) { char const* text = R"( @@ -2346,30 +2285,6 @@ BOOST_AUTO_TEST_CASE(constant_string_literal_disallows_assignment) CHECK_ERROR(text, TypeError, "Index access for string is not possible."); } -BOOST_AUTO_TEST_CASE(assign_constant_function_value_to_constant_0_4_x) -{ - char const* text = R"( - contract C { - function () constant returns (uint) x; - uint constant y = x(); - } - )"; - CHECK_WARNING(text, "Initial value for constant variable has to be compile-time constant."); -} - -BOOST_AUTO_TEST_CASE(assign_constant_function_value_to_constant) -{ - char const* text = R"( - pragma experimental "v0.5.0"; - - contract C { - function () constant returns (uint) x; - uint constant y = x(); - } - )"; - CHECK_ERROR(text, TypeError, "Initial value for constant variable has to be compile-time constant."); -} - BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_conversion) { char const* text = R"( @@ -2913,7 +2828,7 @@ BOOST_AUTO_TEST_CASE(dynamic_return_types_not_possible) contract C { function f(uint) public returns (string); function g() public { - var (x,) = this.f(2); + var x = this.f(2); // we can assign to x but it is not usable. bytes(x).length; } @@ -5823,7 +5738,7 @@ BOOST_AUTO_TEST_CASE(bare_others) CHECK_WARNING("contract C { function f() pure public { assert; } }", "Statement has no effect."); // This is different because it does have overloads. CHECK_ERROR("contract C { function f() pure public { require; } }", TypeError, "No matching declaration found after variable lookup."); - CHECK_WARNING("contract C { function f() pure public { suicide; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() pure public { selfdestruct; } }", "Statement has no effect."); } BOOST_AUTO_TEST_CASE(pure_statement_in_for_loop) @@ -5852,80 +5767,6 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop) CHECK_SUCCESS(text); } -BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - (x, y) = (y, x); - } - } - )"; - CHECK_WARNING(text, "This assignment performs two copies to storage."); -} - -BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_right) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - (x, y, ) = (y, x, 1, 2); - } - } - )"; - CHECK_WARNING(text, "This assignment performs two copies to storage."); -} - -BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_left) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - (,x, y) = (1, 2, y, x); - } - } - )"; - CHECK_WARNING(text, "This assignment performs two copies to storage."); -} - -BOOST_AUTO_TEST_CASE(nowarn_swap_memory) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - function f() pure public { - S memory x; - S memory y; - (x, y) = (y, x); - } - } - )"; - CHECK_SUCCESS_NO_WARNINGS(text); -} - -BOOST_AUTO_TEST_CASE(nowarn_swap_storage_pointers) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - S storage x_local = x; - S storage y_local = y; - S storage z_local = x; - (x, y_local, x_local, z_local) = (y, x_local, y_local, y); - } - } - )"; - CHECK_SUCCESS_NO_WARNINGS(text); -} - BOOST_AUTO_TEST_CASE(warn_unused_local) { char const* text = R"( @@ -6958,31 +6799,6 @@ BOOST_AUTO_TEST_CASE(invalid_literal_in_tuple) CHECK_SUCCESS(text); } -BOOST_AUTO_TEST_CASE(warn_about_sha3) -{ - char const* text = R"( - contract test { - function f() pure public { - bytes32 x = sha3(uint8(1)); - x; - } - } - )"; - CHECK_WARNING(text, "\"sha3\" has been deprecated in favour of \"keccak256\""); -} - -BOOST_AUTO_TEST_CASE(warn_about_suicide) -{ - char const* text = R"( - contract test { - function f() public { - suicide(1); - } - } - )"; - CHECK_WARNING(text, "\"suicide\" has been deprecated in favour of \"selfdestruct\""); -} - BOOST_AUTO_TEST_CASE(address_overload_resolution) { char const* text = R"( diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 100b3662..cbea8694 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -112,61 +112,6 @@ while(0) BOOST_AUTO_TEST_SUITE(SolidityParser) -BOOST_AUTO_TEST_CASE(empty_function) -{ - char const* text = R"( - contract test { - uint256 stateVar; - function functionName(bytes20 arg1, address addr) constant - returns (int id) - { } - } - )"; - BOOST_CHECK(successParse(text)); -} - -BOOST_AUTO_TEST_CASE(no_function_params) -{ - char const* text = R"( - contract test { - uint256 stateVar; - function functionName() {} - } - )"; - BOOST_CHECK(successParse(text)); -} - -BOOST_AUTO_TEST_CASE(single_function_param) -{ - char const* text = R"( - contract test { - uint256 stateVar; - function functionName(bytes32 input) returns (bytes32 out) {} - } - )"; - BOOST_CHECK(successParse(text)); -} - -BOOST_AUTO_TEST_CASE(single_function_param_trailing_comma) -{ - char const* text = R"( - contract test { - function(uint a,) {} - } - )"; - CHECK_PARSE_ERROR(text, "Unexpected trailing comma in parameter list."); -} - -BOOST_AUTO_TEST_CASE(single_return_param_trailing_comma) -{ - char const* text = R"( - contract test { - function() returns (uint a,) {} - } - )"; - CHECK_PARSE_ERROR(text, "Unexpected trailing comma in parameter list."); -} - BOOST_AUTO_TEST_CASE(single_modifier_arg_trailing_comma) { char const* text = R"( @@ -241,39 +186,6 @@ BOOST_AUTO_TEST_CASE(function_no_body) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(missing_parameter_name_in_named_args) -{ - char const* text = R"( - contract test { - function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; } - function b() returns (uint r) { r = a({: 1, : 2, : 3}); } - } - )"; - CHECK_PARSE_ERROR(text, "Expected identifier"); -} - -BOOST_AUTO_TEST_CASE(missing_argument_in_named_args) -{ - char const* text = R"( - contract test { - function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; } - function b() returns (uint r) { r = a({a: , b: , c: }); } - } - )"; - CHECK_PARSE_ERROR(text, "Expected primary expression"); -} - -BOOST_AUTO_TEST_CASE(trailing_comma_in_named_args) -{ - char const* text = R"( - contract test { - function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; } - function b() returns (uint r) { r = a({a: 1, b: 2, c: 3, }); } - } - )"; - CHECK_PARSE_ERROR(text, "Unexpected trailing comma"); -} - BOOST_AUTO_TEST_CASE(two_exact_functions) { char const* text = R"( @@ -557,18 +469,6 @@ BOOST_AUTO_TEST_CASE(variable_definition_with_initialization) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(variable_definition_in_mapping) -{ - char const* text = R"( - contract test { - function fun() { - mapping(var=>bytes32) d; - } - } - )"; - CHECK_PARSE_ERROR(text, "Expected elementary type name for mapping key type"); -} - BOOST_AUTO_TEST_CASE(operator_expression) { char const* text = R"( @@ -849,16 +749,6 @@ BOOST_AUTO_TEST_CASE(modifier) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(modifier_without_semicolon) -{ - char const* text = R"( - contract c { - modifier mod { if (msg.sender == 0) _ } - } - )"; - CHECK_PARSE_ERROR(text, "Expected token Semicolon got"); -} - BOOST_AUTO_TEST_CASE(modifier_arguments) { char const* text = R"( @@ -918,16 +808,6 @@ BOOST_AUTO_TEST_CASE(event_arguments_indexed) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(event_with_no_argument_list_fails) -{ - char const* text = R"( - contract c { - event e; - } - )"; - CHECK_PARSE_ERROR(text, "Expected token LParen got 'Semicolon'"); -} - BOOST_AUTO_TEST_CASE(visibility_specifiers) { char const* text = R"( @@ -958,40 +838,6 @@ BOOST_AUTO_TEST_CASE(multiple_visibility_specifiers) CHECK_PARSE_ERROR(text, "Visibility already specified as \"private\"."); } -BOOST_AUTO_TEST_CASE(multiple_statemutability_specifiers) -{ - char const* text = R"( - contract c { - function f() payable payable {} - })"; - CHECK_PARSE_ERROR(text, "State mutability already specified as \"payable\"."); - text = R"( - contract c { - function f() constant constant {} - })"; - CHECK_PARSE_ERROR(text, "State mutability already specified as \"view\"."); - text = R"( - contract c { - function f() constant view {} - })"; - CHECK_PARSE_ERROR(text, "State mutability already specified as \"view\"."); - text = R"( - contract c { - function f() payable constant {} - })"; - CHECK_PARSE_ERROR(text, "State mutability already specified as \"payable\"."); - text = R"( - contract c { - function f() pure payable {} - })"; - CHECK_PARSE_ERROR(text, "State mutability already specified as \"pure\"."); - text = R"( - contract c { - function f() pure constant {} - })"; - CHECK_PARSE_ERROR(text, "State mutability already specified as \"pure\"."); -} - BOOST_AUTO_TEST_CASE(literal_constants_with_ether_subdenominations) { char const* text = R"( @@ -1038,24 +884,6 @@ BOOST_AUTO_TEST_CASE(enum_valid_declaration) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(empty_enum_declaration) -{ - char const* text = R"( - contract c { - enum foo { } - })"; - CHECK_PARSE_ERROR(text, "enum with no members is not allowed"); -} - -BOOST_AUTO_TEST_CASE(malformed_enum_declaration) -{ - char const* text = R"( - contract c { - enum foo { WARNING,} - })"; - CHECK_PARSE_ERROR(text, "Expected Identifier after"); -} - BOOST_AUTO_TEST_CASE(external_function) { char const* text = R"( @@ -1065,15 +893,6 @@ BOOST_AUTO_TEST_CASE(external_function) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(external_variable) -{ - char const* text = R"( - contract c { - uint external x; - })"; - CHECK_PARSE_ERROR(text, "Expected identifier"); -} - BOOST_AUTO_TEST_CASE(arrays_in_storage) { char const* text = R"( @@ -1113,15 +932,6 @@ BOOST_AUTO_TEST_CASE(multi_arrays) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(constant_is_keyword) -{ - char const* text = R"( - contract Foo { - uint constant = 4; - })"; - CHECK_PARSE_ERROR(text, "Expected identifier"); -} - BOOST_AUTO_TEST_CASE(keyword_is_reserved) { auto keywords = { @@ -1148,19 +958,10 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved) for (const auto& keyword: keywords) { auto text = std::string("contract ") + keyword + " {}"; - CHECK_PARSE_ERROR(text.c_str(), "Expected identifier"); + CHECK_PARSE_ERROR(text.c_str(), "Expected identifier but got reserved keyword"); } } -BOOST_AUTO_TEST_CASE(var_array) -{ - char const* text = R"( - contract Foo { - function f() { var[] a; } - })"; - CHECK_PARSE_ERROR(text, "Expected identifier"); -} - BOOST_AUTO_TEST_CASE(location_specifiers_for_params) { char const* text = R"( @@ -1184,24 +985,6 @@ BOOST_AUTO_TEST_CASE(location_specifiers_for_locals) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(location_specifiers_for_state) -{ - char const* text = R"( - contract Foo { - uint[] memory x; - })"; - CHECK_PARSE_ERROR(text, "Expected identifier"); -} - -BOOST_AUTO_TEST_CASE(location_specifiers_with_var) -{ - char const* text = R"( - contract Foo { - function f() { var memory x; } - })"; - CHECK_PARSE_ERROR(text, "Location specifier needs explicit type name"); -} - BOOST_AUTO_TEST_CASE(empty_comment) { char const* text = R"( @@ -1224,7 +1007,6 @@ BOOST_AUTO_TEST_CASE(comment_end_with_double_star) BOOST_CHECK(successParse(text)); } - BOOST_AUTO_TEST_CASE(library_simple) { char const* text = R"( @@ -1235,20 +1017,6 @@ BOOST_AUTO_TEST_CASE(library_simple) BOOST_CHECK(successParse(text)); } - -BOOST_AUTO_TEST_CASE(local_const_variable) -{ - char const* text = R"( - contract Foo { - function localConst() returns (uint ret) - { - uint constant local = 4; - return local; - } - })"; - CHECK_PARSE_ERROR(text, "Expected token Semicolon"); -} - BOOST_AUTO_TEST_CASE(multi_variable_declaration) { char const* text = R"( @@ -1285,18 +1053,6 @@ BOOST_AUTO_TEST_CASE(tuples) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(tuples_without_commas) -{ - char const* text = R"( - contract C { - function f() { - var a = (2 2); - } - } - )"; - CHECK_PARSE_ERROR(text, "Expected token Comma"); -} - BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity) { char const* text = R"( @@ -1365,34 +1121,6 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration) BOOST_CHECK(successParse(text)); } - -BOOST_AUTO_TEST_CASE(inline_array_empty_cells_check_lvalue) -{ - char const* text = R"( - contract c { - uint[] a; - function f() returns (uint) { - a = [,2,3]; - return (a[0]); - } - } - )"; - CHECK_PARSE_ERROR(text, "Expected expression"); -} - -BOOST_AUTO_TEST_CASE(inline_array_empty_cells_check_without_lvalue) -{ - char const* text = R"( - contract c { - uint[] a; - function f() returns (uint, uint) { - return ([3, ,4][0]); - } - } - )"; - CHECK_PARSE_ERROR(text, "Expected expression"); -} - BOOST_AUTO_TEST_CASE(conditional_true_false_literal) { char const* text = R"( @@ -1520,38 +1248,6 @@ BOOST_AUTO_TEST_CASE(declaring_fixed_literal_variables) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(no_double_radix_in_fixed_literal) -{ - char const* text = R"( - contract A { - fixed40x40 pi = 3.14.15; - } - )"; - CHECK_PARSE_ERROR(text, "Expected token Semicolon"); -} - -BOOST_AUTO_TEST_CASE(invalid_fixed_conversion_leading_zeroes_check) -{ - char const* text = R"( - contract test { - function f() { - fixed a = 1.0x2; - } - } - )"; - CHECK_PARSE_ERROR(text, "Expected primary expression"); -} - -BOOST_AUTO_TEST_CASE(payable_accessor) -{ - char const* text = R"( - contract test { - uint payable x; - } - )"; - CHECK_PARSE_ERROR(text, "Expected identifier"); -} - BOOST_AUTO_TEST_CASE(function_type_in_expression) { char const* text = R"( @@ -1575,16 +1271,6 @@ BOOST_AUTO_TEST_CASE(function_type_as_storage_variable) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_modifiers) -{ - char const* text = R"( - contract test { - function (uint, uint) modifier1() returns (uint) f1; - } - )"; - CHECK_PARSE_ERROR(text, "Expected token LBrace"); -} - BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_assignment) { char const* text = R"( @@ -1660,20 +1346,6 @@ BOOST_AUTO_TEST_CASE(function_type_state_variable) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(scientific_notation) -{ - char const* text = R"( - contract test { - uint256 a = 2e10; - uint256 b = 2E10; - uint256 c = 200e-2; - uint256 d = 2E10 wei; - uint256 e = 2.5e10; - } - )"; - BOOST_CHECK(successParse(text)); -} - BOOST_AUTO_TEST_CASE(interface) { char const* text = R"( @@ -1684,31 +1356,6 @@ BOOST_AUTO_TEST_CASE(interface) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(newInvalidTypeName) -{ - char const* text = R"( - contract C { - function f() { - new var; - } - } - )"; - CHECK_PARSE_ERROR(text, "Expected explicit type name"); -} - -BOOST_AUTO_TEST_CASE(emitWithoutEvent) -{ - char const* text = R"( - contract C { - event A(); - function f() { - emit A; - } - } - )"; - CHECK_PARSE_ERROR(text, "Expected token LParen got 'Semicolon'"); -} - BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 74bf01b2..63c03881 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -326,9 +326,9 @@ BOOST_AUTO_TEST_CASE(compilation_error) { BOOST_CHECK_EQUAL( dev::jsonCompactPrint(error), - "{\"component\":\"general\",\"formattedMessage\":\"fileA:1:23: ParserError: Expected identifier, got 'RBrace'\\n" - "contract A { function }\\n ^\\n\",\"message\":\"Expected identifier, got 'RBrace'\"," - "\"severity\":\"error\",\"sourceLocation\":{\"end\":22,\"file\":\"fileA\",\"start\":22},\"type\":\"ParserError\"}" + "{\"component\":\"general\",\"formattedMessage\":\"fileA:1:23: ParserError: Expected identifier but got '}'\\n" + "contract A { function }\\n ^\\n\",\"message\":\"Expected identifier but got '}'\"," + "\"severity\":\"error\",\"sourceLocation\":{\"end\":23,\"file\":\"fileA\",\"start\":22},\"type\":\"ParserError\"}" ); } } diff --git a/test/libsolidity/syntaxTests/constants/assign_constant_function_value.sol b/test/libsolidity/syntaxTests/constants/assign_constant_function_value.sol new file mode 100644 index 00000000..88e94e29 --- /dev/null +++ b/test/libsolidity/syntaxTests/constants/assign_constant_function_value.sol @@ -0,0 +1,6 @@ +contract C { + function () pure returns (uint) x; + uint constant y = x(); +} +// ---- +// Warning: (74-77): Initial value for constant variable has to be compile-time constant. This will fail to compile with the next breaking version change. diff --git a/test/libsolidity/syntaxTests/constants/assign_constant_function_value_050.sol b/test/libsolidity/syntaxTests/constants/assign_constant_function_value_050.sol new file mode 100644 index 00000000..2c92899d --- /dev/null +++ b/test/libsolidity/syntaxTests/constants/assign_constant_function_value_050.sol @@ -0,0 +1,8 @@ +pragma experimental "v0.5.0"; + +contract C { + function () pure returns (uint) x; + uint constant y = x(); +} +// ---- +// TypeError: (105-108): Initial value for constant variable has to be compile-time constant. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_fine.sol new file mode 100644 index 00000000..65902cc8 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_fine.sol @@ -0,0 +1,26 @@ +contract C { + struct S { bool f; } + S s; + function f() internal returns (S storage c) { + assembly { + sstore(c_slot, sload(s_slot)) + } + } + function g(bool flag) internal returns (S storage c) { + // control flow in assembly will not be analyzed for now, + // so this will not issue a warning + assembly { + if flag { + sstore(c_slot, sload(s_slot)) + } + } + } + function h() internal returns (S storage c) { + // any reference from assembly will be sufficient for now, + // so this will not issue a warning + assembly { + sstore(s_slot, sload(c_slot)) + } + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_warn.sol new file mode 100644 index 00000000..09c13847 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_warn.sol @@ -0,0 +1,10 @@ +contract C { + struct S { bool f; } + S s; + function f() internal pure returns (S storage) { + assembly { + } + } +} +// ---- +// Warning: (87-88): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/default_location.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/default_location.sol new file mode 100644 index 00000000..9a42192d --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/default_location.sol @@ -0,0 +1,19 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S c) { + c = s; + } + function g() internal view returns (S) { + return s; + } + function h() internal pure returns (S) { + } + function i(bool flag) internal view returns (S c) { + if (flag) c = s; + } + function j(bool flag) internal view returns (S) { + if (flag) return s; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol new file mode 100644 index 00000000..6520672c --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol @@ -0,0 +1,36 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + do {} while((c = s).f); + } + function g() internal view returns (S storage c) { + do { c = s; } while(false); + } + function h() internal view returns (S storage c) { + c = s; + do {} while(false); + } + function i() internal view returns (S storage c) { + do {} while(false); + c = s; + } + function j() internal view returns (S storage c) { + do { + c = s; + break; + } while(false); + } + function k() internal view returns (S storage c) { + do { + if (s.f) { + continue; + break; + } + else { + c = s; + } + } while(false); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_warn.sol new file mode 100644 index 00000000..f1a92e9c --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_warn.sol @@ -0,0 +1,35 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + do { + break; + c = s; + } while(false); + } + function g() internal view returns (S storage c) { + do { + if (s.f) { + continue; + c = s; + } + else { + } + } while(false); + } + function h() internal view returns (S storage c) { + do { + if (s.f) { + break; + continue; + } + else { + c = s; + } + } while(false); + } +} +// ---- +// Warning: (87-98): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (223-234): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (440-451): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/emptyReturn_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/emptyReturn_fine.sol new file mode 100644 index 00000000..3a0a30ea --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/emptyReturn_fine.sol @@ -0,0 +1,6 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c, S storage d) { c = s; d = s; return; } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/emptyReturn_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/emptyReturn_warn.sol new file mode 100644 index 00000000..0a5b2fbf --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/emptyReturn_warn.sol @@ -0,0 +1,15 @@ +contract C { + struct S { bool f; } + S s; + function f() internal pure returns (S storage) { return; } + function g() internal view returns (S storage c, S storage) { c = s; return; } + function h() internal view returns (S storage, S storage d) { d = s; return; } + function i() internal pure returns (S storage, S storage) { return; } + function j() internal view returns (S storage, S storage) { return (s,s); } +} +// ---- +// Warning: (87-88): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (163-164): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (233-234): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (316-317): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (327-328): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_fine.sol new file mode 100644 index 00000000..aa82cb9a --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_fine.sol @@ -0,0 +1,13 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + for(c = s;;) { + } + } + function g() internal view returns (S storage c) { + for(; (c = s).f;) { + } + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_warn.sol new file mode 100644 index 00000000..ba9a2440 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_warn.sol @@ -0,0 +1,16 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + for(;; c = s) { + } + } + function g() internal view returns (S storage c) { + for(;;) { + c = s; + } + } +} +// ---- +// Warning: (87-98): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (182-193): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_fine.sol new file mode 100644 index 00000000..b809e95d --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_fine.sol @@ -0,0 +1,29 @@ +contract C { + struct S { bool f; } + S s; + function f(bool flag) internal view returns (S storage c) { + if (flag) c = s; + else c = s; + } + function g(bool flag) internal view returns (S storage c) { + if (flag) c = s; + else { c = s; } + } + function h(bool flag) internal view returns (S storage c) { + if (flag) c = s; + else + { + if (!flag) c = s; + else c = s; + } + } + function i() internal view returns (S storage c) { + if ((c = s).f) { + } + } + function j() internal view returns (S storage c) { + if ((c = s).f && !(c = s).f) { + } + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_warn.sol new file mode 100644 index 00000000..c257c252 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_warn.sol @@ -0,0 +1,18 @@ +contract C { + struct S { bool f; } + S s; + function f(bool flag) internal view returns (S storage c) { + if (flag) c = s; + } + function g(bool flag) internal returns (S storage c) { + if (flag) c = s; + else + { + if (!flag) c = s; + else s.f = true; + } + } +} +// ---- +// Warning: (96-107): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (186-197): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_fine.sol new file mode 100644 index 00000000..ee37f6d6 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_fine.sol @@ -0,0 +1,20 @@ +contract C { + modifier revertIfNoReturn() { + _; + revert(); + } + modifier ifFlag(bool flag) { + if (flag) + _; + } + struct S { uint a; } + S s; + function f(bool flag) revertIfNoReturn() internal view returns(S storage) { + if (flag) return s; + } + function g(bool flag) revertIfNoReturn() ifFlag(flag) internal view returns(S storage) { + return s; + } + +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_warn.sol new file mode 100644 index 00000000..50c6dd99 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_warn.sol @@ -0,0 +1,22 @@ +contract C { + modifier revertIfNoReturn() { + _; + revert(); + } + modifier ifFlag(bool flag) { + if (flag) + _; + } + struct S { uint a; } + S s; + function f(bool flag) ifFlag(flag) internal view returns(S storage) { + return s; + } + + function g(bool flag) ifFlag(flag) revertIfNoReturn() internal view returns(S storage) { + return s; + } +} +// ---- +// Warning: (249-250): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (367-368): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/revert_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/revert_fine.sol new file mode 100644 index 00000000..022f2d1c --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/revert_fine.sol @@ -0,0 +1,12 @@ +contract C { + struct S { bool f; } + S s; + function f() internal pure returns (S storage) { + revert(); + } + function g(bool flag) internal view returns (S storage c) { + if (flag) c = s; + else revert(); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_fine.sol new file mode 100644 index 00000000..699849c0 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_fine.sol @@ -0,0 +1,11 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + (c = s).f && false; + } + function g() internal view returns (S storage c) { + (c = s).f || true; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_warn.sol new file mode 100644 index 00000000..9f660f11 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_warn.sol @@ -0,0 +1,18 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + false && (c = s).f; + } + function g() internal view returns (S storage c) { + true || (c = s).f; + } + function h() internal view returns (S storage c) { + // expect warning, although this is always fine + true && (false || (c = s).f); + } +} +// ---- +// Warning: (87-98): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (176-187): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (264-275): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/smoke.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/smoke.sol new file mode 100644 index 00000000..beeadbe4 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/smoke.sol @@ -0,0 +1,10 @@ +contract C { + struct S { bool f; } + S s; + function f() internal pure {} + function g() internal view returns (S storage) { return s; } + function h() internal view returns (S storage c) { return s; } + function i() internal view returns (S storage c) { c = s; } + function j() internal view returns (S storage c) { (c) = s; } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_fine.sol new file mode 100644 index 00000000..ee3869bd --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_fine.sol @@ -0,0 +1,14 @@ +contract C { + struct S { bool f; } + S s; + function f(bool flag) internal view returns (S storage c) { + flag ? c = s : c = s; + } + function g(bool flag) internal view returns (S storage c) { + flag ? c = s : (c = s); + } + function h(bool flag) internal view returns (S storage c) { + flag ? (c = s).f : (c = s).f; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_warn.sol new file mode 100644 index 00000000..57561fbb --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_warn.sol @@ -0,0 +1,13 @@ +contract C { + struct S { bool f; } + S s; + function f(bool flag) internal view returns (S storage c) { + flag ? (c = s).f : false; + } + function g(bool flag) internal view returns (S storage c) { + flag ? false : (c = s).f; + } +} +// ---- +// Warning: (96-107): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. +// Warning: (200-211): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/throw_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/throw_fine.sol new file mode 100644 index 00000000..4cecc27c --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/throw_fine.sol @@ -0,0 +1,9 @@ +contract C { + struct S { bool f; } + S s; + function f() internal pure returns (S storage) { + throw; + } +} +// ---- +// Warning: (108-113): "throw" is deprecated in favour of "revert()", "require()" and "assert()". diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/tuple_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/tuple_fine.sol new file mode 100644 index 00000000..0b171560 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/tuple_fine.sol @@ -0,0 +1,12 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage, uint) { + return (s,2); + } + function g() internal view returns (S storage c) { + uint a; + (c, a) = f(); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_fine.sol new file mode 100644 index 00000000..71543422 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_fine.sol @@ -0,0 +1,19 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + while((c = s).f) { + } + } + function g() internal view returns (S storage c) { + c = s; + while(false) { + } + } + function h() internal view returns (S storage c) { + while(false) { + } + c = s; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_warn.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_warn.sol new file mode 100644 index 00000000..26db892f --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_warn.sol @@ -0,0 +1,11 @@ +contract C { + struct S { bool f; } + S s; + function f() internal view returns (S storage c) { + while(false) { + c = s; + } + } +} +// ---- +// Warning: (87-98): This variable is of storage pointer type and might be returned without assignment. This can cause storage corruption. Assign the variable (potentially from itself) to remove this warning. diff --git a/test/libsolidity/syntaxTests/denominations/combining_hex_and_denomination.sol b/test/libsolidity/syntaxTests/denominations/combining_hex_and_denomination.sol new file mode 100644 index 00000000..3571e8a9 --- /dev/null +++ b/test/libsolidity/syntaxTests/denominations/combining_hex_and_denomination.sol @@ -0,0 +1,5 @@ +contract C { + uint constant x = 0x01 wei; +} +// ---- +// Warning: (32-40): Hexadecimal numbers with unit denominations are deprecated. You can use an expression of the form "0x1234 * 1 day" instead. diff --git a/test/libsolidity/syntaxTests/denominations/combining_hex_and_denomination_050.sol b/test/libsolidity/syntaxTests/denominations/combining_hex_and_denomination_050.sol new file mode 100644 index 00000000..98865999 --- /dev/null +++ b/test/libsolidity/syntaxTests/denominations/combining_hex_and_denomination_050.sol @@ -0,0 +1,6 @@ +pragma experimental "v0.5.0"; +contract C { + uint constant x = 0x01 wei; +} +// ---- +// TypeError: (62-70): Hexadecimal numbers cannot be used with unit denominations. You can use an expression of the form "0x1234 * 1 day" instead. diff --git a/test/libsolidity/syntaxTests/denominations/denominations.sol b/test/libsolidity/syntaxTests/denominations/denominations.sol new file mode 100644 index 00000000..6d1aa2f3 --- /dev/null +++ b/test/libsolidity/syntaxTests/denominations/denominations.sol @@ -0,0 +1,7 @@ +contract C { + uint constant a = 1 wei + 2 szabo + 3 finney + 4 ether; + uint constant b = 1 seconds + 2 minutes + 3 hours + 4 days + 5 weeks + 6 years; + uint constant c = 2 szabo / 1 seconds + 3 finney * 3 hours; +} +// ---- +// Warning: (142-149): Using "years" as a unit denomination is deprecated. diff --git a/test/libsolidity/syntaxTests/denominations/deprecated_year.sol b/test/libsolidity/syntaxTests/denominations/deprecated_year.sol new file mode 100644 index 00000000..30e86535 --- /dev/null +++ b/test/libsolidity/syntaxTests/denominations/deprecated_year.sol @@ -0,0 +1,5 @@ +contract C { + uint constant a = 3 years; +} +// ---- +// Warning: (32-39): Using "years" as a unit denomination is deprecated. diff --git a/test/libsolidity/syntaxTests/denominations/deprecated_year_050.sol b/test/libsolidity/syntaxTests/denominations/deprecated_year_050.sol new file mode 100644 index 00000000..4baaeaa3 --- /dev/null +++ b/test/libsolidity/syntaxTests/denominations/deprecated_year_050.sol @@ -0,0 +1,6 @@ +pragma experimental "v0.5.0"; +contract C { + uint constant a = 3 years; +} +// ---- +// TypeError: (62-69): Using "years" as a unit denomination is deprecated. diff --git a/test/libsolidity/syntaxTests/denominations/fixed_point_division.sol b/test/libsolidity/syntaxTests/denominations/fixed_point_division.sol new file mode 100644 index 00000000..22331b51 --- /dev/null +++ b/test/libsolidity/syntaxTests/denominations/fixed_point_division.sol @@ -0,0 +1,6 @@ +contract C { + uint constant a = 4 ether / 3 hours; + ufixed constant b = ufixed(4 ether / 3 hours); +} +// ---- +// TypeError: (32-49): Type rational_const 10000000000000000 / 27 is not implicitly convertible to expected type uint256. Try converting to type ufixed256x62 or use an explicit conversion. diff --git a/test/libsolidity/syntaxTests/deprecated_functions.sol b/test/libsolidity/syntaxTests/deprecated_functions.sol new file mode 100644 index 00000000..57ee484a --- /dev/null +++ b/test/libsolidity/syntaxTests/deprecated_functions.sol @@ -0,0 +1,12 @@ +contract test { + function f() pure public { + bytes32 x = sha3(uint8(1)); + x; + } + function g() public { + suicide(1); + } +} +// ---- +// Warning: (58-72): "sha3" has been deprecated in favour of "keccak256" +// Warning: (107-117): "suicide" has been deprecated in favour of "selfdestruct" diff --git a/test/libsolidity/syntaxTests/deprecated_functions_050.sol b/test/libsolidity/syntaxTests/deprecated_functions_050.sol new file mode 100644 index 00000000..7e36543b --- /dev/null +++ b/test/libsolidity/syntaxTests/deprecated_functions_050.sol @@ -0,0 +1,13 @@ +pragma experimental "v0.5.0"; +contract test { + function f() pure public { + bytes32 x = sha3(uint8(1)); + x; + } + function g() public { + suicide(1); + } +} +// ---- +// TypeError: (88-102): "sha3" has been deprecated in favour of "keccak256" +// TypeError: (137-147): "suicide" has been deprecated in favour of "selfdestruct" diff --git a/test/libsolidity/syntaxTests/emit_non_event.sol b/test/libsolidity/syntaxTests/emit_non_event.sol new file mode 100644 index 00000000..1df6990d --- /dev/null +++ b/test/libsolidity/syntaxTests/emit_non_event.sol @@ -0,0 +1,10 @@ +contract C { + uint256 Test; + + function f() { + emit Test(); + } +} +// ---- +// TypeError: (56-62): Type is not callable +// TypeError: (56-60): Expression has to be an event invocation. diff --git a/test/libsolidity/syntaxTests/empty_string_var.sol b/test/libsolidity/syntaxTests/empty_string_var.sol new file mode 100644 index 00000000..e9837590 --- /dev/null +++ b/test/libsolidity/syntaxTests/empty_string_var.sol @@ -0,0 +1,11 @@ +contract C { + function f() { + var a = ""; + bytes1 b = bytes1(a); + bytes memory c = bytes(a); + string memory d = string(a); + } +} +// ---- +// Warning: (34-39): Use of the "var" keyword is deprecated. +// TypeError: (61-70): Explicit type conversion not allowed from "string memory" to "bytes1". diff --git a/test/libsolidity/syntaxTests/fallback/pure_modifier.sol b/test/libsolidity/syntaxTests/fallback/pure_modifier.sol new file mode 100644 index 00000000..20d5b0ac --- /dev/null +++ b/test/libsolidity/syntaxTests/fallback/pure_modifier.sol @@ -0,0 +1,6 @@ +contract C { + uint x; + function() pure { x = 2; } +} +// ---- +// TypeError: (29-55): Fallback function must be payable or non-payable, but is "pure". diff --git a/test/libsolidity/syntaxTests/fallback/view_modifier.sol b/test/libsolidity/syntaxTests/fallback/view_modifier.sol new file mode 100644 index 00000000..44c5d204 --- /dev/null +++ b/test/libsolidity/syntaxTests/fallback/view_modifier.sol @@ -0,0 +1,6 @@ +contract C { + uint x; + function() view { x = 2; } +} +// ---- +// TypeError: (29-55): Fallback function must be payable or non-payable, but is "view". diff --git a/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol b/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol index b7763d28..b89a3bb4 100644 --- a/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol +++ b/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// ParserError: (118-118): Expected token Semicolon got 'Identifier' +// ParserError: (118-119): Expected ';' but got identifier diff --git a/test/libsolidity/syntaxTests/inheritance/override/add_view.sol b/test/libsolidity/syntaxTests/inheritance/override/add_view.sol new file mode 100644 index 00000000..9973b23e --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/add_view.sol @@ -0,0 +1,4 @@ +contract B { function f() public {} } +contract C is B { function f() view {} } +// ---- +// TypeError: (56-76): Overriding function changes state mutability from "nonpayable" to "view". diff --git a/test/libsolidity/syntaxTests/inheritance/override/remove_view.sol b/test/libsolidity/syntaxTests/inheritance/override/remove_view.sol new file mode 100644 index 00000000..e58f6b20 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/remove_view.sol @@ -0,0 +1,4 @@ +contract B { function f() view {} } +contract C is B { function f() public {} } +// ---- +// TypeError: (54-76): Overriding function changes state mutability from "view" to "nonpayable". diff --git a/test/libsolidity/syntaxTests/missing_state_variable.sol b/test/libsolidity/syntaxTests/missing_state_variable.sol new file mode 100644 index 00000000..02082a45 --- /dev/null +++ b/test/libsolidity/syntaxTests/missing_state_variable.sol @@ -0,0 +1,7 @@ +contract Scope { + function getStateVar() view public returns (uint stateVar) { + stateVar = Scope.stateVar; // should fail. + } +} +// ---- +// TypeError: (101-115): Member "stateVar" not found or not visible after argument-dependent lookup in type(contract Scope) diff --git a/test/libsolidity/syntaxTests/docstring_empty_description.sol b/test/libsolidity/syntaxTests/natspec/docstring_empty_description.sol index 0caa1b23..0caa1b23 100644 --- a/test/libsolidity/syntaxTests/docstring_empty_description.sol +++ b/test/libsolidity/syntaxTests/natspec/docstring_empty_description.sol diff --git a/test/libsolidity/syntaxTests/natspec/docstring_empty_tag.sol b/test/libsolidity/syntaxTests/natspec/docstring_empty_tag.sol new file mode 100644 index 00000000..9a28143a --- /dev/null +++ b/test/libsolidity/syntaxTests/natspec/docstring_empty_tag.sol @@ -0,0 +1,6 @@ +contract C { + /// @param + function vote(uint id) public; +} +// ---- +// DocstringParsingError: End of tag @param not found diff --git a/test/libsolidity/syntaxTests/parsing/constant_is_keyword.sol b/test/libsolidity/syntaxTests/parsing/constant_is_keyword.sol new file mode 100644 index 00000000..26d126ce --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/constant_is_keyword.sol @@ -0,0 +1,5 @@ +contract Foo { + uint constant = 4; +} +// ---- +// ParserError: (30-31): Expected identifier but got '=' diff --git a/test/libsolidity/syntaxTests/parsing/constant_state_modifier.sol b/test/libsolidity/syntaxTests/parsing/constant_state_modifier.sol new file mode 100644 index 00000000..da068351 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/constant_state_modifier.sol @@ -0,0 +1,7 @@ +contract C { + uint s; + // this test should fail starting from 0.5.0 + function f() public constant returns (uint) { + return s; + } +} diff --git a/test/libsolidity/syntaxTests/parsing/emit_without_event.sol b/test/libsolidity/syntaxTests/parsing/emit_without_event.sol new file mode 100644 index 00000000..b838b4af --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/emit_without_event.sol @@ -0,0 +1,8 @@ +contract C { + event A(); + function f() { + emit A; + } +} +// ---- +// ParserError: (49-50): Expected '(' but got ';' diff --git a/test/libsolidity/syntaxTests/parsing/empty_enum.sol b/test/libsolidity/syntaxTests/parsing/empty_enum.sol new file mode 100644 index 00000000..483f6a91 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/empty_enum.sol @@ -0,0 +1,5 @@ +contract c { + enum foo { } +} +// ---- +// ParserError: (25-26): enum with no members is not allowed. diff --git a/test/libsolidity/syntaxTests/parsing/empty_function.sol b/test/libsolidity/syntaxTests/parsing/empty_function.sol new file mode 100644 index 00000000..218fd9a7 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/empty_function.sol @@ -0,0 +1,10 @@ +contract test { + uint256 stateVar; + function functionName(bytes20 arg1, address addr) view returns (int id) { } +} +// ---- +// Warning: (36-111): No visibility specified. Defaulting to "public". +// Warning: (58-70): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (72-84): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (100-106): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (36-111): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/event_with_no_argument_list.sol b/test/libsolidity/syntaxTests/parsing/event_with_no_argument_list.sol new file mode 100644 index 00000000..b38c7dc9 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/event_with_no_argument_list.sol @@ -0,0 +1,5 @@ +contract c { + event e; +} +// ---- +// ParserError: (21-22): Expected '(' but got ';' diff --git a/test/libsolidity/syntaxTests/parsing/external_variable.sol b/test/libsolidity/syntaxTests/parsing/external_variable.sol new file mode 100644 index 00000000..2a02d19a --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/external_variable.sol @@ -0,0 +1,5 @@ +contract c { + uint external x; +} +// ---- +// ParserError: (19-27): Expected identifier but got 'external' diff --git a/test/libsolidity/syntaxTests/parsing/fixed_literal_with_double_radix.sol b/test/libsolidity/syntaxTests/parsing/fixed_literal_with_double_radix.sol new file mode 100644 index 00000000..0d5734aa --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/fixed_literal_with_double_radix.sol @@ -0,0 +1,5 @@ +contract A { + fixed40x40 pi = 3.14.15; +} +// ---- +// ParserError: (34-37): Expected ';' but got 'Number' diff --git a/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_modifiers.sol b/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_modifiers.sol new file mode 100644 index 00000000..6c22f4b7 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_modifiers.sol @@ -0,0 +1,5 @@ +contract test { + function (uint, uint) modifier1() returns (uint) f1; +} +// ---- +// ParserError: (66-68): Expected '{' but got identifier diff --git a/test/libsolidity/syntaxTests/parsing/inline_array_empty_cells_check_lvalue.sol b/test/libsolidity/syntaxTests/parsing/inline_array_empty_cells_check_lvalue.sol new file mode 100644 index 00000000..9460751c --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/inline_array_empty_cells_check_lvalue.sol @@ -0,0 +1,9 @@ +contract c { + uint[] a; + function f() returns (uint) { + a = [,2,3]; + return (a[0]); + } +} +// ---- +// ParserError: (62-63): Expected expression (inline array elements cannot be omitted). diff --git a/test/libsolidity/syntaxTests/parsing/inline_array_empty_cells_check_without_lvalue.sol b/test/libsolidity/syntaxTests/parsing/inline_array_empty_cells_check_without_lvalue.sol new file mode 100644 index 00000000..5b78232d --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/inline_array_empty_cells_check_without_lvalue.sol @@ -0,0 +1,8 @@ +contract c { + uint[] a; + function f() returns (uint, uint) { + return ([3, ,4][0]); + } +} +// ---- +// ParserError: (75-76): Expected expression (inline array elements cannot be omitted). diff --git a/test/libsolidity/syntaxTests/parsing/invalid_fixed_conversion_leading_zeroes_check.sol b/test/libsolidity/syntaxTests/parsing/invalid_fixed_conversion_leading_zeroes_check.sol new file mode 100644 index 00000000..fb267ba3 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/invalid_fixed_conversion_leading_zeroes_check.sol @@ -0,0 +1,7 @@ +contract test { + function f() { + fixed a = 1.0x2; + } +} +// ---- +// ParserError: (44-47): Expected primary expression. diff --git a/test/libsolidity/syntaxTests/parsing/local_const_variable.sol b/test/libsolidity/syntaxTests/parsing/local_const_variable.sol new file mode 100644 index 00000000..505fe0b5 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/local_const_variable.sol @@ -0,0 +1,9 @@ +contract Foo { + function localConst() returns (uint ret) + { + uint constant local = 4; + return local; + } +} +// ---- +// ParserError: (67-75): Expected ';' but got 'constant' diff --git a/test/libsolidity/syntaxTests/parsing/location_specifiers_for_state_variables.sol b/test/libsolidity/syntaxTests/parsing/location_specifiers_for_state_variables.sol new file mode 100644 index 00000000..40eaf838 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/location_specifiers_for_state_variables.sol @@ -0,0 +1,5 @@ +contract Foo { + uint[] memory x; +} +// ---- +// ParserError: (23-29): Expected identifier but got 'memory' diff --git a/test/libsolidity/syntaxTests/parsing/location_specifiers_with_var.sol b/test/libsolidity/syntaxTests/parsing/location_specifiers_with_var.sol new file mode 100644 index 00000000..2b8f08c5 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/location_specifiers_with_var.sol @@ -0,0 +1,5 @@ +contract Foo { + function f() { var memory x; } +} +// ---- +// ParserError: (35-41): Location specifier needs explicit type name. diff --git a/test/libsolidity/syntaxTests/parsing/malformed_enum_declaration.sol b/test/libsolidity/syntaxTests/parsing/malformed_enum_declaration.sol new file mode 100644 index 00000000..811b4219 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/malformed_enum_declaration.sol @@ -0,0 +1,5 @@ +contract c { + enum foo { WARNING,} +} +// ---- +// ParserError: (33-34): Expected identifier after ',' diff --git a/test/libsolidity/syntaxTests/parsing/missing_argument_in_named_args.sol b/test/libsolidity/syntaxTests/parsing/missing_argument_in_named_args.sol new file mode 100644 index 00000000..f7c3fb31 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/missing_argument_in_named_args.sol @@ -0,0 +1,6 @@ +contract test { + function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; } + function b() returns (uint r) { r = a({a: , b: , c: }); } +} +// ---- +// ParserError: (146-147): Expected primary expression. diff --git a/test/libsolidity/syntaxTests/parsing/missing_parameter_name_in_named_args.sol b/test/libsolidity/syntaxTests/parsing/missing_parameter_name_in_named_args.sol new file mode 100644 index 00000000..d7c2f2be --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/missing_parameter_name_in_named_args.sol @@ -0,0 +1,6 @@ +contract test { + function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; } + function b() returns (uint r) { r = a({: 1, : 2, : 3}); } +} +// ---- +// ParserError: (143-144): Expected identifier but got ':' diff --git a/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol b/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol index fd3067e3..51b7bd24 100644 --- a/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol +++ b/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol @@ -2,4 +2,4 @@ contract test { uint256 ; } // ---- -// ParserError: (28-28): Expected identifier, got 'Semicolon' +// ParserError: (28-29): Expected identifier but got ';' diff --git a/test/libsolidity/syntaxTests/parsing/modifier_without_semicolon.sol b/test/libsolidity/syntaxTests/parsing/modifier_without_semicolon.sol new file mode 100644 index 00000000..a9fa33e6 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/modifier_without_semicolon.sol @@ -0,0 +1,5 @@ +contract c { + modifier mod { if (msg.sender == 0) _ } +} +// ---- +// ParserError: (52-53): Expected ';' but got '}' diff --git a/test/libsolidity/syntaxTests/parsing/multiple_statemutability_specifiers.sol b/test/libsolidity/syntaxTests/parsing/multiple_statemutability_specifiers.sol new file mode 100644 index 00000000..a05bf452 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/multiple_statemutability_specifiers.sol @@ -0,0 +1,33 @@ +contract c1 { + function f() payable payable {} +} +contract c2 { + function f() view view {} +} +contract c3 { + function f() pure pure {} +} +contract c4 { + function f() pure view {} +} +contract c5 { + function f() payable view {} +} +contract c6 { + function f() pure payable {} +} +contract c7 { + function f() pure constant {} +} +contract c8 { + function f() view constant {} +} +// ---- +// ParserError: (39-46): State mutability already specified as "payable". +// ParserError: (88-92): State mutability already specified as "view". +// ParserError: (134-138): State mutability already specified as "pure". +// ParserError: (180-184): State mutability already specified as "pure". +// ParserError: (229-233): State mutability already specified as "payable". +// ParserError: (275-282): State mutability already specified as "pure". +// ParserError: (324-332): State mutability already specified as "pure". +// ParserError: (374-382): State mutability already specified as "view". diff --git a/test/libsolidity/syntaxTests/parsing/new_invalid_type_name.sol b/test/libsolidity/syntaxTests/parsing/new_invalid_type_name.sol new file mode 100644 index 00000000..d469bc75 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/new_invalid_type_name.sol @@ -0,0 +1,7 @@ +contract C { + function f() { + new var; + } +} +// ---- +// ParserError: (35-38): Expected explicit type name. diff --git a/test/libsolidity/syntaxTests/parsing/no_function_params.sol b/test/libsolidity/syntaxTests/parsing/no_function_params.sol new file mode 100644 index 00000000..020f1233 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/no_function_params.sol @@ -0,0 +1,7 @@ +contract test { + uint256 stateVar; + function functionName() {} +} +// ---- +// Warning: (36-62): No visibility specified. Defaulting to "public". +// Warning: (36-62): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/payable_accessor.sol b/test/libsolidity/syntaxTests/parsing/payable_accessor.sol new file mode 100644 index 00000000..6504004b --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/payable_accessor.sol @@ -0,0 +1,5 @@ +contract test { + uint payable x; +} +// ---- +// ParserError: (22-29): Expected identifier but got 'payable' diff --git a/test/libsolidity/syntaxTests/parsing/return_var.sol b/test/libsolidity/syntaxTests/parsing/return_var.sol index 47ac9ef0..b9ce8f95 100644 --- a/test/libsolidity/syntaxTests/parsing/return_var.sol +++ b/test/libsolidity/syntaxTests/parsing/return_var.sol @@ -9,17 +9,17 @@ contract C { function f() public pure returns (var storage x, var storage y) {} } // ---- -// ParserError: (38-38): Expected explicit type name. -// ParserError: (71-71): Expected explicit type name. -// ParserError: (106-106): Expected explicit type name. -// ParserError: (157-157): Expected explicit type name. -// ParserError: (192-192): Expected explicit type name. -// ParserError: (199-199): Expected explicit type name. -// ParserError: (247-247): Expected explicit type name. -// ParserError: (251-251): Location specifier needs explicit type name. -// ParserError: (301-301): Expected explicit type name. -// ParserError: (305-305): Location specifier needs explicit type name. -// ParserError: (357-357): Expected explicit type name. -// ParserError: (361-361): Location specifier needs explicit type name. -// ParserError: (372-372): Expected explicit type name. -// ParserError: (376-376): Location specifier needs explicit type name. +// ParserError: (38-41): Expected explicit type name. +// ParserError: (71-74): Expected explicit type name. +// ParserError: (106-109): Expected explicit type name. +// ParserError: (157-160): Expected explicit type name. +// ParserError: (192-195): Expected explicit type name. +// ParserError: (199-202): Expected explicit type name. +// ParserError: (247-250): Expected explicit type name. +// ParserError: (251-258): Location specifier needs explicit type name. +// ParserError: (301-304): Expected explicit type name. +// ParserError: (305-312): Location specifier needs explicit type name. +// ParserError: (357-360): Expected explicit type name. +// ParserError: (361-368): Location specifier needs explicit type name. +// ParserError: (372-375): Expected explicit type name. +// ParserError: (376-383): Location specifier needs explicit type name. diff --git a/test/libsolidity/syntaxTests/parsing/scientific_notation.sol b/test/libsolidity/syntaxTests/parsing/scientific_notation.sol new file mode 100644 index 00000000..5d656508 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/scientific_notation.sol @@ -0,0 +1,7 @@ +contract test { + uint256 a = 2e10; + uint256 b = 2E10; + uint256 c = 200e-2; + uint256 d = 2E10 wei; + uint256 e = 2.5e10; +} diff --git a/test/libsolidity/syntaxTests/parsing/single_function_param.sol b/test/libsolidity/syntaxTests/parsing/single_function_param.sol new file mode 100644 index 00000000..08e531f1 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/single_function_param.sol @@ -0,0 +1,9 @@ +contract test { + uint256 stateVar; + function functionName(bytes32 input) returns (bytes32 out) {} +} +// ---- +// Warning: (36-97): No visibility specified. Defaulting to "public". +// Warning: (58-71): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (82-93): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (36-97): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/single_function_param_trailing_comma.sol b/test/libsolidity/syntaxTests/parsing/single_function_param_trailing_comma.sol new file mode 100644 index 00000000..fe52b3b2 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/single_function_param_trailing_comma.sol @@ -0,0 +1,5 @@ +contract test { + function(uint a,) {} +} +// ---- +// ParserError: (32-33): Unexpected trailing comma in parameter list. diff --git a/test/libsolidity/syntaxTests/parsing/single_return_param_trailing_comma.sol b/test/libsolidity/syntaxTests/parsing/single_return_param_trailing_comma.sol new file mode 100644 index 00000000..99f91819 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/single_return_param_trailing_comma.sol @@ -0,0 +1,5 @@ +contract test { + function() returns (uint a,) {} +} +// ---- +// ParserError: (43-44): Unexpected trailing comma in parameter list. diff --git a/test/libsolidity/syntaxTests/parsing/trailing_comma_in_named_args.sol b/test/libsolidity/syntaxTests/parsing/trailing_comma_in_named_args.sol new file mode 100644 index 00000000..8fc3dab8 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/trailing_comma_in_named_args.sol @@ -0,0 +1,6 @@ +contract test { + function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; } + function b() returns (uint r) { r = a({a: 1, b: 2, c: 3, }); } +} +// ---- +// ParserError: (159-160): Unexpected trailing comma. diff --git a/test/libsolidity/syntaxTests/parsing/tuples_without_commas.sol b/test/libsolidity/syntaxTests/parsing/tuples_without_commas.sol new file mode 100644 index 00000000..fcbb875c --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/tuples_without_commas.sol @@ -0,0 +1,7 @@ +contract C { + function f() { + var a = (2 2); + } +} +// ---- +// ParserError: (42-43): Expected ',' but got 'Number' diff --git a/test/libsolidity/syntaxTests/parsing/var_array.sol b/test/libsolidity/syntaxTests/parsing/var_array.sol new file mode 100644 index 00000000..e08f5511 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/var_array.sol @@ -0,0 +1,5 @@ +contract Foo { + function f() { var[] a; } +} +// ---- +// ParserError: (34-35): Expected identifier but got '[' diff --git a/test/libsolidity/syntaxTests/parsing/var_in_function_arguments.sol b/test/libsolidity/syntaxTests/parsing/var_in_function_arguments.sol index e041247d..2e5a9c58 100644 --- a/test/libsolidity/syntaxTests/parsing/var_in_function_arguments.sol +++ b/test/libsolidity/syntaxTests/parsing/var_in_function_arguments.sol @@ -9,17 +9,17 @@ contract C { function f(var storage x, var storage y) public pure {} } // ---- -// ParserError: (28-28): Expected explicit type name. -// ParserError: (63-63): Expected explicit type name. -// ParserError: (100-100): Expected explicit type name. -// ParserError: (107-107): Expected explicit type name. -// ParserError: (152-152): Expected explicit type name. -// ParserError: (189-189): Expected explicit type name. -// ParserError: (234-234): Expected explicit type name. -// ParserError: (238-238): Location specifier needs explicit type name. -// ParserError: (277-277): Expected explicit type name. -// ParserError: (281-281): Location specifier needs explicit type name. -// ParserError: (322-322): Expected explicit type name. -// ParserError: (326-326): Location specifier needs explicit type name. -// ParserError: (337-337): Expected explicit type name. -// ParserError: (341-341): Location specifier needs explicit type name. +// ParserError: (28-31): Expected explicit type name. +// ParserError: (63-66): Expected explicit type name. +// ParserError: (100-103): Expected explicit type name. +// ParserError: (107-110): Expected explicit type name. +// ParserError: (152-155): Expected explicit type name. +// ParserError: (189-192): Expected explicit type name. +// ParserError: (234-237): Expected explicit type name. +// ParserError: (238-245): Location specifier needs explicit type name. +// ParserError: (277-280): Expected explicit type name. +// ParserError: (281-288): Location specifier needs explicit type name. +// ParserError: (322-325): Expected explicit type name. +// ParserError: (326-333): Location specifier needs explicit type name. +// ParserError: (337-340): Expected explicit type name. +// ParserError: (341-348): Location specifier needs explicit type name. diff --git a/test/libsolidity/syntaxTests/parsing/var_storage_var.sol b/test/libsolidity/syntaxTests/parsing/var_storage_var.sol index 431d4ca5..6e4ccfa5 100644 --- a/test/libsolidity/syntaxTests/parsing/var_storage_var.sol +++ b/test/libsolidity/syntaxTests/parsing/var_storage_var.sol @@ -2,4 +2,4 @@ contract C { var a; } // ---- -// ParserError: (17-17): Function, variable, struct or modifier declaration expected. +// ParserError: (17-20): Function, variable, struct or modifier declaration expected. diff --git a/test/libsolidity/syntaxTests/parsing/variable_definition_in_mapping.sol b/test/libsolidity/syntaxTests/parsing/variable_definition_in_mapping.sol new file mode 100644 index 00000000..61f5be53 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/variable_definition_in_mapping.sol @@ -0,0 +1,7 @@ +contract test { + function fun() { + mapping(var=>bytes32) d; + } +} +// ---- +// ParserError: (44-47): Expected elementary type name for mapping key type diff --git a/test/libsolidity/syntaxTests/tight_packing_literals_050.sol b/test/libsolidity/syntaxTests/tight_packing_literals_050.sol new file mode 100644 index 00000000..ef6da75d --- /dev/null +++ b/test/libsolidity/syntaxTests/tight_packing_literals_050.sol @@ -0,0 +1,26 @@ +pragma experimental "v0.5.0"; +contract C { + function f() pure public returns (bytes32) { + return keccak256(1); + } + function g() pure public returns (bytes32) { + return sha3(1); + } + function h() pure public returns (bytes32) { + return sha256(1); + } + function j() pure public returns (bytes32) { + return ripemd160(1); + } + function k() pure public returns (bytes) { + return abi.encodePacked(1); + } +} + +// ---- +// TypeError: (117-118): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. +// TypeError: (191-198): "sha3" has been deprecated in favour of "keccak256" +// TypeError: (196-197): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. +// TypeError: (277-278): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. +// TypeError: (361-362): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. +// TypeError: (450-451): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. diff --git a/test/libsolidity/syntaxTests/tight_packing_literals_fine.sol b/test/libsolidity/syntaxTests/tight_packing_literals_fine.sol index 46407f71..81e69eb4 100644 --- a/test/libsolidity/syntaxTests/tight_packing_literals_fine.sol +++ b/test/libsolidity/syntaxTests/tight_packing_literals_fine.sol @@ -2,10 +2,21 @@ contract C { function f() pure public returns (bytes32) { return keccak256(uint8(1)); } - function g() pure public returns (bytes) { - return abi.encode(1); + function g() pure public returns (bytes32) { + return sha3(uint8(1)); + } + function h() pure public returns (bytes32) { + return sha256(uint8(1)); + } + function j() pure public returns (bytes32) { + return ripemd160(uint8(1)); } - function h() pure public returns (bytes) { + function k() pure public returns (bytes) { return abi.encodePacked(uint8(1)); } + function l() pure public returns (bytes) { + return abi.encode(1); + } } +// ---- +// Warning: (168-182): "sha3" has been deprecated in favour of "keccak256" diff --git a/test/libsolidity/syntaxTests/tupleAssignments/error_fill.sol b/test/libsolidity/syntaxTests/tupleAssignments/error_fill.sol new file mode 100644 index 00000000..5b7f870b --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/error_fill.sol @@ -0,0 +1,12 @@ +pragma experimental "v0.5.0"; +contract C { + function f() public pure returns (uint, uint, bytes32) { + uint a; + bytes32 b; + (a,) = f(); + (,b) = f(); + } +} +// ---- +// TypeError: (126-136): Different number of components on the left hand side (2) than on the right hand side (3). +// TypeError: (140-150): Different number of components on the left hand side (2) than on the right hand side (3). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/large_component_count.sol b/test/libsolidity/syntaxTests/tupleAssignments/large_component_count.sol new file mode 100644 index 00000000..bbf21d7e --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/large_component_count.sol @@ -0,0 +1,24 @@ +pragma experimental "v0.5.0"; +contract C { + function g() public pure returns ( + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint + ) { } + function f() public pure returns (uint, uint, bytes32) { + uint a; + uint b; + (,,,,a,,,,b,,,,) = g(); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/tupleAssignments/nowarn_explicit_singleton_token_expression.sol b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_explicit_singleton_token_expression.sol new file mode 100644 index 00000000..3262781b --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_explicit_singleton_token_expression.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + uint a; + (a,) = (uint(1),); + } +} +// ---- +// Warning: (53-70): Different number of components on the left hand side (2) than on the right hand side (1). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_memory.sol b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_memory.sol new file mode 100644 index 00000000..b20bbf90 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_memory.sol @@ -0,0 +1,8 @@ +contract C { + struct S { uint a; uint b; } + function f() pure public { + S memory x; + S memory y; + (x, y) = (y, x); + } +} diff --git a/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_storage_pointers.sol b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_storage_pointers.sol new file mode 100644 index 00000000..5f7a18f7 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_storage_pointers.sol @@ -0,0 +1,10 @@ + contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + S storage x_local = x; + S storage y_local = y; + S storage z_local = x; + (x, y_local, x_local, z_local) = (y, x_local, y_local, y); + } + } diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_assignment.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_assignment.sol new file mode 100644 index 00000000..a079a509 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_assignment.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure returns (uint, uint, bytes32) { + uint a; + bytes32 b; + (a,) = f(); + (,b) = f(); + } +} +// ---- +// Warning: (96-106): Different number of components on the left hand side (2) than on the right hand side (3). +// Warning: (110-120): Different number of components on the left hand side (2) than on the right hand side (3). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_vardecl.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_vardecl.sol new file mode 100644 index 00000000..1d243c7c --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_vardecl.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure returns (uint, uint, uint, uint) { + // Can later be replaced by (uint a, uint b,) = f(); + var (a,b,) = f(); + a; b; + } +} +// ---- +// Warning: (136-137): Use of the "var" keyword is deprecated. +// Warning: (138-139): Use of the "var" keyword is deprecated. +// Warning: (131-147): Different number of components on the left hand side (3) than on the right hand side (4). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies.sol new file mode 100644 index 00000000..e4c3e694 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies.sol @@ -0,0 +1,9 @@ +contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + (x, y) = (y, x); + } +} +// ---- +// Warning: (79-94): This assignment performs two copies to storage. Since storage copies do not first copy to a temporary location, one of them might be overwritten before the second is executed and thus may have unexpected effects. It is safer to perform the copies separately or assign to storage pointers first. diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_left.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_left.sol new file mode 100644 index 00000000..b2979804 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_left.sol @@ -0,0 +1,10 @@ +contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + (,x, y) = (1, 2, y, x); + } +} +// ---- +// Warning: (79-101): This assignment performs two copies to storage. Since storage copies do not first copy to a temporary location, one of them might be overwritten before the second is executed and thus may have unexpected effects. It is safer to perform the copies separately or assign to storage pointers first. +// Warning: (79-101): Different number of components on the left hand side (3) than on the right hand side (4). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_right.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_right.sol new file mode 100644 index 00000000..aa35d7d4 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_right.sol @@ -0,0 +1,10 @@ +contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + (x, y, ) = (y, x, 1, 2); + } +} +// ---- +// Warning: (79-102): This assignment performs two copies to storage. Since storage copies do not first copy to a temporary location, one of them might be overwritten before the second is executed and thus may have unexpected effects. It is safer to perform the copies separately or assign to storage pointers first. +// Warning: (79-102): Different number of components on the left hand side (3) than on the right hand side (4). diff --git a/test/libsolidity/syntaxTests/types/bytes0.sol b/test/libsolidity/syntaxTests/types/bytes0.sol new file mode 100644 index 00000000..7c6d5974 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytes0.sol @@ -0,0 +1,5 @@ +contract C { + bytes0 b0 = 1; +} +// ---- +// DeclarationError: (15-21): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/types/bytes256.sol b/test/libsolidity/syntaxTests/types/bytes256.sol new file mode 100644 index 00000000..22b5408d --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytes256.sol @@ -0,0 +1,5 @@ +contract C { + bytes256 b256 = 1; +} +// ---- +// DeclarationError: (15-23): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/types/bytes33.sol b/test/libsolidity/syntaxTests/types/bytes33.sol new file mode 100644 index 00000000..7edf13d3 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytes33.sol @@ -0,0 +1,5 @@ +contract C { + bytes33 b33 = 1; +} +// ---- +// DeclarationError: (15-22): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/types/bytesm.sol b/test/libsolidity/syntaxTests/types/bytesm.sol new file mode 100644 index 00000000..550760b9 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytesm.sol @@ -0,0 +1,36 @@ +contract C { + byte b = byte(1); + bytes1 b1 = b; + bytes2 b2 = b1; + bytes3 b3 = b2; + bytes4 b4 = b3; + bytes5 b5 = b4; + bytes6 b6 = b5; + bytes7 b7 = b6; + bytes8 b8 = b7; + bytes9 b9 = b8; + bytes10 b10 = b9; + bytes11 b11 = b10; + bytes12 b12 = b11; + bytes13 b13 = b12; + bytes14 b14 = b13; + bytes15 b15 = b14; + bytes16 b16 = b15; + bytes17 b17 = b16; + bytes18 b18 = b17; + bytes19 b19 = b18; + bytes20 b20 = b19; + bytes21 b21 = b20; + bytes22 b22 = b21; + bytes23 b23 = b22; + bytes24 b24 = b23; + bytes25 b25 = b24; + bytes26 b26 = b25; + bytes27 b27 = b26; + bytes28 b28 = b27; + bytes29 b29 = b28; + bytes30 b30 = b29; + bytes31 b31 = b30; + bytes32 b32 = b31; +} +// ---- diff --git a/test/libsolidity/syntaxTests/types/empty_tuple_event.sol b/test/libsolidity/syntaxTests/types/empty_tuple_event.sol new file mode 100644 index 00000000..3e40b155 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/empty_tuple_event.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.4.3; +contract C { + event SomeEvent(); + function a() public { + (SomeEvent(), 7); + } +} +// ---- +// Warning: (95-106): Invoking events without "emit" prefix is deprecated. +// Warning: (95-106): Tuple component cannot be empty. diff --git a/test/libsolidity/syntaxTests/types/empty_tuple_event_050.sol b/test/libsolidity/syntaxTests/types/empty_tuple_event_050.sol new file mode 100644 index 00000000..aec5ff2a --- /dev/null +++ b/test/libsolidity/syntaxTests/types/empty_tuple_event_050.sol @@ -0,0 +1,10 @@ +pragma experimental "v0.5.0"; +contract C { + event SomeEvent(); + function a() public { + (SomeEvent(), 7); + } +} +// ---- +// TypeError: (101-112): Event invocations have to be prefixed by "emit". +// TypeError: (101-112): Tuple component cannot be empty. diff --git a/test/libsolidity/syntaxTests/types/empty_tuple_function.sol b/test/libsolidity/syntaxTests/types/empty_tuple_function.sol new file mode 100644 index 00000000..05b54442 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/empty_tuple_function.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.4.3; +contract C { + function f() private pure {} + function a() public pure { + bool x = true; + bool y = true; + (x) ? (f(), y = false) : (f(), y = false); + } +} +// ---- +// Warning: (162-165): Tuple component cannot be empty. +// Warning: (181-184): Tuple component cannot be empty. diff --git a/test/libsolidity/syntaxTests/types/empty_tuple_function_050.sol b/test/libsolidity/syntaxTests/types/empty_tuple_function_050.sol new file mode 100644 index 00000000..c4b9e03f --- /dev/null +++ b/test/libsolidity/syntaxTests/types/empty_tuple_function_050.sol @@ -0,0 +1,11 @@ +pragma experimental "v0.5.0"; +contract C { + function f() private pure {} + function a() public pure { + bool x = true; + bool y = true; + (x) ? (f(), y = false) : (f(), y = false); + } +} +// ---- +// TypeError: (168-171): Tuple component cannot be empty. diff --git a/test/libsolidity/syntaxTests/types/empty_tuple_lvalue.sol b/test/libsolidity/syntaxTests/types/empty_tuple_lvalue.sol new file mode 100644 index 00000000..cba30c1b --- /dev/null +++ b/test/libsolidity/syntaxTests/types/empty_tuple_lvalue.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.4.3; +contract C { + function f() private pure {} + function a() public { + uint x; + uint y; + (x, y) = (f(), f()); + } +} +// ---- +// Warning: (146-149): Tuple component cannot be empty. +// Warning: (151-154): Tuple component cannot be empty. +// TypeError: (145-155): Type tuple(tuple(),tuple()) is not implicitly convertible to expected type tuple(uint256,uint256). diff --git a/test/libsolidity/syntaxTests/types/empty_tuple_lvalue_050.sol b/test/libsolidity/syntaxTests/types/empty_tuple_lvalue_050.sol new file mode 100644 index 00000000..b0691778 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/empty_tuple_lvalue_050.sol @@ -0,0 +1,11 @@ +pragma experimental "v0.5.0"; +contract C { + function f() private pure {} + function a() public { + uint x; + uint y; + (x, y) = (f(), f()); + } +} +// ---- +// TypeError: (152-155): Tuple component cannot be empty. diff --git a/test/libsolidity/syntaxTests/types/empty_tuple_lvalue_array.sol b/test/libsolidity/syntaxTests/types/empty_tuple_lvalue_array.sol new file mode 100644 index 00000000..f8b2ae7e --- /dev/null +++ b/test/libsolidity/syntaxTests/types/empty_tuple_lvalue_array.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.3; +contract C { + function f() private pure {} + function a() public { + uint x; + uint y; + (x, y) = [f(), f()]; + } +} +// ---- +// TypeError: (146-149): Array component cannot be empty. diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index febb0c26..11714017 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(solfuzzer fuzzer.cpp) -target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) add_executable(isoltest isoltest.cpp ../Options.cpp ../libsolidity/SyntaxTest.cpp ../libsolidity/AnalysisFramework.cpp) target_link_libraries(isoltest PRIVATE libsolc solidity evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) |