diff --git a/.circleci/config.yml b/.circleci/config.yml index 48b9a537f22f..5d276b2632d5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,9 +17,20 @@ defaults: [ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON" cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS make -j4 + - run_build_ossfuzz: &run_build_ossfuzz + name: Build_ossfuzz + command: | + mkdir -p build + cd build + /src/LPM/external.protobuf/bin/protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz + cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS + make ossfuzz ossfuzz_proto -j4 - run_tests: &run_tests name: Tests command: scripts/tests.sh --junit_report test_results + - run_regressions: &run_regressions + name: Regression tests + command: scripts/regressions.py -o test_results - solc_artifact: &solc_artifact path: build/solc/solc destination: solc @@ -29,6 +40,15 @@ defaults: - solc/solc - test/soltest - test/tools/solfuzzer + - ossfuzz_artifacts: &ossfuzz_artifacts + root: build + paths: + - test/tools/ossfuzz/solc_opt_ossfuzz + - test/tools/ossfuzz/solc_noopt_ossfuzz + - test/tools/ossfuzz/const_opt_ossfuzz + - test/tools/ossfuzz/strictasm_diff_ossfuzz + - test/tools/ossfuzz/yul_proto_ossfuzz + - test/tools/ossfuzz/yul_proto_diff_ossfuzz version: 2 jobs: @@ -86,7 +106,7 @@ jobs: command: | test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) - test_emscripten_external: + test_emscripten_external_gnosis: docker: - image: circleci/node:10 environment: @@ -96,14 +116,23 @@ jobs: - attach_workspace: at: /tmp/workspace - run: - name: Install external tests deps + name: External GnosisSafe tests command: | - node --version - npm --version + test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js + + test_emscripten_external_zeppelin: + docker: + - image: circleci/node:10 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: /tmp/workspace - run: - name: External tests + name: External Zeppelin tests command: | - test/externalTests.sh /tmp/workspace/soljson.js || test/externalTests.sh /tmp/workspace/soljson.js + test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js build_x86_linux: docker: @@ -117,8 +146,7 @@ jobs: name: Install build dependencies 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 libcvc4-dev - ./scripts/install_obsolete_jsoncpp_1_7_4.sh + apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - run: *setup_prerelease_commit_hash - run: *run_build - store_artifacts: *solc_artifact @@ -139,8 +167,7 @@ jobs: name: Install build dependencies 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 libcvc4-dev - ./scripts/install_obsolete_jsoncpp_1_7_4.sh + apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - run: *setup_prerelease_commit_hash - run: *run_build @@ -179,8 +206,7 @@ jobs: name: Install build dependencies command: | apt-get -qq update - apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev - ./scripts/install_obsolete_jsoncpp_1_7_4.sh + apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - run: *setup_prerelease_commit_hash - run: *run_build - store_artifacts: *solc_artifact @@ -306,12 +332,19 @@ jobs: update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1 - run: mkdir -p test_results - run: - name: Run tests with ASAN + name: Run soltest with ASAN command: | ulimit -a # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). ulimit -s 16384 build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test + - run: + name: Run commandline tests with ASAN + command: | + ulimit -a + # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). + ulimit -s 16384 + test/cmdlineTests.sh - store_test_results: path: test_results/ - store_artifacts: @@ -381,6 +414,51 @@ jobs: path: docs/_build/html/ destination: docs-html + build_x86_linux_ossfuzz: + docker: + - image: buildpack-deps:cosmic + environment: + TERM: xterm + CC: /usr/bin/clang-7 + CXX: /usr/bin/clang++-7 + CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake + steps: + - checkout + - run: + name: Install build dependencies + command: | + apt-get -qq update + apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\* + ./scripts/install_lpm.sh + ./scripts/install_libfuzzer.sh + - run: *setup_prerelease_commit_hash + - run: *run_build_ossfuzz + - persist_to_workspace: *ossfuzz_artifacts + + test_x86_ossfuzz_regression: + docker: + - image: buildpack-deps:cosmic + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build + - run: + name: Install dependencies + command: | + apt-get -qq update + apt-get -qy install libcvc4-dev llvm-7-dev + ./scripts/download_ossfuzz_corpus.sh + update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1 + - run: mkdir -p test_results + - run: *run_regressions + - store_test_results: + path: test_results/ + - store_artifacts: + path: test_results/ + destination: test_results/ + workflows: version: 2 build_all: @@ -393,7 +471,11 @@ workflows: <<: *build_on_tags requires: - build_emscripten - - test_emscripten_external: + - test_emscripten_external_zeppelin: + <<: *build_on_tags + requires: + - build_emscripten + - test_emscripten_external_gnosis: <<: *build_on_tags requires: - build_emscripten @@ -406,9 +488,7 @@ workflows: requires: - build_x86_linux - test_x86_clang7_asan: - filters: - branches: - only: develop + <<: *build_on_tags requires: - build_x86_clang7_asan - test_x86_mac: @@ -421,3 +501,28 @@ workflows: <<: *build_on_tags requires: - build_x86_archlinux + - build_x86_linux_ossfuzz: *build_on_tags + + test_nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - develop + jobs: + - build_emscripten: *build_on_tags + - test_emscripten_external_zeppelin: + <<: *build_on_tags + requires: + - build_emscripten + - test_emscripten_external_gnosis: + <<: *build_on_tags + requires: + - build_emscripten + - build_x86_linux_ossfuzz: *build_on_tags + - test_x86_ossfuzz_regression: + <<: *build_on_tags + requires: + - build_x86_linux_ossfuzz diff --git a/.editorconfig b/.editorconfig index 7b8a7be9f83a..d80fb3570f9d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,3 +17,7 @@ indent_size = 4 [std/**.sol] indent_style = space indent_size = 4 + +[*.{txt,cmake}] +indent_style = tab +indent_size = 4 diff --git a/.gitignore b/.gitignore index 62ddd1af001c..bbdf9d68dbe9 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ prerelease.txt # Build directory build/ +build*/ emscripten_build/ docs/_build docs/utils/__pycache__ @@ -44,6 +45,9 @@ deps/cache [._]*.sw[a-p] [._]sw[a-p] +# emacs stuff +*~ + # IDE files .idea .vscode @@ -53,3 +57,6 @@ CMakeLists.txt.user /.vs /.cproject /.project + +# place to put local temporary files +tmp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f5d78383eae..56e58f3320a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.5.7") +set(PROJECT_VERSION "0.5.8") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) option(LLL "Build LLL" OFF) diff --git a/CODING_STYLE.md b/CODING_STYLE.md index a0fe98644557..3f44cd1f9c5c 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -46,6 +46,8 @@ foo->bar(someLongVariableName, cout << "some very long string that contains completely irrelevant text that talks about this and that and contains the words \"lorem\" and \"ipsum\"" << endl; ``` +To set indentation and tab width settings uniformly, the repository contains an [EditorConfig](https://editorconfig.org/) [`.editorconfig`](https://github.com/ethereum/solidity/blob/develop/.editorconfig) file, which describes some of the styles used and which is recognized by many IDE's and editors. + ## 1. Namespaces 1. No `using namespace` declarations in header files. diff --git a/Changelog.md b/Changelog.md index f9755997ed5a..8ffe5df6b3f2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,46 @@ +### 0.5.8 (2019-04-30) + +Important Bugfixes: + * Code Generator: Fix initialization routine of uninitialized internal function pointers in constructor context. + * Yul Optimizer: Fix SSA transform for multi-assignments. + + +Language Features: + * ABIEncoderV2: Implement encoding of calldata arrays and structs. + * Code Generation: Implement copying recursive structs from storage to memory. + * Yul: Disallow function definitions inside for-loop init blocks. + + +Compiler Features: + * ABI Decoder: Raise a runtime error on dirty inputs when using the experimental decoder. + * Optimizer: Add rule for shifts by constants larger than 255 for Constantinople. + * Optimizer: Add rule to simplify certain ANDs and SHL combinations + * SMTChecker: Support arithmetic compound assignment operators. + * SMTChecker: Support unary increment and decrement for array and mapping access. + * SMTChecker: Show unsupported warning for inline assembly blocks. + * SMTChecker: Support mod. + * SMTChecker: Support ``contract`` type. + * SMTChecker: Support ``this`` as address. + * SMTChecker: Support address members. + * Standard JSON Interface: Metadata settings now re-produce the original ``"useLiteralContent"`` setting from the compilation input. + * Yul: Adds break and continue keywords to for-loop syntax. + * Yul: Support ``.`` as part of identifiers. + * Yul Optimizer: Adds steps for detecting and removing of dead code. + + +Bugfixes: + * SMTChecker: Implement Boolean short-circuiting. + * SMTChecker: SSA control-flow did not take into account state variables that were modified inside inlined functions that were called inside branches. + * Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation. + * Type System: Allow direct call to base class functions that have overloads. + * Yul: Properly register functions and disallow shadowing between function variables and variables in the outside scope. + + +Build System: + * Soltest: Add commandline option `--test` / `-t` to isoltest which takes a string that allows filtering unit tests. + * soltest.sh: allow environment variable ``SOLIDITY_BUILD_DIR`` to specify build folder and add ``--help`` usage. + + ### 0.5.7 (2019-03-26) Important Bugfixes: @@ -368,6 +411,20 @@ Bugfixes: * Parser: Fix incorrect source location for nameless parameters. * Command Line Interface: Fix internal error when compiling stdin with no content and --ast option. + +### 0.4.26 (2019-04-29) + +Important Bugfixes: + * Code Generator: Fix initialization routine of uninitialized internal function pointers in constructor context. + * Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation. + +Bugfixes: + * ABIEncoderV2: Refuse to generate code that is known to be potentially buggy. + * General: Split rule list such that JavaScript environments with small stacks can use the compiler. + +Note: The above changes are not included in 0.5.0, because they were backported. + + ### 0.4.25 (2018-09-12) Important Bugfixes: diff --git a/README.md b/README.md index 959fefc2a948..b32f1ce2564c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # The Solidity Contract-Oriented Programming Language -[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/ethereum/solidity.svg?branch=develop)](https://travis-ci.org/ethereum/solidity) +[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform. ## Table of Contents diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 60ebe88634fb..4d3dfb418f59 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -33,10 +33,10 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-Werror) # Configuration-specific compiler settings. - set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DETH_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g3") # Additional GCC-specific compiler settings. if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") @@ -54,6 +54,12 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # Set stack size to 32MB - by default Apple's clang defines a stack size of 8MB. # Normally 16MB is enough to run all tests, but it will exceed the stack, if -DSANITIZE=address is used. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-stack_size -Wl,0x2000000") + + # Boost libraries use visibility=hidden to reduce unnecessary DWARF entries. + # Unless we match visibility, ld will give a warning message like: + # ld: warning: direct access in function 'boost::filesystem... from file ... + # means the weak symbol cannot be overridden at runtime. This was likely caused by different translation units being compiled with different visibility settings. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") endif() # Some Linux-specific Clang settings. We don't want these for OS X. diff --git a/cmake/EthOptions.cmake b/cmake/EthOptions.cmake index 68d6cb04505f..6dca5e1be5d8 100644 --- a/cmake/EthOptions.cmake +++ b/cmake/EthOptions.cmake @@ -1,3 +1,6 @@ +# CMAKE macros to set default CMAKE options and to show the +# resulting configuration. + macro(configure_project) set(NAME ${PROJECT_NAME}) @@ -22,7 +25,7 @@ endmacro() macro(print_config NAME) message("") message("------------------------------------------------------------------------") - message("-- Configuring ${NAME}") + message("-- Configuring ${NAME} ${PROJECT_VERSION}") message("------------------------------------------------------------------------") message("-- CMake Version ${CMAKE_VERSION}") message("-- CMAKE_BUILD_TYPE Build type ${CMAKE_BUILD_TYPE}") @@ -36,6 +39,9 @@ endif() if (SUPPORT_TOOLS) message("-- TOOLS Build tools ${TOOLS}") endif() + message("------------------------------------------------------------------ flags") + message("-- OSSFUZZ ${OSSFUZZ}") + message("-- LLL ${LLL}") message("------------------------------------------------------------------------") message("") endmacro() diff --git a/cmake/toolchains/libfuzzer.cmake b/cmake/toolchains/libfuzzer.cmake new file mode 100644 index 000000000000..86d45d304a3f --- /dev/null +++ b/cmake/toolchains/libfuzzer.cmake @@ -0,0 +1,2 @@ +# Require libfuzzer specific flags +set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++") diff --git a/docs/assembly.rst b/docs/assembly.rst index 835ae11ac249..2b918a526a09 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -42,7 +42,8 @@ Syntax ------ Assembly parses comments, literals and identifiers in the same way as Solidity, so you can use the -usual ``//`` and ``/* */`` comments. Inline assembly is marked by ``assembly { ... }`` and inside +usual ``//`` and ``/* */`` comments. There is one exception: Identifiers in inline assembly can contain +``.``. Inline assembly is marked by ``assembly { ... }`` and inside these curly braces, you can use the following (see the later sections for more details): - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) @@ -765,7 +766,7 @@ Grammar:: SubAssembly AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral - Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* + Identifier = [a-zA-Z_$] [a-zA-Z_0-9.]* AssemblyCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')' AssemblyLocalDefinition = 'let' IdentifierOrList ( ':=' AssemblyExpression )? AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression diff --git a/docs/bugs.json b/docs/bugs.json index 55214f7d5f6c..2f13b13dfb3f 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,19 +1,65 @@ [ + { + "name": "UninitializedFunctionPointerInConstructor", + "summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.", + "description": "Uninitialized internal function pointers point to a special piece of code that causes a revert when called. Jump target positions are different during construction and after deployment, but the code for setting this special jump target only considered the situation after deployment.", + "introduced": "0.5.0", + "fixed": "0.5.8", + "severity": "very low" + }, + { + "name": "UninitializedFunctionPointerInConstructor_0.4.x", + "summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.", + "description": "Uninitialized internal function pointers point to a special piece of code that causes a revert when called. Jump target positions are different during construction and after deployment, but the code for setting this special jump target only considered the situation after deployment.", + "introduced": "0.4.5", + "fixed": "0.4.26", + "severity": "very low" + }, + { + "name": "IncorrectEventSignatureInLibraries", + "summary": "Contract types used in events in libraries cause an incorrect event signature hash", + "description": "Instead of using the type `address` in the hashed signature, the actual contract name was used, leading to a wrong hash in the logs.", + "introduced": "0.5.0", + "fixed": "0.5.8", + "severity": "very low" + }, + { + "name": "IncorrectEventSignatureInLibraries_0.4.x", + "summary": "Contract types used in events in libraries cause an incorrect event signature hash", + "description": "Instead of using the type `address` in the hashed signature, the actual contract name was used, leading to a wrong hash in the logs.", + "introduced": "0.3.0", + "fixed": "0.4.26", + "severity": "very low" + }, { "name": "ABIEncoderV2PackedStorage", "summary": "Storage structs and arrays with types shorter than 32 bytes can cause data corruption if encoded directly from storage using the experimental ABIEncoderV2.", "description": "Elements of structs and arrays that are shorter than 32 bytes are not properly decoded from storage when encoded directly (i.e. not via a memory type) using ABIEncoderV2. This can cause corruption in the values themselves but can also overwrite other parts of the encoded data.", - "introduced": "0.4.19", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", + "introduced": "0.5.0", "fixed": "0.5.7", "severity": "low", "conditions": { "ABIEncoderV2": true } }, + { + "name": "ABIEncoderV2PackedStorage_0.4.x", + "summary": "Storage structs and arrays with types shorter than 32 bytes can cause data corruption if encoded directly from storage using the experimental ABIEncoderV2.", + "description": "Elements of structs and arrays that are shorter than 32 bytes are not properly decoded from storage when encoded directly (i.e. not via a memory type) using ABIEncoderV2. This can cause corruption in the values themselves but can also overwrite other parts of the encoded data.", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", + "introduced": "0.4.19", + "fixed": "0.4.26", + "severity": "low", + "conditions": { + "ABIEncoderV2": true + } + }, { "name": "IncorrectByteInstructionOptimization", - "summary": "The optimizer incorrectly handles byte opcodes whose second argument is 31 or a constant expression that evaluates to 31. This can result in unexpected values.", + "summary": "The optimizer incorrectly handles byte opcodes whose second argument is 31 or a constant expression that evaluates to 31. This can result in unexpected values.", "description": "The optimizer incorrectly handles byte opcodes that use the constant 31 as second argument. This can happen when performing index access on bytesNN types with a compile-time constant value (not index) of 31 or when using the byte opcode in inline assembly.", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", "introduced": "0.5.5", "fixed": "0.5.7", "severity": "very low", @@ -25,6 +71,7 @@ "name": "DoubleShiftSizeOverflow", "summary": "Double bitwise shifts by large constants whose sum overflows 256 bits can result in unexpected values.", "description": "Nested logical shift operations whose total shift size is 2**256 or more are incorrectly optimized. This only applies to shifts by numbers of bits that are compile-time constant expressions.", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", "introduced": "0.5.5", "fixed": "0.5.6", "severity": "low", @@ -37,6 +84,7 @@ "name": "ExpExponentCleanup", "summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.", "description": "Higher order bits in the exponent are not properly cleaned before the EXP opcode is applied if the type of the exponent expression is smaller than 256 bits and not smaller than the type of the base. In that case, the result might be larger than expected if the exponent is assumed to lie within the value range of the type. Literal numbers as exponents are unaffected as are exponents or bases of type uint256.", + "link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/", "fixed": "0.4.25", "severity": "medium/high", "check": {"regex-source": "[^/]\\*\\* *[^/0-9 ]"} @@ -45,6 +93,7 @@ "name": "EventStructWrongData", "summary": "Using structs in events logged wrong data.", "description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.", + "link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/", "introduced": "0.4.17", "fixed": "0.4.25", "severity": "very low", @@ -54,6 +103,7 @@ "name": "NestedArrayFunctionCallDecoder", "summary": "Calling functions that return multi-dimensional fixed-size arrays can result in memory corruption.", "description": "If Solidity code calls a function that returns a multi-dimensional fixed-size array, array elements are incorrectly interpreted as memory pointers and thus can cause memory corruption if the return values are accessed. Calling functions with multi-dimensional fixed-size arrays is unaffected as is returning fixed-size arrays from function calls. The regular expression only checks if such functions are present, not if they are called, which is required for the contract to be affected.", + "link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/", "introduced": "0.1.4", "fixed": "0.4.22", "severity": "medium", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index fd2ad4b5e3cc..f0e5b95b66d5 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -211,6 +211,7 @@ }, "0.3.0": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -231,6 +232,7 @@ }, "0.3.1": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -250,6 +252,7 @@ }, "0.3.2": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -269,6 +272,7 @@ }, "0.3.3": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -287,6 +291,7 @@ }, "0.3.4": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -305,6 +310,7 @@ }, "0.3.5": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -323,6 +329,7 @@ }, "0.3.6": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -339,6 +346,7 @@ }, "0.4.0": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -355,6 +363,7 @@ }, "0.4.1": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -371,6 +380,8 @@ }, "0.4.10": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -383,6 +394,8 @@ }, "0.4.11": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -394,6 +407,8 @@ }, "0.4.12": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -404,6 +419,8 @@ }, "0.4.13": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -414,6 +431,8 @@ }, "0.4.14": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -423,6 +442,8 @@ }, "0.4.15": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector" @@ -431,6 +452,8 @@ }, "0.4.16": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector" @@ -439,6 +462,8 @@ }, "0.4.17": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder", @@ -448,6 +473,8 @@ }, "0.4.18": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -456,7 +483,9 @@ }, "0.4.19": { "bugs": [ - "ABIEncoderV2PackedStorage", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -465,6 +494,7 @@ }, "0.4.2": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -480,7 +510,9 @@ }, "0.4.20": { "bugs": [ - "ABIEncoderV2PackedStorage", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -489,7 +521,9 @@ }, "0.4.21": { "bugs": [ - "ABIEncoderV2PackedStorage", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -498,7 +532,9 @@ }, "0.4.22": { "bugs": [ - "ABIEncoderV2PackedStorage", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "OneOfTwoConstructorsSkipped" @@ -507,7 +543,9 @@ }, "0.4.23": { "bugs": [ - "ABIEncoderV2PackedStorage", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData" ], @@ -515,7 +553,9 @@ }, "0.4.24": { "bugs": [ - "ABIEncoderV2PackedStorage", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData" ], @@ -523,12 +563,19 @@ }, "0.4.25": { "bugs": [ - "ABIEncoderV2PackedStorage" + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x" ], "released": "2018-09-12" }, + "0.4.26": { + "bugs": [], + "released": "2019-04-29" + }, "0.4.3": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -543,6 +590,7 @@ }, "0.4.4": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -556,6 +604,8 @@ }, "0.4.5": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -570,6 +620,8 @@ }, "0.4.6": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -583,6 +635,8 @@ }, "0.4.7": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -595,6 +649,8 @@ }, "0.4.8": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -607,6 +663,8 @@ }, "0.4.9": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -619,36 +677,48 @@ }, "0.5.0": { "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", "ABIEncoderV2PackedStorage" ], "released": "2018-11-13" }, "0.5.1": { "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", "ABIEncoderV2PackedStorage" ], "released": "2018-12-03" }, "0.5.2": { "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", "ABIEncoderV2PackedStorage" ], "released": "2018-12-19" }, "0.5.3": { "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", "ABIEncoderV2PackedStorage" ], "released": "2019-01-22" }, "0.5.4": { "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", "ABIEncoderV2PackedStorage" ], "released": "2019-02-12" }, "0.5.5": { "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", "ABIEncoderV2PackedStorage", "IncorrectByteInstructionOptimization", "DoubleShiftSizeOverflow" @@ -657,13 +727,22 @@ }, "0.5.6": { "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", "ABIEncoderV2PackedStorage", "IncorrectByteInstructionOptimization" ], "released": "2019-03-13" }, "0.5.7": { - "bugs": [], + "bugs": [ + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries" + ], "released": "2019-03-26" + }, + "0.5.8": { + "bugs": [], + "released": "2019-04-30" } } \ No newline at end of file diff --git a/docs/contributing.rst b/docs/contributing.rst index c0ac358b7a92..5fce8d1a6d24 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -71,15 +71,21 @@ Running the compiler tests ========================== The ``./scripts/tests.sh`` script executes most Solidity tests and -runs ``aleth`` automatically if it is in the path, but does not download it, +runs ``aleth`` automatically if it is in the path. The script does not download it, so you need to install it first. Please read on for the details. -Solidity includes different types of tests, most of them bundled into the ``soltest`` -application. Some of them require the ``aleth`` client in testing mode, others require ``libz3``. +Solidity includes different types of tests, most of them bundled into the `Boost C++ Test Framework `_ application ``soltest``. +Some of them require the ``aleth`` client in testing mode, others require ``libz3``. To run a basic set of tests that require neither ``aleth`` nor ``libz3``, run -``./scripts/soltest.sh --no-ipc --no-smt``. This script runs ``./build/test/soltest`` -internally. +``./scripts/soltest.sh --no-ipc --no-smt``. + +``./build/test/soltest --help`` has extensive help on all of the options available. +See especially: + +- `show_progress (-p) `_ to show test completion, +- `run_test (-t) `_ to run specific tests cases, and +- `report-level (-r) `_ give a more detailed report. .. note :: @@ -90,7 +96,7 @@ The option ``--no-smt`` disables the tests that require ``libz3`` and ``--no-ipc`` disables those that require ``aleth``. If you want to run the ipc tests (that test the semantics of the generated code), -you need to install `aleth `_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``. +you need to install `aleth `_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``. To run the actual tests, use: ``./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc``. @@ -415,4 +421,4 @@ Running Documentation Tests --------------------------- Make sure your contributions pass our documentation tests by running ``./scripts/docs.sh`` that installs dependencies -needed for documentation and checks for any problems such as broken links or syntax issues. \ No newline at end of file +needed for documentation and checks for any problems such as broken links or syntax issues. diff --git a/docs/control-structures.rst b/docs/control-structures.rst index fa59a4adf127..c8be772f493f 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -72,7 +72,6 @@ all function arguments have to be copied to memory. When calling functions of other contracts, you can specify the amount of Wei or gas sent with the call with the special options ``.value()`` and ``.gas()``, respectively. Any Wei you send to the contract is added to the total balance of the contract: - :: pragma solidity >=0.4.0 <0.7.0; diff --git a/docs/index.rst b/docs/index.rst index 0ec500e7f9b7..e36d4ffce48f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,10 @@ and multi-signature wallets. When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number `to indicate this fast pace of change `_. +.. warning:: + + Solidity recently released the 0.5.x version that introduced a lot of breaking changes. Make sure you read :doc:`the full list <050-breaking-changes>`. + Language Documentation ---------------------- @@ -63,6 +67,7 @@ version stands as a reference. * `Simplified Chinese `_ (in progress) * `Spanish `_ +* `Turkish `_ (partial) * `Russian `_ (rather outdated) * `Korean `_ (in progress) * `French `_ (in progress) @@ -79,6 +84,7 @@ Contents installing-solidity.rst solidity-by-example.rst solidity-in-depth.rst + natspec-format.rst security-considerations.rst resources.rst using-the-compiler.rst diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index fcbeb3aaad2a..cea4cbc7b130 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -170,7 +170,7 @@ The following are dependencies for all builds of Solidity: +-----------------------------------+-------------------------------------------------------+ | `Git`_ | Command-line tool for retrieving source code. | +-----------------------------------+-------------------------------------------------------+ -| `z3`_ (version 5.6+, Optional) | For use with SMT checker. | +| `z3`_ (version 4.6+, Optional) | For use with SMT checker. | +-----------------------------------+-------------------------------------------------------+ | `cvc4`_ (Optional) | For use with SMT checker. | +-----------------------------------+-------------------------------------------------------+ diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index 5195a131c55f..3b680113e9f0 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -89,12 +89,12 @@ It is activated for the Ubuntu PPA releases in most versions, but not for solc-js, the Docker images, Windows binaries or the statically-built Linux binaries. -If you use -``pragma experimental SMTChecker;``, then you get additional -safety warnings which are obtained by querying an SMT solver. -The component does not yet support all features of the Solidity language -and likely outputs many warnings. In case it reports unsupported -features, the analysis may not be fully sound. +If you use ``pragma experimental SMTChecker;``, then you get additional +:ref:`safety warnings` which are obtained by querying an +SMT solver. +The component does not yet support all features of the Solidity language and +likely outputs many warnings. In case it reports unsupported features, the +analysis may not be fully sound. .. index:: source file, ! import, module @@ -238,6 +238,7 @@ GitHub and automatically retrieves the file over the network. You can import the iterable mapping as above, e.g. :: + import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping; Remix may add other source code providers in the future. diff --git a/docs/metadata.rst b/docs/metadata.rst index 0e6282ddb7d4..2e7ae6d43cc7 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -65,7 +65,7 @@ explanatory purposes. settings: { // Required for Solidity: Sorted list of remappings - remappings: [ ":g/dir" ], + remappings: [ ":g=/dir" ], // Optional: Optimizer settings. The fields "enabled" and "runs" are deprecated // and are only given for backwards-compatibility. optimizer: { @@ -84,6 +84,10 @@ explanatory purposes. yulDetails: {} } }, + metadata: { + // Reflects the setting used in the input json, defaults to false + useLiteralContent: true + } // Required for Solidity: File and name of the contract or library this // metadata is created for. compilationTarget: { @@ -121,9 +125,10 @@ Encoding of the Metadata Hash in the Bytecode Because we might support other ways to retrieve the metadata file in the future, the mapping ``{"bzzr0": }`` is stored -`CBOR `_-encoded. Since the beginning of that +`CBOR `_-encoded. Since the mapping might +contain more keys (see below) and the beginning of that encoding is not easy to find, its length is added in a two-byte big-endian -encoding. The current version of the Solidity compiler thus adds the following +encoding. The current version of the Solidity compiler usually adds the following to the end of the deployed bytecode:: 0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29 @@ -131,6 +136,14 @@ to the end of the deployed bytecode:: So in order to retrieve the data, the end of the deployed bytecode can be checked to match that pattern and use the Swarm hash to retrieve the file. +.. note:: + The CBOR mapping can also contain other keys, so it is better to fully + decode the data instead of relying on it starting with ``0xa165``. + For example, if any experimental features that affect code generation + are used, the mapping will also contain ``"experimental": true``. + Furthermore, we are planning to add the compiler version to the mapping + to ease source-verification and scanning for bugs. + .. note:: The compiler currently uses the "swarm version 0" hash of the metadata, but this might change in the future, so do not rely on this sequence @@ -154,7 +167,7 @@ Furthermore, the wallet can use the NatSpec user documentation to display a conf whenever they interact with the contract, together with requesting authorization for the transaction signature. -Additional information about Ethereum Natural Specification (NatSpec) can be found `here `_. +For additional information, read :doc:`Ethereum Natural Language Specification (NatSpec) format `. Usage for Source Code Verification ================================== @@ -167,3 +180,7 @@ bytecode is compared to the data of the creation transaction or ``CREATE`` opcod This automatically verifies the metadata since its hash is part of the bytecode. Excess data corresponds to the constructor input data, which should be decoded according to the interface and presented to the user. + +In the repository [source-verify](https://github.com/ethereum/source-verify) +([npm package](https://www.npmjs.com/package/source-verify)) you can see +example code that shows how to use this feature. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 95cddde36a13..ff80b01aed02 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -19,6 +19,8 @@ For contracts that use inheritance, the ordering of state variables is determine C3-linearized order of contracts starting with the most base-ward contract. If allowed by the above rules, state variables from different contracts do share the same storage slot. +The elements of structs and arrays are stored after each other, just as if they were given explicitly. + .. warning:: When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller @@ -36,7 +38,7 @@ by the above rules, state variables from different contracts do share the same s ``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the latter will take up three. - .. note:: +.. note:: The layout of state variables in storage is considered to be part of the external interface of Solidity due to the fact that storage pointers can be passed to libraries. This means that any change to the rules outlined in this section is considered a breaking change @@ -44,8 +46,6 @@ by the above rules, state variables from different contracts do share the same s being executed. -The elements of structs and arrays are stored after each other, just as if they were given explicitly. - Mappings and Dynamic Arrays =========================== diff --git a/docs/natspec-format.rst b/docs/natspec-format.rst new file mode 100644 index 000000000000..d55547050b91 --- /dev/null +++ b/docs/natspec-format.rst @@ -0,0 +1,199 @@ +.. _natspec: + +############## +NatSpec Format +############## + +Solidity contracts can use a special form of comments to provide rich +documentation for functions, return variables and more. This special form is +named the Ethereum Natural Language Specification Format (NatSpec). + +This documentation is segmented into developer-focused messages and end-user-facing +messages. These messages may be shown to the end user (the human) at the +time that they will interact with the contract (i.e. sign a transaction). + +It is recommended that Solidity contracts are fully annontated using NatSpec for +all public interfaces (everything in the ABI). + +NatSpec includes the formatting for comments that the smart contract author will +use, and which are understood by the Solidity compiler. Also detailed below is +output of the Solidity compiler, which extracts these comments into a machine-readable +format. + +.. _header-doc-example: + +Documentation Example +===================== + +Documentation is inserted above each ``class``, ``interface`` and +``function`` using the doxygen notation format. + +- For Solidity you may choose ``///`` for single or multi-line + comments, or ``/**`` and ending with ``*/``. + +- For Vyper, use ``"""`` indented to the inner contents with bare + comments. See `Vyper + documentation `__. + +The following example shows a contract and a function using all available tags. +Note: NatSpec currently does NOT apply to public state variables (see +`solidity#3418 `__), +even if they are declared public and therefore do affect the ABI. Note: +The Solidity compiler only interprets tags if they are external or +public. You are welcome to use similar comments for your internal and +private functions, but those will not be parsed. + +.. code:: solidity + + pragma solidity ^0.5.6; + + /// @title A simulator for trees + /// @author Larry A. Gardner + /// @notice You can use this contract for only the most basic simulation + /// @dev All function calls are currently implemented without side effects + contract Tree { + /// @author Mary A. Botanist + /// @notice Calculate tree age in years, rounded up, for live trees + /// @dev The Alexandr N. Tetearing algorithm could increase precision + /// @param rings The number of rings from dendrochronological sample + /// @return age in years, rounded up for partial years + function age(uint256 rings) external pure returns (uint256) { + return rings + 1; + } + } + +.. _header-tags: + +Tags +==== + +All tags are optional. The following table explains the purpose of each +NatSpec tag and where it may be used. As a special case, if no tags are +used then the Solidity compiler will interpret a `///` or `/**` comment +in the same way as if it were tagged with `@notice`. + +=========== =============================================================================== ============================= +Tag Context +=========== =============================================================================== ============================= +``@title`` A title that should describe the contract/interface contract, interface +``@author`` The name of the author contract, interface, function +``@notice`` Explain to an end user what this does contract, interface, function +``@dev`` Explain to a developer any extra details contract, interface, function +``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function +``@return`` Documents the return type of a contract's function function +=========== =============================================================================== ============================= + +If your function returns multiple values, like ``(int quotient, int remainder)`` +then use multiple ``@return`` statements in the same format as the +``@param`` statements. + +.. _header-dynamic: + +Dynamic expressions +------------------- + +The Solidity compiler will pass through NatSpec documentation from your Solidity +source code to the JSON output as described in this guide. The consumer of this +JSON output, for example the end-user client software, may present this to the end-user directly or it may apply some pre-processing. + +For example, some client software will render: + +.. code:: solidity + + /// @notice This function will multiply `a` by 7 + +to the end-user as: + +.. code:: text + + This function will multiply 10 by 7 + +if a function is being called and the input ``a`` is assigned a value of 7. + +Specifying these dynamic expressions is outside the scope of the Solidity +documentation and you may read more at +`the radspec project `__. + +.. _header-inheritance: + +Inheritance Notes +----------------- + +Currently it is undefined whether a contract with a function having no +NatSpec will inherit the NatSpec of a parent contract/interface for that +same function. + +.. _header-output: + +Documentation Output +==================== + +When parsed by the compiler, documentation such as the one from the +above example will produce two different JSON files. One is meant to be +consumed by the end user as a notice when a function is executed and the +other to be used by the developer. + +If the above contract is saved as ``ex1.sol`` then you can generate the +documentation using: + +.. code:: + + solc --userdoc --devdoc ex1.sol + +And the output is below. + +.. _header-user-doc: + +User Documentation +------------------ + +The above documentation will produce the following user documentation +JSON file as output: + +.. code:: + + { + "methods" : + { + "age(uint256)" : + { + "notice" : "Calculate tree age in years, rounded up, for live trees" + } + }, + "notice" : "You can use this contract for only the most basic simulation" + } + +Note that the key by which to find the methods is the function's +canonical signature as defined in the `Contract +ABI `__ and not simply the function's +name. + +.. _header-developer-doc: + +Developer Documentation +----------------------- + +Apart from the user documentation file, a developer documentation JSON +file should also be produced and should look like this: + +.. code:: + + { + "author" : "Larry A. Gardner", + "details" : "All function calls are currently implemented without side effects", + "methods" : + { + "age(uint256)" : + { + "author" : "Mary A. Botanist", + "details" : "The Alexandr N. Tetearing algorithm could increase precision", + "params" : + { + "rings" : "The number of rings from dendrochronological sample" + }, + "return" : "age in years, rounded up for partial years" + } + }, + "title" : "A simulator for trees" + } + diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index c3b04f08fe47..61609ec6d342 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -149,6 +149,9 @@ Sending and Receiving Ether into the sending contract or other state changes you might not have thought of. So it allows for great flexibility for honest users but also for malicious actors. +- Use the most precise units to represent the wei amount as possible, as you lose + any that is rounded due to a lack of precision. + - If you want to send Ether using ``address.transfer``, there are certain details to be aware of: 1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract. @@ -331,6 +334,8 @@ The more people examine a piece of code, the more issues are found. Asking people to review your code also helps as a cross-check to find out whether your code is easy to understand - a very important criterion for good smart contracts. +.. _formal_verification: + ******************* Formal Verification ******************* @@ -344,3 +349,185 @@ Note that formal verification itself can only help you understand the difference between what you did (the specification) and how you did it (the actual implementation). You still need to check whether the specification is what you wanted and that you did not miss any unintended effects of it. + +Solidity implements a formal verification approach based on SMT solving. The +SMTChecker module automatically tries to prove that the code satisfies the +specification given by ``require/assert`` statements. That is, it considers +``require`` statements as assumptions and tries to prove that the conditions +inside ``assert`` statements are always true. If an assertion failure is +found, a counterexample is given to the user, showing how the assertion can be +violated. + +The SMTChecker also checks automatically for arithmetic underflow/overflow, +trivial conditions and unreachable code. +It is currently an experimental feature, therefore in order to use it you need +to enable it via :ref:`a pragma directive`. + +The SMTChecker traverses the Solidity AST creating and collecting program constraints. +When it encounters a verification target, an SMT solver is invoked to determine the outcome. +If a check fails, the SMTChecker provides specific input values that lead to the failure. + +For more details on how the SMT encoding works internally, see the paper +`SMT-based Verification of Solidity Smart Contracts `_. + +Abstraction and False Positives +=============================== + +The SMTChecker implements abstractions in an incomplete and sound way: If a bug +is reported, it might be a false positive introduced by abstractions (due to +erasing knowledge or using a non-precise type). If it determines that a +verification target is safe, it is indeed safe, that is, there are no false +negatives (unless there is a bug in the SMTChecker). + +The SMT encoding tries to be as precise as possible, mapping Solidity types +and expressions to their closest `SMT-LIB `_ +representation, as shown in the table below. + ++-----------------------+--------------+-----------------------------+ +|Solidity type |SMT sort |Theories (quantifier-free) | ++=======================+==============+=============================+ +|Boolean |Bool |Bool | ++-----------------------+--------------+-----------------------------+ +|intN, uintN, address, |Integer |LIA, NIA | +|bytesN, enum | | | ++-----------------------+--------------+-----------------------------+ +|array, mapping |Array |Arrays | ++-----------------------+--------------+-----------------------------+ +|other types |Integer |LIA | ++-----------------------+--------------+-----------------------------+ + +Types that are not yet supported are abstracted by a single 256-bit unsigned integer, +where their unsupported operations are ignored. + +Function calls to the same contract (or base contracts) are inlined when +possible, that is, when their implementation is available. +Calls to functions in other contracts are not inlined even if their code is +available, since we cannot guarantee that the actual deployed code is the same. +Complex pure functions are abstracted by an uninterpreted function (UF) over +the arguments. + ++-----------------------------------+--------------------------------------+ +|Functions |SMT behavior | ++===================================+======================================+ +|``assert`` |Verification target | ++-----------------------------------+--------------------------------------+ +|``require`` |Assumption | ++-----------------------------------+--------------------------------------+ +|internal |Inline function call | ++-----------------------------------+--------------------------------------+ +|external |Inline function call | +| |Erase knowledge about state variables | +| |and local storage references | ++-----------------------------------+--------------------------------------+ +|``gasleft``, ``blockhash``, |Abstracted with UF | +|``keccak256``, ``ecrecover`` | | +|``ripemd160``, ``addmod``, | | +|``mulmod`` | | ++-----------------------------------+--------------------------------------+ +|pure functions without |Abstracted with UF | +|implementation (external or | | +|complex) | | ++-----------------------------------+--------------------------------------+ +|external functions without |Unsupported | +|implementation | | ++-----------------------------------+--------------------------------------+ +|others |Currently unsupported | ++-----------------------------------+--------------------------------------+ + +Using abstraction means loss of precise knowledge, but in many cases it does +not mean loss of proving power. + +:: + + pragma solidity >=0.5.0; + pragma experimental SMTChecker; + + contract Recover + { + function f( + bytes32 hash, + uint8 _v1, uint8 _v2, + bytes32 _r1, bytes32 _r2, + bytes32 _s1, bytes32 _s2 + ) public pure returns (address) { + address a1 = ecrecover(hash, _v1, _r1, _s1); + require(_v1 == _v2); + require(_r1 == _r2); + require(_s1 == _s2); + address a2 = ecrecover(hash, _v2, _r2, _s2); + assert(a1 == a2); + return a1; + } + } + +In the example above, the SMTChecker is not expressive enough to actually +compute ``ecrecover``, but by modelling the function calls as uninterpreted +functions we know that the return value is the same when called on equivalent +parameters. This is enough to prove that the assertion above is always true. + +Abstracting a function call with an UF can be done for functions known to be +deterministic, and can be easily done for pure functions. It is however +difficult to do this with general external functions, since they might depend +on state variables. + +External function calls also imply that any current knowledge that the +SMTChecker might have regarding mutable state variables needs to be erased to +guarantee no false negatives, since the called external function might direct +or indirectly call a function in the analyzed contract that changes state +variables. + +Reference Types and Aliasing +============================= + +Solidity implements aliasing for reference types with the same :ref:`data +location`. +That means one variable may be modified through a reference to the same data +area. +The SMTChecker does not keep track of which references refer to the same data. +This implies that whenever a local reference or state variable of reference +type is assigned, all knowledge regarding variables of the same type and data +location is erased. +If the type is nested, the knowledge removal also includes all the prefix base +types. + +:: + + pragma solidity >=0.5.0; + pragma experimental SMTChecker; + // This will not compile + contract Aliasing + { + uint[] array; + function f( + uint[] memory a, + uint[] memory b, + uint[][] memory c, + uint[] storage d + ) internal view { + require(array[0] == 42); + require(a[0] == 2); + require(c[0][0] == 2); + require(d[0] == 2); + b[0] = 1; + // Erasing knowledge about memory references should not + // erase knowledge about state variables. + assert(array[0] == 42); + // Fails because `a == b` is possible. + assert(a[0] == 2); + // Fails because `c[i] == b` is possible. + assert(c[0][0] == 2); + assert(d[0] == 2); + assert(b[0] == 1); + } + } + +After the assignment to ``b[0]``, we need to clear knowledge about ``a`` since +it has the same type (``uint[]``) and data location (memory). We also need to +clear knowledge about ``c``, since its base type is also a ``uint[]`` located +in memory. This implies that some ``c[i]`` could refer to the same data as +``b`` or ``a``. + +Notice that we do not clear knowledge about ``array`` and ``d`` because they +are located in storage, even though they also have type ``uint[]``. However, +if ``d`` was assigned, we would need to clear knowledge about ``array`` and +vice-versa. diff --git a/docs/style-guide.rst b/docs/style-guide.rst index d754f1ce7503..7bc75a306ff8 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -1088,8 +1088,6 @@ Avoiding Naming Collisions This convention is suggested when the desired name collides with that of a built-in or otherwise reserved name. -.. _natspec: - ******* NatSpec ******* @@ -1126,25 +1124,6 @@ added looks like the one below:: } } -Natspec uses doxygen style tags with some special meaning. -If no tag is used, then the comment applies to ``@notice``. -The ``@notice`` tag is the main NatSpec tag and its audience is -users of the contract who have never seen the source code, so it should make -as little assumptions about the inner details as possible. -All tags are optional. - -+-------------+-------------------------------------------+-------------------------------+ -| Tag | Description | Context | -+=============+===========================================+===============================+ -| ``@title`` | A title that describes the contract | contract, interface | -+-------------+-------------------------------------------+-------------------------------+ -| ``@author`` | The name of the author | contract, interface, function | -+-------------+-------------------------------------------+-------------------------------+ -| ``@notice`` | Explanation of functionality | contract, interface, function | -+-------------+-------------------------------------------+-------------------------------+ -| ``@dev`` | Any extra details | contract, interface, function | -+-------------+-------------------------------------------+-------------------------------+ -| ``@param`` | Parameter type followed by parameter name | function | -+-------------+-------------------------------------------+-------------------------------+ -| ``@return`` | The return value of a contract's function | function | -+-------------+-------------------------------------------+-------------------------------+ +It is recommended that Solidity contracts are fully annontated using `NatSpec `_ for all public interfaces (everything in the ABI). + +Please see the sectian about `NatSpec `_ for a detailed explanation. \ No newline at end of file diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index eb7e38d512bc..befb97dbb5f3 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -188,8 +188,12 @@ Mathematical and Cryptographic Functions .. warning:: - If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without requiring - knowledge of the corresponding private key. This is usually not a problem unless you require signatures to be unique or + If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without + requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed + for _transaction_ signatures (see `EIP-2 `_), but + the ecrecover function remained unchanged. + + This is usually not a problem unless you require signatures to be unique or use them to identify items. OpenZeppelin have a `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. .. note:: diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 4bf8fa73fd7a..55e22dfec932 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -187,7 +187,7 @@ Input Description "settings": { // Optional: Sorted list of remappings - "remappings": [ ":g/dir" ], + "remappings": [ ":g=/dir" ], // Optional: Optimizer settings "optimizer": { // disabled by default diff --git a/docs/yul.rst b/docs/yul.rst index d13af263f8a5..02543296f335 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -6,16 +6,14 @@ Yul .. index:: ! assembly, ! asm, ! evmasm, ! yul, julia, iulia -Yul (previously also called JULIA or IULIA) is an intermediate language that can -compile to various different backends -(EVM 1.0, EVM 1.5 and eWASM are planned). -Because of that, it is designed to be a usable common denominator of all three -platforms. -It can already be used for "inline assembly" inside Solidity and -future versions of the Solidity compiler will even use Yul as intermediate -language. It should also be easy to build high-level optimizer stages for Yul. - -In its flavour of inline-assembly, Yul can be used as a language setting +Yul (previously also called JULIA or IULIA) is an intermediate language that can be +compiled to bytecode for different backends. + +Support for EVM 1.0, EVM 1.5 and eWASM is planned, and it is designed to be a usable common denominator of all three +platforms. It can already be used for "inline assembly" inside Solidity and future versions of the Solidity compiler +will use Yul as an intermediate language. Yul is a good target for high-level optimisation stages that can benefit all target platforms equally. + +With the "inline assembly" flavour, Yul can be used as a language setting for the :ref:`standard-json interface `: :: @@ -29,15 +27,12 @@ for the :ref:`standard-json interface `: } } -Furthermore, the commandline interface can be switched to Yul mode -using ``solc --strict-assembly``. +And on the command line interface with the ``--strict-assembly`` parameter. -.. note:: +.. warning:: - Note that the flavour used for "inline assembly" does not have types - (everything is ``u256``) and the built-in functions are identical - to the EVM opcodes. Please resort to the inline assembly documentation - for details. + Yul is in active development and bytecode generation is fully implemented only for untyped Yul (everything is ``u256``) + and with EVM 1.0 as target, :ref:`EVM opcodes ` are used as built-in functions. The core components of Yul are functions, blocks, variables, literals, for-loops, if-statements, switch-statements, expressions and assignments to variables. @@ -128,7 +123,7 @@ Grammar:: 'break' | 'continue' FunctionCall = Identifier '(' ( Expression ( ',' Expression )* )? ')' - Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* + Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]* IdentifierList = Identifier ( ',' Identifier)* TypeName = Identifier | BuiltinTypeName BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) @@ -172,6 +167,7 @@ The ``continue`` and ``break`` statements can only be used inside loop bodies and have to be in the same function as the loop (or both have to be at the top level). The condition part of the for-loop has to evaluate to exactly one value. +Functions cannot be defined inside for loop init blocks. Literals cannot be larger than the their type. The largest type defined is 256-bit wide. diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h index 729ffb05d201..4e427fc7e342 100644 --- a/libdevcore/Assertions.h +++ b/libdevcore/Assertions.h @@ -24,7 +24,7 @@ #pragma once -#include "Exceptions.h" +#include namespace dev { diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 29b31ebc1823..fa236b189fb3 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -19,7 +19,11 @@ * @date 2014 */ -#include "CommonIO.h" +#include +#include + +#include + #include #include #include @@ -29,8 +33,6 @@ #include #include #endif -#include -#include "Assertions.h" using namespace std; using namespace dev; diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index 0d8aca79ba5d..61fa230aaf1c 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -23,10 +23,10 @@ #pragma once +#include +#include #include #include -#include -#include "Common.h" namespace dev { diff --git a/libdevcore/JSON.cpp b/libdevcore/JSON.cpp index 086fae50097c..29627d98f667 100644 --- a/libdevcore/JSON.cpp +++ b/libdevcore/JSON.cpp @@ -19,9 +19,9 @@ * @date 2018 */ -#include "JSON.h" +#include -#include "CommonIO.h" +#include #include #include diff --git a/libdevcore/StringUtils.cpp b/libdevcore/StringUtils.cpp index 196ac8f7d763..e14f704e900e 100644 --- a/libdevcore/StringUtils.cpp +++ b/libdevcore/StringUtils.cpp @@ -21,7 +21,7 @@ * String routines */ -#include "StringUtils.h" +#include #include #include #include diff --git a/libdevcore/UTF8.cpp b/libdevcore/UTF8.cpp index c61fea8f7b5b..a9fcf0ce3c94 100644 --- a/libdevcore/UTF8.cpp +++ b/libdevcore/UTF8.cpp @@ -21,12 +21,10 @@ * UTF-8 related helpers */ -#include "UTF8.h" - +#include namespace dev { - namespace { @@ -77,6 +75,8 @@ bool isWellFormed(unsigned char byte1, unsigned char byte2) return false; } +} + bool validateUTF8(unsigned char const* _input, size_t _length, size_t& _invalidPosition) { bool valid = true; @@ -133,8 +133,6 @@ bool validateUTF8(unsigned char const* _input, size_t _length, size_t& _invalidP return false; } -} - bool validateUTF8(std::string const& _input, size_t& _invalidPosition) { return validateUTF8(reinterpret_cast(_input.c_str()), _input.length(), _invalidPosition); diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 6b9ece01ef10..494b2ed93270 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include "Assembly.h" +#include #include #include diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index a53c924207cb..ebf032ac1531 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -68,10 +68,10 @@ class Assembly void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); } - AssemblyItem appendJump() { auto ret = append(newPushTag()); append(solidity::Instruction::JUMP); return ret; } - AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(solidity::Instruction::JUMPI); return ret; } - AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMP); return ret; } - AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMPI); return ret; } + AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } + AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } + AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } + AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMPI); return ret; } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the pushsub assembly item. diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 0043f76b639c..61355530e666 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -231,7 +231,7 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) { case Operation: _out << " " << instructionInfo(_item.instruction()).name; - if (_item.instruction() == solidity::Instruction::JUMP || _item.instruction() == solidity::Instruction::JUMPI) + if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI) _out << "\t" << _item.getJumpTypeAsString(); break; case Push: diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index d21be1991bbe..95b09d47f1c0 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -21,14 +21,13 @@ #pragma once -#include -#include -#include -#include #include +#include #include -#include "Exceptions.h" -using namespace dev::solidity; +#include +#include +#include +#include namespace dev { @@ -59,7 +58,7 @@ class AssemblyItem AssemblyItem(u256 _push, langutil::SourceLocation _location = langutil::SourceLocation()): AssemblyItem(Push, std::move(_push), std::move(_location)) { } - AssemblyItem(solidity::Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()): + AssemblyItem(Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()): m_type(Operation), m_instruction(_i), m_location(std::move(_location)) diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index b3ee0fe50008..6dd9da6b91f5 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -19,17 +19,18 @@ * @date 2014 */ -#include "./Instruction.h" +#include -#include -#include #include #include +#include +#include + using namespace std; using namespace dev; -using namespace dev::solidity; +using namespace dev::eth; -std::map const dev::solidity::c_instructions = +std::map const dev::eth::c_instructions = { { "STOP", Instruction::STOP }, { "ADD", Instruction::ADD }, @@ -317,7 +318,7 @@ static std::map const c_instructionInfo = { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Special } } }; -void dev::solidity::eachInstruction( +void dev::eth::eachInstruction( bytes const& _mem, function const& _onInstruction ) @@ -346,7 +347,7 @@ void dev::solidity::eachInstruction( } } -string dev::solidity::disassemble(bytes const& _mem) +string dev::eth::disassemble(bytes const& _mem) { stringstream ret; eachInstruction(_mem, [&](Instruction _instr, u256 const& _data) { @@ -363,7 +364,7 @@ string dev::solidity::disassemble(bytes const& _mem) return ret.str(); } -InstructionInfo dev::solidity::instructionInfo(Instruction _inst) +InstructionInfo dev::eth::instructionInfo(Instruction _inst) { try { @@ -375,7 +376,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst) } } -bool dev::solidity::isValidInstruction(Instruction _inst) +bool dev::eth::isValidInstruction(Instruction _inst) { return !!c_instructionInfo.count(_inst); } diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 539a83b01a16..9a0a5d4e34f4 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -21,14 +21,14 @@ #pragma once -#include +#include #include #include -#include "Exceptions.h" +#include namespace dev { -namespace solidity +namespace eth { DEV_SIMPLE_EXCEPTION(InvalidDeposit); diff --git a/libevmasm/JumpdestRemover.cpp b/libevmasm/JumpdestRemover.cpp index 60493a99e1b1..457853c53b66 100644 --- a/libevmasm/JumpdestRemover.cpp +++ b/libevmasm/JumpdestRemover.cpp @@ -19,7 +19,7 @@ * Removes unused JUMPDESTs. */ -#include "JumpdestRemover.h" +#include #include diff --git a/libevmasm/KnownState.cpp b/libevmasm/KnownState.cpp index d6cc5ddd0818..73db60560c25 100644 --- a/libevmasm/KnownState.cpp +++ b/libevmasm/KnownState.cpp @@ -21,10 +21,11 @@ * Contains knowledge about the state of the virtual machine at a specific instruction. */ -#include "KnownState.h" -#include -#include +#include #include +#include + +#include using namespace std; using namespace dev; diff --git a/libevmasm/PathGasMeter.cpp b/libevmasm/PathGasMeter.cpp index 0b199806c724..761defdc5cc4 100644 --- a/libevmasm/PathGasMeter.cpp +++ b/libevmasm/PathGasMeter.cpp @@ -19,7 +19,7 @@ * @date 2015 */ -#include "PathGasMeter.h" +#include #include #include diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 2279f24a1c01..3e2c45b6f2c5 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -19,7 +19,7 @@ * Performs local optimising code changes to assembly. */ -#include "PeepholeOptimiser.h" +#include #include #include diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index c091fc21afa7..eecef224ede7 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -21,19 +21,20 @@ #pragma once -#include -#include - -#include #include #include #include +#include + +#include +#include + namespace dev { -namespace solidity +namespace eth { template S divWorkaround(S const& _a, S const& _b) @@ -215,7 +216,7 @@ std::vector> simplificationRuleListPart4( template std::vector> simplificationRuleListPart5( - Pattern, + Pattern A, Pattern, Pattern, Pattern X, @@ -235,6 +236,22 @@ std::vector> simplificationRuleListPart5( }); } + // Replace SHL >=256, X with 0 + rules.push_back({ + {Instruction::SHL, {A, X}}, + [=]() -> Pattern { return u256(0); }, + true, + [=]() { return A.d() >= 256; } + }); + + // Replace SHR >=256, X with 0 + rules.push_back({ + {Instruction::SHR, {A, X}}, + [=]() -> Pattern { return u256(0); }, + true, + [=]() { return A.d() >= 256; } + }); + for (auto const& op: std::vector{ Instruction::ADDRESS, Instruction::CALLER, @@ -374,6 +391,30 @@ std::vector> simplificationRuleListPart7( false }); + + std::function feasibilityFunction = [=]() { + if (B.d() > 256) + return false; + unsigned bAsUint = static_cast(B.d()); + return (A.d() & (u256(-1) >> bAsUint)) == (u256(-1) >> bAsUint); + }; + + rules.push_back({ + // AND(A, SHR(B, X)) -> A & ((2^256-1) >> B) == ((2^256-1) >> B) + {Instruction::AND, {A, {Instruction::SHR, {B, X}}}}, + [=]() -> Pattern { return {Instruction::SHR, {B, X}}; }, + false, + feasibilityFunction + }); + + rules.push_back({ + // AND(SHR(B, X), A) -> ((2^256-1) >> B) & A == ((2^256-1) >> B) + {Instruction::AND, {{Instruction::SHR, {B, X}}, A}}, + [=]() -> Pattern { return {Instruction::SHR, {B, X}}; }, + false, + feasibilityFunction + }); + return rules; } diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 2a24a27eaf75..ab539e526dda 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -96,14 +96,14 @@ bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) { if (_item.type() != Operation) return false; - return solidity::isDupInstruction(_item.instruction()); + return dev::eth::isDupInstruction(_item.instruction()); } bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) { if (_item.type() != Operation) return false; - return solidity::isSwapInstruction(_item.instruction()); + return dev::eth::isSwapInstruction(_item.instruction()); } bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) @@ -132,6 +132,22 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) } } +bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + switch (_item.instruction()) + { + case Instruction::RETURN: + case Instruction::SELFDESTRUCT: + case Instruction::STOP: + case Instruction::INVALID: + case Instruction::REVERT: + return true; + default: + return false; + } +} bool SemanticInformation::isDeterministic(AssemblyItem const& _item) { diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h index 8bdc70becc88..b4cb68387d6d 100644 --- a/libevmasm/SemanticInformation.h +++ b/libevmasm/SemanticInformation.h @@ -47,19 +47,20 @@ struct SemanticInformation static bool isSwapInstruction(AssemblyItem const& _item); static bool isJumpInstruction(AssemblyItem const& _item); static bool altersControlFlow(AssemblyItem const& _item); + static bool terminatesControlFlow(AssemblyItem const& _item); /// @returns false if the value put on the stack by _item depends on anything else than /// the information in the current block header, memory, storage or stack. static bool isDeterministic(AssemblyItem const& _item); /// @returns true if the instruction can be moved or copied (together with its arguments) /// without altering the semantics. This means it cannot depend on storage or memory, /// cannot have any side-effects, but it can depend on a call-constant state of the blockchain. - static bool movable(solidity::Instruction _instruction); + static bool movable(Instruction _instruction); /// @returns true if the given instruction modifies memory. - static bool invalidatesMemory(solidity::Instruction _instruction); + static bool invalidatesMemory(Instruction _instruction); /// @returns true if the given instruction modifies storage (even indirectly). - static bool invalidatesStorage(solidity::Instruction _instruction); - static bool invalidInPureFunctions(solidity::Instruction _instruction); - static bool invalidInViewFunctions(solidity::Instruction _instruction); + static bool invalidatesStorage(Instruction _instruction); + static bool invalidInPureFunctions(Instruction _instruction); + static bool invalidInViewFunctions(Instruction _instruction); }; } diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index fa1d50691f56..5c752545fbab 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -24,7 +24,7 @@ namespace dev { -namespace solidity +namespace eth { /** diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt index 1c3d5a05e8f9..4cac9d8bb770 100644 --- a/liblangutil/CMakeLists.txt +++ b/liblangutil/CMakeLists.txt @@ -12,6 +12,8 @@ set(sources ParserBase.h Scanner.cpp Scanner.h + SemVerHandler.cpp + SemVerHandler.h SourceLocation.h SourceReferenceExtractor.cpp SourceReferenceExtractor.h diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 014191b249fa..852a92e9bf9c 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -53,16 +53,15 @@ #include #include #include +#include #include #include #include using namespace std; +using namespace langutil; -namespace langutil -{ - -std::string to_string(ScannerError _errorCode) +string langutil::to_string(ScannerError _errorCode) { switch (_errorCode) { @@ -83,15 +82,19 @@ std::string to_string(ScannerError _errorCode) } } -std::ostream& operator<<(std::ostream& os, ScannerError _errorCode) + +ostream& langutil::operator<<(ostream& os, ScannerError _errorCode) { - os << to_string(_errorCode); - return os; + return os << to_string(_errorCode); } +namespace langutil +{ + /// Scoped helper for literal recording. Automatically drops the literal /// if aborting the scanning before it's complete. -enum LiteralType { +enum LiteralType +{ LITERAL_TYPE_STRING, LITERAL_TYPE_NUMBER, // not really different from string type in behaviour LITERAL_TYPE_COMMENT @@ -100,9 +103,10 @@ enum LiteralType { class LiteralScope { public: - explicit LiteralScope(Scanner* _self, enum LiteralType _type): m_type(_type) - , m_scanner(_self) - , m_complete(false) + explicit LiteralScope(Scanner* _self, enum LiteralType _type): + m_type(_type), + m_scanner(_self), + m_complete(false) { if (_type == LITERAL_TYPE_COMMENT) m_scanner->m_nextSkippedComment.literal.clear(); @@ -125,8 +129,9 @@ class LiteralScope enum LiteralType m_type; Scanner* m_scanner; bool m_complete; -}; // end of LiteralScope class +}; +} void Scanner::reset(CharStream _source) { @@ -134,20 +139,27 @@ void Scanner::reset(CharStream _source) reset(); } -void Scanner::reset(std::shared_ptr _source) +void Scanner::reset(shared_ptr _source) { solAssert(_source.get() != nullptr, "You MUST provide a CharStream when resetting."); - m_source = _source; + m_source = std::move(_source); reset(); } void Scanner::reset() { m_source->reset(); + m_supportPeriodInIdentifier = false; m_char = m_source->get(); skipWhitespace(); - scanToken(); next(); + next(); +} + +void Scanner::supportPeriodInIdentifier(bool _value) +{ + m_supportPeriodInIdentifier = _value; + rescan(); } bool Scanner::scanHexByte(char& o_scannedByte) @@ -168,7 +180,7 @@ bool Scanner::scanHexByte(char& o_scannedByte) return true; } -bool Scanner::scanUnicode(unsigned & o_codepoint) +boost::optional Scanner::scanUnicode() { unsigned x = 0; for (int i = 0; i < 4; i++) @@ -177,13 +189,12 @@ bool Scanner::scanUnicode(unsigned & o_codepoint) if (d < 0) { rollback(i); - return false; + return {}; } x = x * 16 + d; advance(); } - o_codepoint = x; - return true; + return x; } // This supports codepoints between 0000 and FFFF. @@ -204,6 +215,18 @@ void Scanner::addUnicodeAsUTF8(unsigned codepoint) } } +void Scanner::rescan() +{ + size_t rollbackTo = 0; + if (m_skippedComment.literal.empty()) + rollbackTo = m_currentToken.location.start; + else + rollbackTo = m_skippedComment.location.start; + m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo); + next(); + next(); +} + // Ensure that tokens can be stored in a byte. BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100); @@ -664,10 +687,10 @@ bool Scanner::scanEscape() break; case 'u': { - unsigned codepoint; - if (!scanUnicode(codepoint)) + if (boost::optional codepoint = scanUnicode()) + addUnicodeAsUTF8(*codepoint); + else return false; - addUnicodeAsUTF8(codepoint); return true; } case 'x': @@ -754,7 +777,8 @@ void Scanner::scanDecimalDigits() return; // May continue with decimal digit or underscore for grouping. - do addLiteralCharAndAdvance(); + do + addLiteralCharAndAdvance(); while (!m_source->isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_')); // Defer further validation of underscore to SyntaxChecker. @@ -860,11 +884,8 @@ tuple Scanner::scanIdentifierOrKeyword() LiteralScope literal(this, LITERAL_TYPE_STRING); addLiteralCharAndAdvance(); // Scan the rest of the identifier characters. - while (isIdentifierPart(m_char)) //get full literal + while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier)) addLiteralCharAndAdvance(); literal.complete(); return TokenTraits::fromIdentifierOrKeyword(m_nextToken.literal); } - - -} diff --git a/liblangutil/Scanner.h b/liblangutil/Scanner.h index 965328cbb0f5..9a4a170a6535 100644 --- a/liblangutil/Scanner.h +++ b/liblangutil/Scanner.h @@ -103,6 +103,10 @@ class Scanner /// Resets scanner to the start of input. void reset(); + /// Enables or disables support for period in identifier. + /// This re-scans the current token and comment literal and thus invalidates it. + void supportPeriodInIdentifier(bool _value); + /// @returns the next token and advances input Token next(); @@ -191,6 +195,8 @@ class Scanner bool advance() { m_char = m_source->advanceAndGet(); return !m_source->isPastEndOfInput(); } void rollback(int _amount) { m_char = m_source->rollback(_amount); } + /// Rolls back to the start of the current token and re-runs the scanner. + void rescan(); inline Token selectErrorToken(ScannerError _err) { advance(); return setError(_err); } inline Token selectToken(Token _tok) { advance(); return _tok; } @@ -198,7 +204,7 @@ class Scanner inline Token selectToken(char _next, Token _then, Token _else); bool scanHexByte(char& o_scannedByte); - bool scanUnicode(unsigned& o_codepoint); + boost::optional scanUnicode(); /// Scans a single Solidity token. void scanToken(); @@ -233,6 +239,8 @@ class Scanner int sourcePos() const { return m_source->position(); } bool isSourcePastEndOfInput() const { return m_source->isPastEndOfInput(); } + bool m_supportPeriodInIdentifier = false; + TokenDesc m_skippedComment; // desc for current skipped comment TokenDesc m_nextSkippedComment; // desc for next skipped comment diff --git a/libsolidity/analysis/SemVerHandler.cpp b/liblangutil/SemVerHandler.cpp similarity index 98% rename from libsolidity/analysis/SemVerHandler.cpp rename to liblangutil/SemVerHandler.cpp index 7c6ba91fc3c3..378923420a5b 100644 --- a/libsolidity/analysis/SemVerHandler.cpp +++ b/liblangutil/SemVerHandler.cpp @@ -20,13 +20,13 @@ * Utilities to handle semantic versioning. */ -#include +#include #include using namespace std; using namespace dev; -using namespace dev::solidity; +using namespace langutil; SemVerVersion::SemVerVersion(string const& _versionString) { diff --git a/libsolidity/analysis/SemVerHandler.h b/liblangutil/SemVerHandler.h similarity index 97% rename from libsolidity/analysis/SemVerHandler.h rename to liblangutil/SemVerHandler.h index 8018561210c0..09e6c6f4a1c8 100644 --- a/libsolidity/analysis/SemVerHandler.h +++ b/liblangutil/SemVerHandler.h @@ -22,13 +22,11 @@ #pragma once -#include +#include #include #include -namespace dev -{ -namespace solidity +namespace langutil { class SemVerError: dev::Exception @@ -109,4 +107,3 @@ class SemVerMatchExpressionParser }; } -} diff --git a/liblangutil/SourceReferenceFormatter.cpp b/liblangutil/SourceReferenceFormatter.cpp index 4bc47a656779..01c1dd5ffe08 100644 --- a/liblangutil/SourceReferenceFormatter.cpp +++ b/liblangutil/SourceReferenceFormatter.cpp @@ -70,9 +70,17 @@ void SourceReferenceFormatter::printSourceName(SourceReference const& _ref) m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": "; } -void SourceReferenceFormatter::printExceptionInformation(dev::Exception const& _error, std::string const& _category) +void SourceReferenceFormatter::printExceptionInformation(dev::Exception const& _exception, std::string const& _category) { - printExceptionInformation(SourceReferenceExtractor::extract(_error, _category)); + printExceptionInformation(SourceReferenceExtractor::extract(_exception, _category)); +} + +void SourceReferenceFormatter::printErrorInformation(Error const& _error) +{ + printExceptionInformation( + _error, + (_error.type() == Error::Type::Warning) ? "Warning" : "Error" + ); } void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg) diff --git a/liblangutil/SourceReferenceFormatter.h b/liblangutil/SourceReferenceFormatter.h index c01b360eb2f2..fee223088230 100644 --- a/liblangutil/SourceReferenceFormatter.h +++ b/liblangutil/SourceReferenceFormatter.h @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace dev @@ -51,7 +52,16 @@ class SourceReferenceFormatter virtual void printExceptionInformation(SourceReferenceExtractor::Message const& _msg); virtual void printSourceLocation(SourceLocation const* _location); - virtual void printExceptionInformation(dev::Exception const& _error, std::string const& _category); + virtual void printExceptionInformation(dev::Exception const& _exception, std::string const& _category); + virtual void printErrorInformation(Error const& _error); + + static std::string formatErrorInformation(Error const& _error) + { + return formatExceptionInformation( + _error, + (_error.type() == Error::Type::Warning) ? "Warning" : "Error" + ); + } static std::string formatExceptionInformation( dev::Exception const& _exception, diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index b9cb96151690..714515d969cc 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -19,7 +19,11 @@ * @date 2014 */ -#include "CodeFragment.h" +#include +#include +#include +#include +#include #include @@ -34,13 +38,10 @@ #pragma GCC diagnostic pop #endif // defined(__GNUC__) -#include -#include -#include "CompilerState.h" -#include "Parser.h" using namespace std; using namespace dev; +using namespace dev::eth; using namespace dev::lll; void CodeFragment::finalise(CompilerState const& _cs) @@ -66,7 +67,7 @@ bool validAssemblyInstruction(string us) auto it = c_instructions.find(us); return !( it == c_instructions.end() || - solidity::isPushInstruction(it->second) + isPushInstruction(it->second) ); } @@ -76,10 +77,10 @@ bool validFunctionalInstruction(string us) auto it = c_instructions.find(us); return !( it == c_instructions.end() || - solidity::isPushInstruction(it->second) || - solidity::isDupInstruction(it->second) || - solidity::isSwapInstruction(it->second) || - it->second == solidity::Instruction::JUMPDEST + isPushInstruction(it->second) || + isDupInstruction(it->second) || + isSwapInstruction(it->second) || + it->second == Instruction::JUMPDEST ); } } @@ -255,7 +256,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) string contents = m_readFile(fileName); if (contents.empty()) error(std::string("File not found (or empty): ") + fileName); - m_asm.append(CodeFragment::compile(contents, _s, m_readFile).m_asm); + m_asm.append(CodeFragment::compile(std::move(contents), _s, m_readFile).m_asm); } else if (us == "SET") { @@ -744,11 +745,11 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) } } -CodeFragment CodeFragment::compile(string const& _src, CompilerState& _s, ReadCallback const& _readFile) +CodeFragment CodeFragment::compile(string _src, CompilerState& _s, ReadCallback const& _readFile) { CodeFragment ret; sp::utree o; - parseTreeLLL(_src, o); + parseTreeLLL(std::move(_src), o); if (!o.empty()) ret = CodeFragment(o, _s, _readFile); _s.treesToKill.push_back(o); diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h index f59a5d6fd9eb..cbdf867667d9 100644 --- a/liblll/CodeFragment.h +++ b/liblll/CodeFragment.h @@ -21,10 +21,10 @@ #pragma once -#include +#include #include #include -#include "Exceptions.h" +#include namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; @@ -44,7 +44,7 @@ class CodeFragment CodeFragment() = default; CodeFragment(sp::utree const& _t, CompilerState& _s, ReadCallback const& _readFile, bool _allowASM = false); - static CodeFragment compile(std::string const& _src, CompilerState& _s, ReadCallback const& _readFile); + static CodeFragment compile(std::string _src, CompilerState& _s, ReadCallback const& _readFile); /// Consolidates data and compiles code. eth::Assembly& assembly(CompilerState const& _cs) { finalise(_cs); return m_asm; } @@ -69,4 +69,3 @@ static CodeFragment const NullCodeFragment; } } - diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index 03190347ad52..2bea6ca6a747 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -28,13 +28,13 @@ using namespace std; using namespace dev; using namespace dev::lll; -bytes dev::lll::compileLLL(string const& _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) +bytes dev::lll::compileLLL(string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) { try { CompilerState cs; cs.populateStandard(); - auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); + auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs); if (_opt) assembly = assembly.optimise(true, _evmVersion, true, 200); bytes ret = assembly.assemble().bytecode; @@ -66,13 +66,13 @@ bytes dev::lll::compileLLL(string const& _src, langutil::EVMVersion _evmVersion, return bytes(); } -std::string dev::lll::compileLLLToAsm(std::string const& _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) +std::string dev::lll::compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) { try { CompilerState cs; cs.populateStandard(); - auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); + auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs); if (_opt) assembly = assembly.optimise(true, _evmVersion, true, 200); string ret = assembly.assemblyString(); @@ -104,13 +104,13 @@ std::string dev::lll::compileLLLToAsm(std::string const& _src, langutil::EVMVers return string(); } -string dev::lll::parseLLL(string const& _src) +string dev::lll::parseLLL(string _src) { sp::utree o; try { - parseTreeLLL(_src, o); + parseTreeLLL(std::move(_src), o); } catch (...) { diff --git a/liblll/Compiler.h b/liblll/Compiler.h index a8a7b9988a12..14b10f750db2 100644 --- a/liblll/Compiler.h +++ b/liblll/Compiler.h @@ -35,9 +35,9 @@ namespace lll using ReadCallback = std::function; -std::string parseLLL(std::string const& _src); -std::string compileLLLToAsm(std::string const& _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); -bytes compileLLL(std::string const& _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); +std::string parseLLL(std::string _src); +std::string compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); +bytes compileLLL(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); } } diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp index dbd6e91cd5c5..2ae2b0af322e 100644 --- a/liblll/CompilerState.cpp +++ b/liblll/CompilerState.cpp @@ -19,8 +19,8 @@ * @date 2014 */ -#include "CompilerState.h" -#include "CodeFragment.h" +#include +#include using namespace std; using namespace dev; diff --git a/liblll/CompilerState.h b/liblll/CompilerState.h index 0fa50288433f..d7ba38c33eaf 100644 --- a/liblll/CompilerState.h +++ b/liblll/CompilerState.h @@ -21,8 +21,8 @@ #pragma once +#include #include -#include "CodeFragment.h" namespace dev { diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp index 854aeecc803d..3b68bc2daea8 100644 --- a/liblll/Parser.cpp +++ b/liblll/Parser.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include "Parser.h" +#include #if _MSC_VER #pragma warning(disable:4348) diff --git a/liblll/Parser.h b/liblll/Parser.h index cfbec4181f31..37b39c6b69c0 100644 --- a/liblll/Parser.h +++ b/liblll/Parser.h @@ -21,10 +21,10 @@ #pragma once +#include +#include #include #include -#include -#include "Exceptions.h" namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; diff --git a/libsolc/libsolc.cpp b/libsolc/libsolc.cpp index f20b50d46047..89859458c13a 100644 --- a/libsolc/libsolc.cpp +++ b/libsolc/libsolc.cpp @@ -72,10 +72,10 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = n return readCallback; } -string compile(string const& _input, CStyleReadFileCallback _readCallback = nullptr) +string compile(string _input, CStyleReadFileCallback _readCallback = nullptr) { StandardCompiler compiler(wrapReadCallback(_readCallback)); - return compiler.compile(_input); + return compiler.compile(std::move(_input)); } } diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 39ea693502ce..88c1a31b1e84 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -22,8 +22,6 @@ set(sources analysis/PostTypeChecker.h analysis/ReferencesResolver.cpp analysis/ReferencesResolver.h - analysis/SemVerHandler.cpp - analysis/SemVerHandler.h analysis/StaticAnalyzer.cpp analysis/StaticAnalyzer.h analysis/SyntaxChecker.cpp @@ -47,6 +45,8 @@ set(sources ast/ExperimentalFeatures.h ast/Types.cpp ast/Types.h + ast/TypeProvider.cpp + ast/TypeProvider.h codegen/ABIFunctions.cpp codegen/ABIFunctions.h codegen/ArrayUtils.cpp @@ -67,6 +67,14 @@ set(sources codegen/MultiUseYulFunctionCollector.cpp codegen/YulUtilFunctions.h codegen/YulUtilFunctions.cpp + codegen/ir/IRGenerator.cpp + codegen/ir/IRGenerator.h + codegen/ir/IRGeneratorForStatements.cpp + codegen/ir/IRGeneratorForStatements.h + codegen/ir/IRGenerationContext.cpp + codegen/ir/IRGenerationContext.h + formal/EncodingContext.cpp + formal/EncodingContext.h formal/SMTChecker.cpp formal/SMTChecker.h formal/SMTLib2Interface.cpp diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index e637795ad936..c63d1b127dec 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -23,6 +23,7 @@ #include #include +#include #include using namespace std; @@ -56,7 +57,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) setType( _operation, TokenTraits::isCompareOp(_operation.getOperator()) ? - make_shared() : + TypeProvider::boolean() : commonType ); } @@ -64,7 +65,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) void ConstantEvaluator::endVisit(Literal const& _literal) { - setType(_literal, Type::forLiteral(_literal)); + setType(_literal, TypeProvider::forLiteral(_literal)); } void ConstantEvaluator::endVisit(Identifier const& _identifier) diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index 1c6383b8bfda..38c62da7eba1 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -244,13 +245,13 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con { for (VariableDeclaration const* v: contract->stateVariables()) if (v->isPartOfExternalInterface()) - registerFunction(*v, make_shared(*v), true); + registerFunction(*v, TypeProvider::function(*v), true); for (FunctionDefinition const* function: contract->definedFunctions()) if (!function->isConstructor()) registerFunction( *function, - make_shared(*function)->asCallableFunction(false), + TypeProvider::function(*function)->asCallableFunction(false), function->isImplemented() ); } @@ -407,7 +408,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c for (FunctionDefinition const* f: contract->definedFunctions()) if (f->isPartOfExternalInterface()) { - auto functionType = make_shared(*f); + auto functionType = TypeProvider::function(*f); // under non error circumstances this should be true if (functionType->interfaceFunctionType()) externalDeclarations[functionType->externalSignature()].emplace_back( @@ -417,7 +418,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c for (VariableDeclaration const* v: contract->stateVariables()) if (v->isPartOfExternalInterface()) { - auto functionType = make_shared(*v); + auto functionType = TypeProvider::function(*v); // under non error circumstances this should be true if (functionType->interfaceFunctionType()) externalDeclarations[functionType->externalSignature()].emplace_back( diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 66664245f362..fb766eea9f9e 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -235,7 +235,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) solAssert(!!m_currentNode, ""); solAssert(!!_functionCall.expression().annotation().type, ""); - if (auto functionType = dynamic_pointer_cast(_functionCall.expression().annotation().type)) + if (auto functionType = dynamic_cast(_functionCall.expression().annotation().type)) switch (functionType->kind()) { case FunctionType::Kind::Revert: diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 2276d783808e..15c29d37e686 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -34,42 +35,50 @@ namespace dev namespace solidity { -GlobalContext::GlobalContext(): -m_magicVariables(vector>{ - make_shared("abi", make_shared(MagicType::Kind::ABI)), - make_shared("addmod", make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), - make_shared("assert", make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), - make_shared("block", make_shared(MagicType::Kind::Block)), - make_shared("blockhash", make_shared(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)), - make_shared("ecrecover", make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), - make_shared("gasleft", make_shared(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), - make_shared("keccak256", make_shared(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), - make_shared("log0", make_shared(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), - make_shared("log1", make_shared(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), - make_shared("log2", make_shared(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)), - make_shared("log3", make_shared(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)), - make_shared("log4", make_shared(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), - make_shared("msg", make_shared(MagicType::Kind::Message)), - make_shared("mulmod", make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), - make_shared("now", make_shared(256)), - make_shared("require", make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), - make_shared("require", make_shared(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), - make_shared("revert", make_shared(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), - make_shared("revert", make_shared(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), - make_shared("ripemd160", make_shared(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)), - make_shared("selfdestruct", make_shared(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), - make_shared("sha256", make_shared(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), - make_shared("sha3", make_shared(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), - make_shared("suicide", make_shared(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), - make_shared("tx", make_shared(MagicType::Kind::Transaction)), - make_shared("type", make_shared( - strings{"address"} /* accepts any contract type, handled by the type checker */, - strings{} /* returns a MagicType, handled by the type checker */, - FunctionType::Kind::MetaType, - false, - StateMutability::Pure - )), -}) +inline vector> constructMagicVariables() +{ + static auto const magicVarDecl = [](string const& _name, Type const* _type) { + return make_shared(_name, _type); + }; + + return { + magicVarDecl("abi", TypeProvider::magic(MagicType::Kind::ABI)), + magicVarDecl("addmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), + magicVarDecl("assert", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), + magicVarDecl("block", TypeProvider::magic(MagicType::Kind::Block)), + magicVarDecl("blockhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)), + magicVarDecl("ecrecover", TypeProvider::function(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), + magicVarDecl("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), + magicVarDecl("keccak256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), + magicVarDecl("log0", TypeProvider::function(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), + magicVarDecl("log1", TypeProvider::function(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), + magicVarDecl("log2", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)), + magicVarDecl("log3", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)), + magicVarDecl("log4", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), + magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)), + magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), + magicVarDecl("now", TypeProvider::uint256()), + magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), + magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), + magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)), + magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), + magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), + magicVarDecl("sha3", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), + magicVarDecl("suicide", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), + magicVarDecl("tx", TypeProvider::magic(MagicType::Kind::Transaction)), + magicVarDecl("type", TypeProvider::function( + strings{"address"} /* accepts any contract type, handled by the type checker */, + strings{} /* returns a MagicType, handled by the type checker */, + FunctionType::Kind::MetaType, + false, + StateMutability::Pure + )), + }; +} + +GlobalContext::GlobalContext(): m_magicVariables{constructMagicVariables()} { } @@ -90,7 +99,7 @@ vector GlobalContext::declarations() const MagicVariableDeclaration const* GlobalContext::currentThis() const { if (!m_thisPointer[m_currentContract]) - m_thisPointer[m_currentContract] = make_shared("this", make_shared(*m_currentContract)); + m_thisPointer[m_currentContract] = make_shared("this", TypeProvider::contract(*m_currentContract)); return m_thisPointer[m_currentContract].get(); } @@ -98,7 +107,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const MagicVariableDeclaration const* GlobalContext::currentSuper() const { if (!m_superPointer[m_currentContract]) - m_superPointer[m_currentContract] = make_shared("super", make_shared(*m_currentContract, true)); + m_superPointer[m_currentContract] = make_shared("super", TypeProvider::contract(*m_currentContract, true)); return m_superPointer[m_currentContract].get(); } diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index d5f972801a7f..2b64a3ddbd36 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -228,7 +228,7 @@ vector NameAndTypeResolver::cleanedDeclarations( uniqueFunctions.end(), [&](Declaration const* d) { - shared_ptr newFunctionType { d->functionType(false) }; + FunctionType const* newFunctionType = d->functionType(false); if (!newFunctionType) newFunctionType = d->functionType(true); return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType); @@ -241,7 +241,7 @@ vector NameAndTypeResolver::cleanedDeclarations( void NameAndTypeResolver::warnVariablesNamedLikeInstructions() { - for (auto const& instruction: c_instructions) + for (auto const& instruction: dev::eth::c_instructions) { string const instructionName{boost::algorithm::to_lower_copy(instruction.first)}; auto declarations = nameFromCurrentScope(instructionName, true); diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index 6a7e5c7eb6e3..936e133cae7d 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -17,10 +17,10 @@ #include -#include #include #include #include +#include #include #include diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 492adb5fe16a..a1df47abece5 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -120,7 +121,7 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName) { if (!_typeName.annotation().type) { - _typeName.annotation().type = Type::fromElementaryTypeName(_typeName.typeName()); + _typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName()); if (_typeName.stateMutability().is_initialized()) { // for non-address types this was already caught by the parser @@ -128,8 +129,10 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName) switch(*_typeName.stateMutability()) { case StateMutability::Payable: + _typeName.annotation().type = TypeProvider::payableAddress(); + break; case StateMutability::NonPayable: - _typeName.annotation().type = make_shared(*_typeName.stateMutability()); + _typeName.annotation().type = TypeProvider::address(); break; default: m_errorReporter.typeError( @@ -179,14 +182,14 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) _typeName.annotation().referencedDeclaration = declaration; if (StructDefinition const* structDef = dynamic_cast(declaration)) - _typeName.annotation().type = make_shared(*structDef); + _typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage); else if (EnumDefinition const* enumDef = dynamic_cast(declaration)) - _typeName.annotation().type = make_shared(*enumDef); + _typeName.annotation().type = TypeProvider::enumType(*enumDef); else if (ContractDefinition const* contract = dynamic_cast(declaration)) - _typeName.annotation().type = make_shared(*contract); + _typeName.annotation().type = TypeProvider::contract(*contract); else { - _typeName.annotation().type = make_shared(); + _typeName.annotation().type = TypeProvider::emptyTuple(); typeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); } } @@ -220,7 +223,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) } } - _typeName.annotation().type = make_shared(_typeName); + _typeName.annotation().type = TypeProvider::function(_typeName); } void ReferencesResolver::endVisit(Mapping const& _typeName) @@ -228,10 +231,10 @@ void ReferencesResolver::endVisit(Mapping const& _typeName) TypePointer keyType = _typeName.keyType().annotation().type; TypePointer valueType = _typeName.valueType().annotation().type; // Convert key type to memory. - keyType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, keyType); + keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType); // Convert value type to storage reference. - valueType = ReferenceType::copyForLocationIfReference(DataLocation::Storage, valueType); - _typeName.annotation().type = make_shared(keyType, valueType); + valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType); + _typeName.annotation().type = TypeProvider::mapping(keyType, valueType); } void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) @@ -249,7 +252,7 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) TypePointer& lengthTypeGeneric = length->annotation().type; if (!lengthTypeGeneric) lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length); - RationalNumberType const* lengthType = dynamic_cast(lengthTypeGeneric.get()); + RationalNumberType const* lengthType = dynamic_cast(lengthTypeGeneric); if (!lengthType || !lengthType->mobileType()) fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression."); else if (lengthType->isZero()) @@ -259,10 +262,10 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) else if (lengthType->isNegative()) fatalTypeError(length->location(), "Array with negative length specified."); else - _typeName.annotation().type = make_shared(DataLocation::Storage, baseType, lengthType->literalValue(nullptr)); + _typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthType->literalValue(nullptr)); } else - _typeName.annotation().type = make_shared(DataLocation::Storage, baseType); + _typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType); } bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) @@ -436,10 +439,10 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) } TypePointer type = _variable.typeName()->annotation().type; - if (auto ref = dynamic_cast(type.get())) + if (auto ref = dynamic_cast(type)) { bool isPointer = !_variable.isStateVariable(); - type = ref->copyForLocation(typeLoc, isPointer); + type = TypeProvider::withLocation(ref, typeLoc, isPointer); } _variable.annotation().type = type; diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 11ed6a4fe418..d6602c11d565 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -190,7 +190,7 @@ bool StaticAnalyzer::visit(ExpressionStatement const& _statement) bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) { - if (MagicType const* type = dynamic_cast(_memberAccess.expression().annotation().type.get())) + if (MagicType const* type = dynamic_cast(_memberAccess.expression().annotation().type)) { if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas") m_errorReporter.typeError( @@ -217,7 +217,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) } if (_memberAccess.memberName() == "callcode") - if (auto const* type = dynamic_cast(_memberAccess.annotation().type.get())) + if (auto const* type = dynamic_cast(_memberAccess.annotation().type)) if (type->kind() == FunctionType::Kind::BareCallCode) m_errorReporter.typeError( _memberAccess.location(), @@ -278,7 +278,7 @@ bool StaticAnalyzer::visit(BinaryOperation const& _operation) _operation.rightExpression().annotation().isPure && (_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod) ) - if (auto rhs = dynamic_pointer_cast( + if (auto rhs = dynamic_cast( ConstantEvaluator(m_errorReporter).evaluate(_operation.rightExpression()) )) if (rhs->isZero()) @@ -294,13 +294,13 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall) { if (_functionCall.annotation().kind == FunctionCallKind::FunctionCall) { - auto functionType = dynamic_pointer_cast(_functionCall.expression().annotation().type); + auto functionType = dynamic_cast(_functionCall.expression().annotation().type); solAssert(functionType, ""); if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod) { solAssert(_functionCall.arguments().size() == 3, ""); if (_functionCall.arguments()[2]->annotation().isPure) - if (auto lastArg = dynamic_pointer_cast( + if (auto lastArg = dynamic_cast( ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2]) )) if (lastArg->isZero()) diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 7b8aa0f27875..5f108c6f9e35 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -17,12 +17,12 @@ #include -#include #include #include #include #include +#include #include #include diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d8a92097c5a3..e5895b6def6d 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -107,7 +108,7 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) size_t toStorageCopies = 0; for (size_t i = 0; i < lhs.components().size(); ++i) { - ReferenceType const* ref = dynamic_cast(lhs.components()[i].get()); + ReferenceType const* ref = dynamic_cast(lhs.components()[i]); if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer()) continue; toStorageCopies++; @@ -137,7 +138,7 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c if (arguments.size() >= 1) { - BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType::bytesMemory()); + BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()); if (!result) m_errorReporter.typeErrorConcatenateDescriptions( @@ -169,17 +170,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c for (auto const& typeArgument: tupleExpression->components()) { solAssert(typeArgument, ""); - if (TypeType const* argTypeType = dynamic_cast(type(*typeArgument).get())) + if (TypeType const* argTypeType = dynamic_cast(type(*typeArgument))) { TypePointer actualType = argTypeType->actualType(); solAssert(actualType, ""); // We force memory because the parser currently cannot handle // data locations. Furthermore, storage can be a little dangerous and // calldata is not really implemented anyway. - actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType); + actualType = TypeProvider::withLocationIfReference(DataLocation::Memory, actualType); // We force address payable for address types. if (actualType->category() == Type::Category::Address) - actualType = make_shared(StateMutability::Payable); + actualType = TypeProvider::payableAddress(); solAssert( !actualType->dataStoredIn(DataLocation::CallData) && !actualType->dataStoredIn(DataLocation::Storage), @@ -195,7 +196,7 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c else { m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name."); - components.push_back(make_shared()); + components.push_back(TypeProvider::emptyTuple()); } } return components; @@ -230,7 +231,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio return {}; } - return {MagicType::metaType(dynamic_cast(*firstArgType).actualType())}; + return {TypeProvider::meta(dynamic_cast(*firstArgType).actualType())}; } void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) @@ -303,12 +304,12 @@ bool TypeChecker::visit(StructDefinition const& _struct) for (ASTPointer const& member: _struct.members()) { - Type const* memberType = type(*member).get(); + Type const* memberType = type(*member); while (auto arrayType = dynamic_cast(memberType)) { if (arrayType->isDynamicallySized()) break; - memberType = arrayType->baseType().get(); + memberType = arrayType->baseType(); } if (auto structType = dynamic_cast(memberType)) if (_cycleDetector.run(structType->structDefinition())) @@ -359,7 +360,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) { auto iType = type(var)->interfaceType(isLibraryFunction); - if (!iType.get()) + if (!iType) { solAssert(!iType.message().empty(), "Expected detailed error message!"); m_errorReporter.fatalTypeError(var.location(), iType.message()); @@ -455,7 +456,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (!_variable.type()->isValueType()) { bool allowed = false; - if (auto arrayType = dynamic_cast(_variable.type().get())) + if (auto arrayType = dynamic_cast(_variable.type())) allowed = arrayType->isByteArray(); if (!allowed) m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented."); @@ -498,7 +499,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) switch (varType->category()) { case Type::Category::Array: - if (auto arrayType = dynamic_cast(varType.get())) + if (auto arrayType = dynamic_cast(varType)) if ( ((arrayType->location() == DataLocation::Memory) || (arrayType->location() == DataLocation::CallData)) && @@ -584,7 +585,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) numIndexed++; if (!type(*var)->canLiveOutsideStorage()) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); - if (!type(*var)->interfaceType(false).get()) + if (!type(*var)->interfaceType(false)) m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type."); if ( !_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && @@ -607,7 +608,7 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) { FunctionType const& fun = dynamic_cast(*_funType.annotation().type); if (fun.kind() == FunctionType::Kind::External) - solAssert(fun.interfaceType(false).get(), "External function type uses internal types."); + solAssert(fun.interfaceType(false), "External function type uses internal types."); } bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) @@ -716,7 +717,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) bool TypeChecker::visit(IfStatement const& _ifStatement) { - expectType(_ifStatement.condition(), BoolType()); + expectType(_ifStatement.condition(), *TypeProvider::boolean()); _ifStatement.trueStatement().accept(*this); if (_ifStatement.falseStatement()) _ifStatement.falseStatement()->accept(*this); @@ -725,7 +726,7 @@ bool TypeChecker::visit(IfStatement const& _ifStatement) bool TypeChecker::visit(WhileStatement const& _whileStatement) { - expectType(_whileStatement.condition(), BoolType()); + expectType(_whileStatement.condition(), *TypeProvider::boolean()); _whileStatement.body().accept(*this); return false; } @@ -735,7 +736,7 @@ bool TypeChecker::visit(ForStatement const& _forStatement) if (_forStatement.initializationExpression()) _forStatement.initializationExpression()->accept(*this); if (_forStatement.condition()) - expectType(*_forStatement.condition(), BoolType()); + expectType(*_forStatement.condition(), *TypeProvider::boolean()); if (_forStatement.loopExpression()) _forStatement.loopExpression()->accept(*this); _forStatement.body().accept(*this); @@ -759,7 +760,7 @@ void TypeChecker::endVisit(Return const& _return) TypePointers returnTypes; for (auto const& var: params->parameters()) returnTypes.push_back(type(*var)); - if (auto tupleType = dynamic_cast(type(*_return.expression()).get())) + if (auto tupleType = dynamic_cast(type(*_return.expression()))) { if (tupleType->components().size() != params->parameters().size()) m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration."); @@ -841,7 +842,7 @@ bool typeCanBeExpressed(vector> const& decls) if (!decl->annotation().type) return false; - if (auto functionType = dynamic_cast(decl->annotation().type.get())) + if (auto functionType = dynamic_cast(decl->annotation().type)) if ( functionType->kind() != FunctionType::Kind::Internal && functionType->kind() != FunctionType::Kind::External @@ -878,7 +879,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (!varDecl.annotation().type) m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed."); - if (auto ref = dynamic_cast(type(varDecl).get())) + if (auto ref = dynamic_cast(type(varDecl))) { if (ref->dataStoredIn(DataLocation::Storage)) { @@ -888,7 +889,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) m_errorReporter.declarationError(varDecl.location(), errorText); } } - else if (dynamic_cast(type(varDecl).get())) + else if (dynamic_cast(type(varDecl))) m_errorReporter.typeError( varDecl.location(), "Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable." @@ -902,7 +903,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) _statement.initialValue()->accept(*this); TypePointers valueTypes; - if (auto tupleType = dynamic_cast(type(*_statement.initialValue()).get())) + if (auto tupleType = dynamic_cast(type(*_statement.initialValue()))) valueTypes = tupleType->components(); else valueTypes = TypePointers{type(*_statement.initialValue())}; @@ -950,13 +951,13 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) else solAssert(false, ""); } - else if (*var.annotation().type == TupleType()) + else if (*var.annotation().type == *TypeProvider::emptyTuple()) solAssert(false, "Cannot declare variable with void (empty tuple) type."); else if (valueComponentType->category() == Type::Category::RationalNumber) { string typeName = var.annotation().type->toString(true); string extension; - if (auto type = dynamic_cast(var.annotation().type.get())) + if (auto type = dynamic_cast(var.annotation().type)) { unsigned numBits = type->numBits(); bool isSigned = type->isSigned(); @@ -974,7 +975,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) extension = ", which can hold values between " + minValue + " and " + maxValue; } else - solAssert(dynamic_cast(var.annotation().type.get()), "Unknown type."); + solAssert(dynamic_cast(var.annotation().type), "Unknown type."); } var.accept(*this); @@ -1054,7 +1055,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) if (auto call = dynamic_cast(&_statement.expression())) { - if (auto callType = dynamic_cast(type(call->expression()).get())) + if (auto callType = dynamic_cast(type(call->expression()))) { auto kind = callType->kind(); if ( @@ -1072,7 +1073,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) bool TypeChecker::visit(Conditional const& _conditional) { - expectType(_conditional.condition(), BoolType()); + expectType(_conditional.condition(), *TypeProvider::boolean()); _conditional.trueExpression().accept(*this); _conditional.falseExpression().accept(*this); @@ -1080,7 +1081,7 @@ bool TypeChecker::visit(Conditional const& _conditional) TypePointer trueType = type(_conditional.trueExpression())->mobileType(); TypePointer falseType = type(_conditional.falseExpression())->mobileType(); - TypePointer commonType; + TypePointer commonType = nullptr; if (!trueType) m_errorReporter.typeError(_conditional.trueExpression().location(), "Invalid mobile type in true expression."); @@ -1134,7 +1135,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& if (auto const* tupleExpression = dynamic_cast(&_expression)) { auto const* tupleType = dynamic_cast(&_type); - auto const& types = tupleType ? tupleType->components() : vector { _type.shared_from_this() }; + auto const& types = tupleType ? tupleType->components() : vector { &_type }; solAssert( tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(), @@ -1168,7 +1169,7 @@ bool TypeChecker::visit(Assignment const& _assignment) checkExpressionAssignment(*t, _assignment.leftHandSide()); - if (TupleType const* tupleType = dynamic_cast(t.get())) + if (TupleType const* tupleType = dynamic_cast(t)) { if (_assignment.assignmentOperator() != Token::Assign) m_errorReporter.typeError( @@ -1176,12 +1177,12 @@ bool TypeChecker::visit(Assignment const& _assignment) "Compound assignment is not allowed for tuple types." ); // Sequenced assignments of tuples is not valid, make the result a "void" type. - _assignment.annotation().type = make_shared(); + _assignment.annotation().type = TypeProvider::emptyTuple(); expectType(_assignment.rightHandSide(), *tupleType); // expectType does not cause fatal errors, so we have to check again here. - if (dynamic_cast(type(_assignment.rightHandSide()).get())) + if (dynamic_cast(type(_assignment.rightHandSide()))) checkDoubleStorageAssignment(_assignment); } else if (_assignment.assignmentOperator() == Token::Assign) @@ -1228,14 +1229,14 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (components.size() == 1) _tuple.annotation().type = type(*components[0]); else - _tuple.annotation().type = make_shared(types); + _tuple.annotation().type = TypeProvider::tuple(move(types)); // If some of the components are not LValues, the error is reported above. _tuple.annotation().isLValue = true; } else { bool isPure = true; - TypePointer inlineArrayType; + TypePointer inlineArrayType = nullptr; for (size_t i = 0; i < components.size(); ++i) { @@ -1285,14 +1286,14 @@ bool TypeChecker::visit(TupleExpression const& _tuple) else if (!inlineArrayType->canLiveOutsideStorage()) m_errorReporter.fatalTypeError(_tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage."); - _tuple.annotation().type = make_shared(DataLocation::Memory, inlineArrayType, types.size()); + _tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size()); } else { if (components.size() == 1) _tuple.annotation().type = type(*components[0]); else - _tuple.annotation().type = make_shared(types); + _tuple.annotation().type = TypeProvider::tuple(move(types)); } } @@ -1349,7 +1350,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) _operation.annotation().commonType = commonType; _operation.annotation().type = TokenTraits::isCompareOp(_operation.getOperator()) ? - make_shared() : + TypeProvider::boolean() : commonType; _operation.annotation().isPure = _operation.leftExpression().annotation().isPure && @@ -1401,20 +1402,20 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( ); else { - TypePointer const& argType = type(*arguments.front()); + Type const* argType = type(*arguments.front()); // Resulting data location is memory unless we are converting from a reference // type with a different data location. // (data location cannot yet be specified for type conversions) DataLocation dataLoc = DataLocation::Memory; - if (auto argRefType = dynamic_cast(argType.get())) + if (auto argRefType = dynamic_cast(argType)) dataLoc = argRefType->location(); - if (auto type = dynamic_cast(resultType.get())) - resultType = type->copyForLocation(dataLoc, type->isPointer()); + if (auto type = dynamic_cast(resultType)) + resultType = TypeProvider::withLocation(type, dataLoc, type->isPointer()); if (argType->isExplicitlyConvertibleTo(*resultType)) { - if (auto argArrayType = dynamic_cast(argType.get())) + if (auto argArrayType = dynamic_cast(argType)) { - auto resultArrayType = dynamic_cast(resultType.get()); + auto resultArrayType = dynamic_cast(resultType); solAssert(!!resultArrayType, ""); solAssert( argArrayType->location() != DataLocation::Storage || @@ -1436,9 +1437,9 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( argType->category() == Type::Category::Address ) { - solAssert(dynamic_cast(resultType.get())->isPayable(), ""); + solAssert(dynamic_cast(resultType)->isPayable(), ""); solAssert( - dynamic_cast(argType.get())->stateMutability() < + dynamic_cast(argType)->stateMutability() < StateMutability::Payable, "" ); @@ -1474,10 +1475,8 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( } if (resultType->category() == Type::Category::Address) { - bool const payable = argType->isExplicitlyConvertibleTo(AddressType::addressPayable()); - resultType = make_shared( - payable ? StateMutability::Payable : StateMutability::NonPayable - ); + bool const payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress()); + resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address(); } } return resultType; @@ -1829,17 +1828,17 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) _functionCall.expression().accept(*this); - TypePointer const& expressionType = type(_functionCall.expression()); + Type const* expressionType = type(_functionCall.expression()); // Determine function call kind and function type for this FunctionCall node FunctionCallAnnotation& funcCallAnno = _functionCall.annotation(); - FunctionTypePointer functionType; + FunctionTypePointer functionType = nullptr; // Determine and assign function call kind, purity and function type for this FunctionCall node switch (expressionType->category()) { case Type::Category::Function: - functionType = dynamic_pointer_cast(expressionType); + functionType = dynamic_cast(expressionType); funcCallAnno.kind = FunctionCallKind::FunctionCall; // Purity for function calls also depends upon the callee and its FunctionType @@ -1926,7 +1925,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) funcCallAnno.type = returnTypes.size() == 1 ? move(returnTypes.front()) : - make_shared(move(returnTypes)); + TypeProvider::tuple(move(returnTypes)); break; } @@ -1936,7 +1935,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) // for non-callables, ensure error reported and annotate node to void function solAssert(m_errorReporter.hasErrors(), ""); funcCallAnno.kind = FunctionCallKind::FunctionCall; - funcCallAnno.type = make_shared(); + funcCallAnno.type = TypeProvider::emptyTuple(); break; } @@ -1998,9 +1997,9 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) _newExpression.typeName().location(), "Length has to be placed in parentheses after the array type for new expression." ); - type = ReferenceType::copyForLocationIfReference(DataLocation::Memory, type); - _newExpression.annotation().type = make_shared( - TypePointers{make_shared(256)}, + type = TypeProvider::withLocationIfReference(DataLocation::Memory, type); + _newExpression.annotation().type = TypeProvider::function( + TypePointers{TypeProvider::uint256()}, TypePointers{type}, strings(1, ""), strings(1, ""), @@ -2044,7 +2043,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (initialMemberCount == 0) { // Try to see if the member was removed because it is only available for storage types. - auto storageType = ReferenceType::copyForLocationIfReference( + auto storageType = TypeProvider::withLocationIfReference( DataLocation::Storage, exprType ); @@ -2059,7 +2058,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) string errorMsg = "Member \"" + memberName + "\" not found or not visible " "after argument-dependent lookup in " + exprType->toString() + "."; - if (auto const& funType = dynamic_pointer_cast(exprType)) + if (auto const& funType = dynamic_cast(exprType)) { auto const& t = funType->returnParameterTypes(); @@ -2079,7 +2078,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) } else if (exprType->category() == Type::Category::Contract) { - for (auto const& addressMember: AddressType::addressPayable().nativeMembers(nullptr)) + for (auto const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr)) if (addressMember.name == memberName) { Identifier const* var = dynamic_cast(&_memberAccess.expression()); @@ -2088,7 +2087,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) break; } } - else if (auto addressType = dynamic_cast(exprType.get())) + else if (auto addressType = dynamic_cast(exprType)) { // Trigger error when using send or transfer with a non-payable fallback function. if (memberName == "send" || memberName == "transfer") @@ -2118,14 +2117,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) annotation.referencedDeclaration = possibleMembers.front().declaration; annotation.type = possibleMembers.front().type; - if (auto funType = dynamic_cast(annotation.type.get())) + if (auto funType = dynamic_cast(annotation.type)) solAssert( !funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()), "Function \"" + memberName + "\" cannot be called on an object of type " + exprType->toString() + " (expected " + funType->selfType()->toString() + ")." ); - if (auto const* structType = dynamic_cast(exprType.get())) + if (auto const* structType = dynamic_cast(exprType)) annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); else if (exprType->category() == Type::Category::Array) { @@ -2138,18 +2137,18 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) } else if (exprType->category() == Type::Category::FixedBytes) annotation.isLValue = false; - else if (TypeType const* typeType = dynamic_cast(exprType.get())) + else if (TypeType const* typeType = dynamic_cast(exprType)) { - if (ContractType const* contractType = dynamic_cast(typeType->actualType().get())) + if (ContractType const* contractType = dynamic_cast(typeType->actualType())) annotation.isLValue = annotation.referencedDeclaration->isLValue(); } // TODO some members might be pure, but for example `address(0x123).balance` is not pure // although every subexpression is, so leaving this limited for now. - if (auto tt = dynamic_cast(exprType.get())) + if (auto tt = dynamic_cast(exprType)) if (tt->actualType()->category() == Type::Category::Enum) annotation.isPure = true; - if (auto magicType = dynamic_cast(exprType.get())) + if (auto magicType = dynamic_cast(exprType)) { if (magicType->kind() == MagicType::Kind::ABI) annotation.isPure = true; @@ -2178,7 +2177,7 @@ bool TypeChecker::visit(IndexAccess const& _access) { _access.baseExpression().accept(*this); TypePointer baseType = type(_access.baseExpression()); - TypePointer resultType; + TypePointer resultType = nullptr; bool isLValue = false; bool isPure = _access.baseExpression().annotation().isPure; Expression const* index = _access.indexExpression(); @@ -2196,9 +2195,9 @@ bool TypeChecker::visit(IndexAccess const& _access) } else { - expectType(*index, IntegerType::uint256()); + expectType(*index, *TypeProvider::uint256()); if (!m_errorReporter.hasErrors()) - if (auto numberType = dynamic_cast(type(*index).get())) + if (auto numberType = dynamic_cast(type(*index))) { solAssert(!numberType->isFractional(), ""); if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr)) @@ -2223,16 +2222,16 @@ bool TypeChecker::visit(IndexAccess const& _access) case Type::Category::TypeType: { TypeType const& typeType = dynamic_cast(*baseType); - if (dynamic_cast(typeType.actualType().get())) + if (dynamic_cast(typeType.actualType())) m_errorReporter.typeError(_access.location(), "Index access for contracts or libraries is not possible."); if (!index) - resultType = make_shared(make_shared(DataLocation::Memory, typeType.actualType())); + resultType = TypeProvider::typeType(TypeProvider::array(DataLocation::Memory, typeType.actualType())); else { u256 length = 1; - if (expectType(*index, IntegerType::uint256())) + if (expectType(*index, *TypeProvider::uint256())) { - if (auto indexValue = dynamic_cast(type(*index).get())) + if (auto indexValue = dynamic_cast(type(*index))) length = indexValue->literalValue(nullptr); else m_errorReporter.fatalTypeError(index->location(), "Integer constant expected."); @@ -2240,7 +2239,7 @@ bool TypeChecker::visit(IndexAccess const& _access) else solAssert(m_errorReporter.hasErrors(), "Expected errors as expectType returned false"); - resultType = make_shared(make_shared( + resultType = TypeProvider::typeType(TypeProvider::array( DataLocation::Memory, typeType.actualType(), length @@ -2255,13 +2254,13 @@ bool TypeChecker::visit(IndexAccess const& _access) m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted."); else { - if (!expectType(*index, IntegerType::uint256())) + if (!expectType(*index, *TypeProvider::uint256())) m_errorReporter.fatalTypeError(_access.location(), "Index expression cannot be represented as an unsigned integer."); - if (auto integerType = dynamic_cast(type(*index).get())) + if (auto integerType = dynamic_cast(type(*index))) if (bytesType.numBytes() <= integerType->literalValue(nullptr)) m_errorReporter.typeError(_access.location(), "Out of bounds array access."); } - resultType = make_shared(1); + resultType = TypeProvider::fixedBytes(1); isLValue = false; // @todo this heavily depends on how it is embedded break; } @@ -2271,7 +2270,7 @@ bool TypeChecker::visit(IndexAccess const& _access) "Indexed expression has to be a type, mapping or array (is " + baseType->toString() + ")" ); } - _access.annotation().type = move(resultType); + _access.annotation().type = resultType; _access.annotation().isLValue = isLValue; if (index && !index->annotation().isPure) isPure = false; @@ -2337,16 +2336,16 @@ bool TypeChecker::visit(Identifier const& _identifier) annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); else if (dynamic_cast(annotation.referencedDeclaration)) { - if (dynamic_cast(annotation.type.get())) + if (dynamic_cast(annotation.type)) annotation.isPure = true; } - else if (dynamic_cast(annotation.type.get())) + else if (dynamic_cast(annotation.type)) annotation.isPure = true; // Check for deprecated function names. // The check is done here for the case without an actual function call. - if (FunctionType const* fType = dynamic_cast(_identifier.annotation().type.get())) + if (FunctionType const* fType = dynamic_cast(_identifier.annotation().type)) { if (_identifier.name() == "sha3" && fType->kind() == FunctionType::Kind::KECCAK256) m_errorReporter.typeError( @@ -2365,7 +2364,7 @@ bool TypeChecker::visit(Identifier const& _identifier) void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) { - _expr.annotation().type = make_shared(Type::fromElementaryTypeName(_expr.typeName())); + _expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.typeName())); _expr.annotation().isPure = true; } @@ -2374,7 +2373,7 @@ void TypeChecker::endVisit(Literal const& _literal) if (_literal.looksLikeAddress()) { // Assign type here if it even looks like an address. This prevents double errors for invalid addresses - _literal.annotation().type = make_shared(StateMutability::Payable); + _literal.annotation().type = TypeProvider::payableAddress(); string msg; if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits @@ -2413,7 +2412,7 @@ void TypeChecker::endVisit(Literal const& _literal) ); if (!_literal.annotation().type) - _literal.annotation().type = Type::forLiteral(_literal); + _literal.annotation().type = TypeProvider::forLiteral(_literal); if (!_literal.annotation().type) m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); @@ -2460,7 +2459,7 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte _expectedType.toString(); if ( type(_expression)->category() == Type::Category::RationalNumber && - dynamic_pointer_cast(type(_expression))->isFractional() && + dynamic_cast(type(_expression))->isFractional() && type(_expression)->mobileType() ) { diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 70b907b5bad4..9067e13ea2d4 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -108,11 +108,11 @@ class AssemblyViewPureChecker: public boost::static_visitor private: std::function m_reportMutability; - void checkInstruction(SourceLocation _location, solidity::Instruction _instruction) + void checkInstruction(SourceLocation _location, dev::eth::Instruction _instruction) { if (eth::SemanticInformation::invalidInViewFunctions(_instruction)) m_reportMutability(StateMutability::NonPayable, _location); - else if (_instruction == Instruction::CALLVALUE) + else if (_instruction == dev::eth::Instruction::CALLVALUE) m_reportMutability(StateMutability::Payable, _location); else if (eth::SemanticInformation::invalidInPureFunctions(_instruction)) m_reportMutability(StateMutability::View, _location); diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 5be036f4e5f5..685b821d085d 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -105,7 +106,7 @@ ImportAnnotation& ImportDirective::annotation() const TypePointer ImportDirective::type() const { solAssert(!!annotation().sourceUnit, ""); - return make_shared(*annotation().sourceUnit); + return TypeProvider::module(*annotation().sourceUnit); } map, FunctionTypePointer> ContractDefinition::interfaceFunctions() const @@ -188,10 +189,10 @@ vector, FunctionTypePointer>> const& ContractDefinition::inter vector functions; for (FunctionDefinition const* f: contract->definedFunctions()) if (f->isPartOfExternalInterface()) - functions.push_back(make_shared(*f, false)); + functions.push_back(TypeProvider::function(*f, false)); for (VariableDeclaration const* v: contract->stateVariables()) if (v->isPartOfExternalInterface()) - functions.push_back(make_shared(*v)); + functions.push_back(TypeProvider::function(*v)); for (FunctionTypePointer const& fun: functions) { if (!fun->interfaceFunctionType()) @@ -214,16 +215,12 @@ vector const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) { - set memberSeen; m_inheritableMembers.reset(new vector()); auto addInheritableMember = [&](Declaration const* _decl) { solAssert(_decl, "addInheritableMember got a nullpointer."); - if (memberSeen.count(_decl->name()) == 0 && _decl->isVisibleInDerivedContracts()) - { - memberSeen.insert(_decl->name()); + if (_decl->isVisibleInDerivedContracts()) m_inheritableMembers->push_back(_decl); - } }; for (FunctionDefinition const* f: definedFunctions()) @@ -246,7 +243,7 @@ vector const& ContractDefinition::inheritableMembers() const TypePointer ContractDefinition::type() const { - return make_shared(make_shared(*this)); + return TypeProvider::typeType(TypeProvider::contract(*this)); } ContractDefinitionAnnotation& ContractDefinition::annotation() const @@ -265,7 +262,7 @@ TypeNameAnnotation& TypeName::annotation() const TypePointer StructDefinition::type() const { - return make_shared(make_shared(*this)); + return TypeProvider::typeType(TypeProvider::structType(*this, DataLocation::Storage)); } TypeDeclarationAnnotation& StructDefinition::annotation() const @@ -279,12 +276,12 @@ TypePointer EnumValue::type() const { auto parentDef = dynamic_cast(scope()); solAssert(parentDef, "Enclosing Scope of EnumValue was not set"); - return make_shared(*parentDef); + return TypeProvider::enumType(*parentDef); } TypePointer EnumDefinition::type() const { - return make_shared(make_shared(*this)); + return TypeProvider::typeType(TypeProvider::enumType(*this)); } TypeDeclarationAnnotation& EnumDefinition::annotation() const @@ -312,7 +309,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const case Declaration::Visibility::Private: case Declaration::Visibility::Internal: case Declaration::Visibility::Public: - return make_shared(*this, _internal); + return TypeProvider::function(*this, _internal); case Declaration::Visibility::External: return {}; } @@ -328,7 +325,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const return {}; case Declaration::Visibility::Public: case Declaration::Visibility::External: - return make_shared(*this, _internal); + return TypeProvider::function(*this, _internal); } } @@ -339,12 +336,12 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const TypePointer FunctionDefinition::type() const { solAssert(visibility() != Declaration::Visibility::External, ""); - return make_shared(*this); + return TypeProvider::function(*this); } string FunctionDefinition::externalSignature() const { - return FunctionType(*this).externalSignature(); + return TypeProvider::function(*this)->externalSignature(); } FunctionDefinitionAnnotation& FunctionDefinition::annotation() const @@ -356,7 +353,7 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const TypePointer ModifierDefinition::type() const { - return make_shared(*this); + return TypeProvider::modifier(*this); } ModifierDefinitionAnnotation& ModifierDefinition::annotation() const @@ -368,15 +365,15 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const TypePointer EventDefinition::type() const { - return make_shared(*this); + return TypeProvider::function(*this); } FunctionTypePointer EventDefinition::functionType(bool _internal) const { if (_internal) - return make_shared(*this); + return TypeProvider::function(*this); else - return {}; + return nullptr; } EventDefinitionAnnotation& EventDefinition::annotation() const @@ -508,8 +505,8 @@ bool VariableDeclaration::hasReferenceOrMappingType() const { solAssert(typeName(), ""); solAssert(typeName()->annotation().type, "Can only be called after reference resolution"); - TypePointer const& type = typeName()->annotation().type; - return type->category() == Type::Category::Mapping || dynamic_cast(type.get()); + Type const* type = typeName()->annotation().type; + return type->category() == Type::Category::Mapping || dynamic_cast(type); } set VariableDeclaration::allowedDataLocations() const @@ -557,21 +554,21 @@ TypePointer VariableDeclaration::type() const FunctionTypePointer VariableDeclaration::functionType(bool _internal) const { if (_internal) - return {}; + return nullptr; switch (visibility()) { case Declaration::Visibility::Default: solAssert(false, "visibility() should not return Default"); case Declaration::Visibility::Private: case Declaration::Visibility::Internal: - return {}; + return nullptr; case Declaration::Visibility::Public: case Declaration::Visibility::External: - return make_shared(*this); + return TypeProvider::function(*this); } // To make the compiler happy - return {}; + return nullptr; } VariableDeclarationAnnotation& VariableDeclaration::annotation() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 62170284c0ca..c5a13b8cdfc5 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -848,8 +848,9 @@ class EventDefinition: public CallableDeclaration, public Documented class MagicVariableDeclaration: public Declaration { public: - MagicVariableDeclaration(ASTString const& _name, std::shared_ptr const& _type): + MagicVariableDeclaration(ASTString const& _name, Type const* _type): Declaration(SourceLocation(), std::make_shared(_name)), m_type(_type) {} + void accept(ASTVisitor&) override { solAssert(false, "MagicVariableDeclaration used inside real AST."); @@ -859,15 +860,15 @@ class MagicVariableDeclaration: public Declaration solAssert(false, "MagicVariableDeclaration used inside real AST."); } - FunctionTypePointer functionType(bool) const override + FunctionType const* functionType(bool) const override { solAssert(m_type->category() == Type::Category::Function, ""); - return std::dynamic_pointer_cast(m_type); + return dynamic_cast(m_type); } TypePointer type() const override { return m_type; } private: - std::shared_ptr m_type; + Type const* m_type; }; /// Types diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 93e793ace2ca..64c8c779cf42 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -45,7 +45,7 @@ namespace solidity { class Type; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; struct ASTAnnotation { @@ -122,7 +122,7 @@ struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation struct VariableDeclarationAnnotation: ASTAnnotation { /// Type of variable (type of identifier referencing this variable). - TypePointer type; + TypePointer type = nullptr; }; struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation @@ -155,7 +155,7 @@ struct TypeNameAnnotation: ASTAnnotation { /// Type declared by this type name, i.e. type of a variable where this type name is used. /// Set during reference resolution stage. - TypePointer type; + TypePointer type = nullptr; }; struct UserDefinedTypeNameAnnotation: TypeNameAnnotation @@ -170,7 +170,7 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation struct ExpressionAnnotation: ASTAnnotation { /// Inferred type of the expression. - TypePointer type; + TypePointer type = nullptr; /// Whether the expression is a constant variable bool isConstant = false; /// Whether the expression is pure, i.e. compile-time constant. @@ -203,7 +203,7 @@ struct BinaryOperationAnnotation: ExpressionAnnotation { /// The common type that is used for the operation, not necessarily the result type (which /// e.g. for comparisons is bool). - TypePointer commonType; + TypePointer commonType = nullptr; }; enum class FunctionCallKind diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h index a4101eccf7b9..a1a36efbddc5 100644 --- a/libsolidity/ast/ASTEnums.h +++ b/libsolidity/ast/ASTEnums.h @@ -57,7 +57,7 @@ class Type; struct FuncCallArguments { /// Types of arguments - std::vector> types; + std::vector types; /// Names of the arguments if given, otherwise unset std::vector> names; diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp new file mode 100644 index 000000000000..6e94c011be3b --- /dev/null +++ b/libsolidity/ast/TypeProvider.cpp @@ -0,0 +1,541 @@ +/* + 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 . +*/ + +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace solidity; + +BoolType const TypeProvider::m_boolean{}; +InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{}; + +/// The string and bytes unique_ptrs are initialized when they are first used because +/// they rely on `byte` being available which we cannot guarantee in the static init context. +unique_ptr TypeProvider::m_bytesStorage; +unique_ptr TypeProvider::m_bytesMemory; +unique_ptr TypeProvider::m_stringStorage; +unique_ptr TypeProvider::m_stringMemory; + +TupleType const TypeProvider::m_emptyTuple{}; +AddressType const TypeProvider::m_payableAddress{StateMutability::Payable}; +AddressType const TypeProvider::m_address{StateMutability::NonPayable}; + +array, 32> const TypeProvider::m_intM{{ + {make_unique(8 * 1, IntegerType::Modifier::Signed)}, + {make_unique(8 * 2, IntegerType::Modifier::Signed)}, + {make_unique(8 * 3, IntegerType::Modifier::Signed)}, + {make_unique(8 * 4, IntegerType::Modifier::Signed)}, + {make_unique(8 * 5, IntegerType::Modifier::Signed)}, + {make_unique(8 * 6, IntegerType::Modifier::Signed)}, + {make_unique(8 * 7, IntegerType::Modifier::Signed)}, + {make_unique(8 * 8, IntegerType::Modifier::Signed)}, + {make_unique(8 * 9, IntegerType::Modifier::Signed)}, + {make_unique(8 * 10, IntegerType::Modifier::Signed)}, + {make_unique(8 * 11, IntegerType::Modifier::Signed)}, + {make_unique(8 * 12, IntegerType::Modifier::Signed)}, + {make_unique(8 * 13, IntegerType::Modifier::Signed)}, + {make_unique(8 * 14, IntegerType::Modifier::Signed)}, + {make_unique(8 * 15, IntegerType::Modifier::Signed)}, + {make_unique(8 * 16, IntegerType::Modifier::Signed)}, + {make_unique(8 * 17, IntegerType::Modifier::Signed)}, + {make_unique(8 * 18, IntegerType::Modifier::Signed)}, + {make_unique(8 * 19, IntegerType::Modifier::Signed)}, + {make_unique(8 * 20, IntegerType::Modifier::Signed)}, + {make_unique(8 * 21, IntegerType::Modifier::Signed)}, + {make_unique(8 * 22, IntegerType::Modifier::Signed)}, + {make_unique(8 * 23, IntegerType::Modifier::Signed)}, + {make_unique(8 * 24, IntegerType::Modifier::Signed)}, + {make_unique(8 * 25, IntegerType::Modifier::Signed)}, + {make_unique(8 * 26, IntegerType::Modifier::Signed)}, + {make_unique(8 * 27, IntegerType::Modifier::Signed)}, + {make_unique(8 * 28, IntegerType::Modifier::Signed)}, + {make_unique(8 * 29, IntegerType::Modifier::Signed)}, + {make_unique(8 * 30, IntegerType::Modifier::Signed)}, + {make_unique(8 * 31, IntegerType::Modifier::Signed)}, + {make_unique(8 * 32, IntegerType::Modifier::Signed)} +}}; + +array, 32> const TypeProvider::m_uintM{{ + {make_unique(8 * 1, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 2, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 3, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 4, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 5, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 6, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 7, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 8, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 9, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 10, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 11, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 12, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 13, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 14, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 15, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 16, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 17, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 18, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 19, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 20, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 21, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 22, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 23, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 24, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 25, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 26, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 27, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 28, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 29, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 30, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 31, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 32, IntegerType::Modifier::Unsigned)} +}}; + +array, 32> const TypeProvider::m_bytesM{{ + {make_unique(1)}, + {make_unique(2)}, + {make_unique(3)}, + {make_unique(4)}, + {make_unique(5)}, + {make_unique(6)}, + {make_unique(7)}, + {make_unique(8)}, + {make_unique(9)}, + {make_unique(10)}, + {make_unique(11)}, + {make_unique(12)}, + {make_unique(13)}, + {make_unique(14)}, + {make_unique(15)}, + {make_unique(16)}, + {make_unique(17)}, + {make_unique(18)}, + {make_unique(19)}, + {make_unique(20)}, + {make_unique(21)}, + {make_unique(22)}, + {make_unique(23)}, + {make_unique(24)}, + {make_unique(25)}, + {make_unique(26)}, + {make_unique(27)}, + {make_unique(28)}, + {make_unique(29)}, + {make_unique(30)}, + {make_unique(31)}, + {make_unique(32)} +}}; + +array, 4> const TypeProvider::m_magics{{ + {make_unique(MagicType::Kind::Block)}, + {make_unique(MagicType::Kind::Message)}, + {make_unique(MagicType::Kind::Transaction)}, + {make_unique(MagicType::Kind::ABI)} + // MetaType is stored separately +}}; + +inline void clearCache(Type const& type) +{ + type.clearCache(); +} + +template +inline void clearCache(unique_ptr const& type) +{ + // Some lazy-initialized types might not exist yet. + if (type) + type->clearCache(); +} + +template +inline void clearCaches(Container& container) +{ + for (auto const& e: container) + clearCache(e); +} + +void TypeProvider::reset() +{ + clearCache(m_boolean); + clearCache(m_inaccessibleDynamic); + clearCache(m_bytesStorage); + clearCache(m_bytesMemory); + clearCache(m_stringStorage); + clearCache(m_stringMemory); + clearCache(m_emptyTuple); + clearCache(m_payableAddress); + clearCache(m_address); + clearCaches(instance().m_intM); + clearCaches(instance().m_uintM); + clearCaches(instance().m_bytesM); + clearCaches(instance().m_magics); + + instance().m_generalTypes.clear(); + instance().m_stringLiteralTypes.clear(); + instance().m_ufixedMxN.clear(); + instance().m_fixedMxN.clear(); +} + +template +inline T const* TypeProvider::createAndGet(Args&& ... _args) +{ + instance().m_generalTypes.emplace_back(make_unique(std::forward(_args)...)); + return static_cast(instance().m_generalTypes.back().get()); +} + +Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type) +{ + solAssert( + TokenTraits::isElementaryTypeName(_type.token()), + "Expected an elementary type name but got " + _type.toString() + ); + + unsigned const m = _type.firstNumber(); + unsigned const n = _type.secondNumber(); + + switch (_type.token()) + { + case Token::IntM: + return integer(m, IntegerType::Modifier::Signed); + case Token::UIntM: + return integer(m, IntegerType::Modifier::Unsigned); + case Token::Byte: + return byte(); + case Token::BytesM: + return fixedBytes(m); + case Token::FixedMxN: + return fixedPoint(m, n, FixedPointType::Modifier::Signed); + case Token::UFixedMxN: + return fixedPoint(m, n, FixedPointType::Modifier::Unsigned); + case Token::Int: + return integer(256, IntegerType::Modifier::Signed); + case Token::UInt: + return integer(256, IntegerType::Modifier::Unsigned); + case Token::Fixed: + return fixedPoint(128, 18, FixedPointType::Modifier::Signed); + case Token::UFixed: + return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned); + case Token::Address: + return address(); + case Token::Bool: + return boolean(); + case Token::Bytes: + return bytesStorage(); + case Token::String: + return stringStorage(); + default: + solAssert( + false, + "Unable to convert elementary typename " + _type.toString() + " to type." + ); + } +} + +TypePointer TypeProvider::fromElementaryTypeName(string const& _name) +{ + vector nameParts; + boost::split(nameParts, _name, boost::is_any_of(" ")); + solAssert(nameParts.size() == 1 || nameParts.size() == 2, "Cannot parse elementary type: " + _name); + + Token token; + unsigned short firstNum, secondNum; + tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(nameParts[0]); + + auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum)); + if (auto* ref = dynamic_cast(t)) + { + DataLocation location = DataLocation::Storage; + if (nameParts.size() == 2) + { + if (nameParts[1] == "storage") + location = DataLocation::Storage; + else if (nameParts[1] == "calldata") + location = DataLocation::CallData; + else if (nameParts[1] == "memory") + location = DataLocation::Memory; + else + solAssert(false, "Unknown data location: " + nameParts[1]); + } + return withLocation(ref, location, true); + } + else if (t->category() == Type::Category::Address) + { + if (nameParts.size() == 2) + { + if (nameParts[1] == "payable") + return payableAddress(); + else + solAssert(false, "Invalid state mutability for address type: " + nameParts[1]); + } + return address(); + } + else + { + solAssert(nameParts.size() == 1, "Storage location suffix only allowed for reference types"); + return t; + } +} + +ArrayType const* TypeProvider::bytesStorage() +{ + if (!m_bytesStorage) + m_bytesStorage = make_unique(DataLocation::Storage, false); + return m_bytesStorage.get(); +} + +ArrayType const* TypeProvider::bytesMemory() +{ + if (!m_bytesMemory) + m_bytesMemory = make_unique(DataLocation::Memory, false); + return m_bytesMemory.get(); +} + +ArrayType const* TypeProvider::stringStorage() +{ + if (!m_stringStorage) + m_stringStorage = make_unique(DataLocation::Storage, true); + return m_stringStorage.get(); +} + +ArrayType const* TypeProvider::stringMemory() +{ + if (!m_stringMemory) + m_stringMemory = make_unique(DataLocation::Memory, true); + return m_stringMemory.get(); +} + +TypePointer TypeProvider::forLiteral(Literal const& _literal) +{ + switch (_literal.token()) + { + case Token::TrueLiteral: + case Token::FalseLiteral: + return boolean(); + case Token::Number: + return rationalNumber(_literal); + case Token::StringLiteral: + return stringLiteral(_literal.value()); + default: + return nullptr; + } +} + +RationalNumberType const* TypeProvider::rationalNumber(Literal const& _literal) +{ + solAssert(_literal.token() == Token::Number, ""); + std::tuple validLiteral = RationalNumberType::isValidLiteral(_literal); + if (std::get<0>(validLiteral)) + { + TypePointer compatibleBytesType = nullptr; + if (_literal.isHexNumber()) + { + size_t const digitCount = _literal.valueWithoutUnderscores().length() - 2; + if (digitCount % 2 == 0 && (digitCount / 2) <= 32) + compatibleBytesType = fixedBytes(digitCount / 2); + } + + return rationalNumber(std::get<1>(validLiteral), compatibleBytesType); + } + return nullptr; +} + +StringLiteralType const* TypeProvider::stringLiteral(string const& literal) +{ + auto i = instance().m_stringLiteralTypes.find(literal); + if (i != instance().m_stringLiteralTypes.end()) + return i->second.get(); + else + return instance().m_stringLiteralTypes.emplace(literal, make_unique(literal)).first->second.get(); +} + +FixedPointType const* TypeProvider::fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier) +{ + auto& map = _modifier == FixedPointType::Modifier::Unsigned ? instance().m_ufixedMxN : instance().m_fixedMxN; + + auto i = map.find(make_pair(m, n)); + if (i != map.end()) + return i->second.get(); + + return map.emplace( + make_pair(m, n), + make_unique(m, n, _modifier) + ).first->second.get(); +} + +TupleType const* TypeProvider::tuple(vector members) +{ + if (members.empty()) + return &m_emptyTuple; + + return createAndGet(move(members)); +} + +ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer) +{ + if (_type->location() == _location && _type->isPointer() == _isPointer) + return _type; + + instance().m_generalTypes.emplace_back(_type->copyForLocation(_location, _isPointer)); + return static_cast(instance().m_generalTypes.back().get()); +} + +FunctionType const* TypeProvider::function(FunctionDefinition const& _function, bool _isInternal) +{ + return createAndGet(_function, _isInternal); +} + +FunctionType const* TypeProvider::function(VariableDeclaration const& _varDecl) +{ + return createAndGet(_varDecl); +} + +FunctionType const* TypeProvider::function(EventDefinition const& _def) +{ + return createAndGet(_def); +} + +FunctionType const* TypeProvider::function(FunctionTypeName const& _typeName) +{ + return createAndGet(_typeName); +} + +FunctionType const* TypeProvider::function( + strings const& _parameterTypes, + strings const& _returnParameterTypes, + FunctionType::Kind _kind, + bool _arbitraryParameters, + StateMutability _stateMutability +) +{ + return createAndGet( + _parameterTypes, _returnParameterTypes, + _kind, _arbitraryParameters, _stateMutability + ); +} + +FunctionType const* TypeProvider::function( + TypePointers const& _parameterTypes, + TypePointers const& _returnParameterTypes, + strings _parameterNames, + strings _returnParameterNames, + FunctionType::Kind _kind, + bool _arbitraryParameters, + StateMutability _stateMutability, + Declaration const* _declaration, + bool _gasSet, + bool _valueSet, + bool _bound +) +{ + return createAndGet( + _parameterTypes, + _returnParameterTypes, + _parameterNames, + _returnParameterNames, + _kind, + _arbitraryParameters, + _stateMutability, + _declaration, + _gasSet, + _valueSet, + _bound + ); +} + +RationalNumberType const* TypeProvider::rationalNumber(rational const& _value, Type const* _compatibleBytesType) +{ + return createAndGet(_value, _compatibleBytesType); +} + +ArrayType const* TypeProvider::array(DataLocation _location, bool _isString) +{ + if (_isString) + { + if (_location == DataLocation::Storage) + return stringStorage(); + if (_location == DataLocation::Memory) + return stringMemory(); + } + else + { + if (_location == DataLocation::Storage) + return bytesStorage(); + if (_location == DataLocation::Memory) + return bytesMemory(); + } + return createAndGet(_location, _isString); +} + +ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseType) +{ + return createAndGet(_location, _baseType); +} + +ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseType, u256 const& _length) +{ + return createAndGet(_location, _baseType, _length); +} + +ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper) +{ + return createAndGet(_contractDef, _isSuper); +} + +EnumType const* TypeProvider::enumType(EnumDefinition const& _enumDef) +{ + return createAndGet(_enumDef); +} + +ModuleType const* TypeProvider::module(SourceUnit const& _source) +{ + return createAndGet(_source); +} + +TypeType const* TypeProvider::typeType(Type const* _actualType) +{ + return createAndGet(_actualType); +} + +StructType const* TypeProvider::structType(StructDefinition const& _struct, DataLocation _location) +{ + return createAndGet(_struct, _location); +} + +ModifierType const* TypeProvider::modifier(ModifierDefinition const& _def) +{ + return createAndGet(_def); +} + +MagicType const* TypeProvider::magic(MagicType::Kind _kind) +{ + solAssert(_kind != MagicType::Kind::MetaType, "MetaType is handled separately"); + return m_magics.at(static_cast(_kind)).get(); +} + +MagicType const* TypeProvider::meta(Type const* _type) +{ + solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now."); + return createAndGet(_type); +} + +MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valueType) +{ + return createAndGet(_keyType, _valueType); +} diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h new file mode 100644 index 000000000000..4b18f52e6b3f --- /dev/null +++ b/libsolidity/ast/TypeProvider.h @@ -0,0 +1,224 @@ +/* + 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 . +*/ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +/** + * API for accessing the Solidity Type System. + * + * This is the Solidity Compiler's type provider. Use it to request for types. The caller does + * not own the types. + * + * It is not recommended to explicitly instantiate types unless you really know what and why + * you are doing it. + */ +class TypeProvider +{ +public: + TypeProvider() = default; + TypeProvider(TypeProvider&&) = default; + TypeProvider(TypeProvider const&) = delete; + TypeProvider& operator=(TypeProvider&&) = default; + TypeProvider& operator=(TypeProvider const&) = delete; + ~TypeProvider() = default; + + /// Resets state of this TypeProvider to initial state, wiping all mutable types. + /// This invalidates all dangling pointers to types provided by this TypeProvider. + static void reset(); + + /// @name Factory functions + /// Factory functions that convert an AST @ref TypeName to a Type. + static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type); + + /// Converts a given elementary type name with optional data location + /// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage". + static TypePointer fromElementaryTypeName(std::string const& _name); + + /// @returns boolean type. + static BoolType const* boolean() noexcept { return &m_boolean; } + + static FixedBytesType const* byte() { return fixedBytes(1); } + static FixedBytesType const* fixedBytes(unsigned m) { return m_bytesM.at(m - 1).get(); } + + static ArrayType const* bytesStorage(); + static ArrayType const* bytesMemory(); + static ArrayType const* stringStorage(); + static ArrayType const* stringMemory(); + + /// Constructor for a byte array ("bytes") and string. + static ArrayType const* array(DataLocation _location, bool _isString = false); + + /// Constructor for a dynamically sized array type ("type[]") + static ArrayType const* array(DataLocation _location, Type const* _baseType); + + /// Constructor for a fixed-size array type ("type[20]") + static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length); + + static AddressType const* payableAddress() noexcept { return &m_payableAddress; } + static AddressType const* address() noexcept { return &m_address; } + + static IntegerType const* integer(unsigned _bits, IntegerType::Modifier _modifier) + { + solAssert((_bits % 8) == 0, ""); + if (_modifier == IntegerType::Modifier::Unsigned) + return m_uintM.at(_bits / 8 - 1).get(); + else + return m_intM.at(_bits / 8 - 1).get(); + } + static IntegerType const* uint(unsigned _bits) { return integer(_bits, IntegerType::Modifier::Unsigned); } + + static IntegerType const* uint256() { return uint(256); } + + static FixedPointType const* fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier); + + static StringLiteralType const* stringLiteral(std::string const& literal); + + /// @param members the member types the tuple type must contain. This is passed by value on purspose. + /// @returns a tuple type with the given members. + static TupleType const* tuple(std::vector members); + + static TupleType const* emptyTuple() noexcept { return &m_emptyTuple; } + + static ReferenceType const* withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer); + + /// @returns a copy of @a _type having the same location as this (and is not a pointer type) + /// if _type is a reference type and an unmodified copy of _type otherwise. + /// This function is mostly useful to modify inner types appropriately. + static Type const* withLocationIfReference(DataLocation _location, Type const* _type) + { + if (auto refType = dynamic_cast(_type)) + return withLocation(refType, _location, false); + + return _type; + } + + /// @returns the internally-facing or externally-facing type of a function. + static FunctionType const* function(FunctionDefinition const& _function, bool _isInternal = true); + + /// @returns the accessor function type of a state variable. + static FunctionType const* function(VariableDeclaration const& _varDecl); + + /// @returns the function type of an event. + static FunctionType const* function(EventDefinition const& _event); + + /// @returns the type of a function type name. + static FunctionType const* function(FunctionTypeName const& _typeName); + + /// @returns the function type to be used for a plain type (not derived from a declaration). + static FunctionType const* function( + strings const& _parameterTypes, + strings const& _returnParameterTypes, + FunctionType::Kind _kind = FunctionType::Kind::Internal, + bool _arbitraryParameters = false, + StateMutability _stateMutability = StateMutability::NonPayable + ); + + /// @returns a highly customized FunctionType, use with care. + static FunctionType const* function( + TypePointers const& _parameterTypes, + TypePointers const& _returnParameterTypes, + strings _parameterNames = strings{}, + strings _returnParameterNames = strings{}, + FunctionType::Kind _kind = FunctionType::Kind::Internal, + bool _arbitraryParameters = false, + StateMutability _stateMutability = StateMutability::NonPayable, + Declaration const* _declaration = nullptr, + bool _gasSet = false, + bool _valueSet = false, + bool _bound = false + ); + + /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does + /// not fit any type. + static TypePointer forLiteral(Literal const& _literal); + static RationalNumberType const* rationalNumber(Literal const& _literal); + + static RationalNumberType const* rationalNumber( + rational const& _value, + Type const* _compatibleBytesType = nullptr + ); + + static ContractType const* contract(ContractDefinition const& _contract, bool _isSuper = false); + + static InaccessibleDynamicType const* inaccessibleDynamic() noexcept { return &m_inaccessibleDynamic; } + + /// @returns the type of an enum instance for given definition, there is one distinct type per enum definition. + static EnumType const* enumType(EnumDefinition const& _enum); + + /// @returns special type for imported modules. These mainly give access to their scope via members. + static ModuleType const* module(SourceUnit const& _source); + + static TypeType const* typeType(Type const* _actualType); + + static StructType const* structType(StructDefinition const& _struct, DataLocation _location); + + static ModifierType const* modifier(ModifierDefinition const& _modifierDef); + + static MagicType const* magic(MagicType::Kind _kind); + + static MagicType const* meta(Type const* _type); + + static MappingType const* mapping(Type const* _keyType, Type const* _valueType); + +private: + /// Global TypeProvider instance. + static TypeProvider& instance() + { + static TypeProvider _provider; + return _provider; + } + + template + static inline T const* createAndGet(Args&& ... _args); + + static BoolType const m_boolean; + static InaccessibleDynamicType const m_inaccessibleDynamic; + + /// These are lazy-initialized because they depend on `byte` being available. + static std::unique_ptr m_bytesStorage; + static std::unique_ptr m_bytesMemory; + static std::unique_ptr m_stringStorage; + static std::unique_ptr m_stringMemory; + + static TupleType const m_emptyTuple; + static AddressType const m_payableAddress; + static AddressType const m_address; + static std::array, 32> const m_intM; + static std::array, 32> const m_uintM; + static std::array, 32> const m_bytesM; + static std::array, 4> const m_magics; ///< MagicType's except MetaType + + std::map, std::unique_ptr> m_ufixedMxN{}; + std::map, std::unique_ptr> m_fixedMxN{}; + std::map> m_stringLiteralTypes{}; + std::vector> m_generalTypes{}; +}; + +} // namespace solidity +} // namespace dev diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index ae2cb2966edf..e58fa1547d63 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -141,12 +142,32 @@ BoolResult fitsIntegerType(bigint const& _value, IntegerType const& _type) /// if _signed is true. bool fitsIntoBits(bigint const& _value, unsigned _bits, bool _signed) { - return fitsIntegerType(_value, IntegerType( + return fitsIntegerType(_value, *TypeProvider::integer( _bits, _signed ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned )); } +Result transformParametersToExternal(TypePointers const& _parameters, bool _inLibrary) +{ + TypePointers transformed; + + for (auto const& type: _parameters) + { + if (TypePointer ext = type->interfaceType(_inLibrary).get()) + transformed.push_back(ext); + else + return Result::err("Parameter should have external type."); + } + + return transformed; +} + +} + +void Type::clearCache() const +{ + m_members.clear(); } void StorageOffsets::computeOffsets(TypePointers const& _types) @@ -156,7 +177,7 @@ void StorageOffsets::computeOffsets(TypePointers const& _types) map> offsets; for (size_t i = 0; i < _types.size(); ++i) { - TypePointer const& type = _types[i]; + Type const* type = _types[i]; if (!type->canBeStored()) continue; if (byteOffset + type->storageBytes() > 32) @@ -237,7 +258,7 @@ string identifierList(Range const&& _list) return parenthesizeIdentifier(boost::algorithm::join(_list, ",")); } -string richIdentifier(TypePointer const& _type) +string richIdentifier(Type const* _type) { return _type ? _type->richIdentifier() : ""; } @@ -247,12 +268,12 @@ string identifierList(vector const& _list) return identifierList(_list | boost::adaptors::transformed(richIdentifier)); } -string identifierList(TypePointer const& _type) +string identifierList(Type const* _type) { return parenthesizeIdentifier(richIdentifier(_type)); } -string identifierList(TypePointer const& _type1, TypePointer const& _type2) +string identifierList(Type const* _type1, Type const* _type2) { TypePointers list; list.push_back(_type1); @@ -289,124 +310,16 @@ string Type::identifier() const return ret; } -TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) -{ - solAssert(TokenTraits::isElementaryTypeName(_type.token()), - "Expected an elementary type name but got " + _type.toString() - ); - - Token token = _type.token(); - unsigned m = _type.firstNumber(); - unsigned n = _type.secondNumber(); - - switch (token) - { - case Token::IntM: - return make_shared(m, IntegerType::Modifier::Signed); - case Token::UIntM: - return make_shared(m, IntegerType::Modifier::Unsigned); - case Token::BytesM: - return make_shared(m); - case Token::FixedMxN: - return make_shared(m, n, FixedPointType::Modifier::Signed); - case Token::UFixedMxN: - return make_shared(m, n, FixedPointType::Modifier::Unsigned); - case Token::Int: - return make_shared(256, IntegerType::Modifier::Signed); - case Token::UInt: - return make_shared(256, IntegerType::Modifier::Unsigned); - case Token::Fixed: - return make_shared(128, 18, FixedPointType::Modifier::Signed); - case Token::UFixed: - return make_shared(128, 18, FixedPointType::Modifier::Unsigned); - case Token::Byte: - return make_shared(1); - case Token::Address: - return make_shared(StateMutability::NonPayable); - case Token::Bool: - return make_shared(); - case Token::Bytes: - return make_shared(DataLocation::Storage); - case Token::String: - return make_shared(DataLocation::Storage, true); - //no types found - default: - solAssert( - false, - "Unable to convert elementary typename " + _type.toString() + " to type." - ); - } -} - -TypePointer Type::fromElementaryTypeName(string const& _name) -{ - vector nameParts; - boost::split(nameParts, _name, boost::is_any_of(" ")); - solAssert(nameParts.size() == 1 || nameParts.size() == 2, "Cannot parse elementary type: " + _name); - Token token; - unsigned short firstNum, secondNum; - tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(nameParts[0]); - auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum)); - if (auto* ref = dynamic_cast(t.get())) - { - DataLocation location = DataLocation::Storage; - if (nameParts.size() == 2) - { - if (nameParts[1] == "storage") - location = DataLocation::Storage; - else if (nameParts[1] == "calldata") - location = DataLocation::CallData; - else if (nameParts[1] == "memory") - location = DataLocation::Memory; - else - solAssert(false, "Unknown data location: " + nameParts[1]); - } - return ref->copyForLocation(location, true); - } - else if (t->category() == Type::Category::Address) - { - if (nameParts.size() == 2) - { - if (nameParts[1] == "payable") - return make_shared(StateMutability::Payable); - else - solAssert(false, "Invalid state mutability for address type: " + nameParts[1]); - } - return make_shared(StateMutability::NonPayable); - } - else - { - solAssert(nameParts.size() == 1, "Storage location suffix only allowed for reference types"); - return t; - } -} - -TypePointer Type::forLiteral(Literal const& _literal) -{ - switch (_literal.token()) - { - case Token::TrueLiteral: - case Token::FalseLiteral: - return make_shared(); - case Token::Number: - return RationalNumberType::forLiteral(_literal); - case Token::StringLiteral: - return make_shared(_literal); - default: - return TypePointer(); - } -} - -TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) +TypePointer Type::commonType(Type const* _a, Type const* _b) { if (!_a || !_b) - return TypePointer(); + return nullptr; else if (_a->mobileType() && _b->isImplicitlyConvertibleTo(*_a->mobileType())) return _a->mobileType(); else if (_b->mobileType() && _a->isImplicitlyConvertibleTo(*_b->mobileType())) return _b->mobileType(); else - return TypePointer(); + return nullptr; } MemberList const& Type::members(ContractDefinition const* _currentScope) const @@ -434,24 +347,24 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage)) return encodingType; TypePointer baseType = encodingType; - while (auto const* arrayType = dynamic_cast(baseType.get())) + while (auto const* arrayType = dynamic_cast(baseType)) baseType = arrayType->baseType(); - if (dynamic_cast(baseType.get())) + if (dynamic_cast(baseType)) if (!_encoderV2) - return TypePointer(); + return nullptr; return encodingType; } MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition const& _scope) { // Normalise data location of type. - TypePointer type = ReferenceType::copyForLocationIfReference(DataLocation::Storage, _type.shared_from_this()); + TypePointer type = TypeProvider::withLocationIfReference(DataLocation::Storage, &_type); set seenFunctions; MemberList::MemberMap members; for (ContractDefinition const* contract: _scope.annotation().linearizedBaseContracts) for (UsingForDirective const* ufd: contract->usingForDirectives()) { - if (ufd->typeName() && *type != *ReferenceType::copyForLocationIfReference( + if (ufd->typeName() && *type != *TypeProvider::withLocationIfReference( DataLocation::Storage, ufd->typeName()->annotation().type )) @@ -528,16 +441,16 @@ u256 AddressType::literalValue(Literal const* _literal) const TypeResult AddressType::unaryOperatorResult(Token _operator) const { - return _operator == Token::Delete ? make_shared() : TypePointer(); + return _operator == Token::Delete ? TypeProvider::emptyTuple() : nullptr; } -TypeResult AddressType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult AddressType::binaryOperatorResult(Token _operator, Type const* _other) const { if (!TokenTraits::isCompareOp(_operator)) return TypeResult::err("Arithmetic operations on addresses are not supported. Convert to integer first before using them."); - return Type::commonType(shared_from_this(), _other); + return Type::commonType(this, _other); } bool AddressType::operator==(Type const& _other) const @@ -551,16 +464,16 @@ bool AddressType::operator==(Type const& _other) const MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) const { MemberList::MemberMap members = { - {"balance", make_shared(256)}, - {"call", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)}, - {"callcode", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, - {"delegatecall", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, false)}, - {"staticcall", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)} + {"balance", TypeProvider::uint256()}, + {"call", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)}, + {"callcode", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, + {"delegatecall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, false, StateMutability::NonPayable)}, + {"staticcall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)} }; if (m_stateMutability == StateMutability::Payable) { - members.emplace_back(MemberList::Member{"send", make_shared(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}); - members.emplace_back(MemberList::Member{"transfer", make_shared(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}); + members.emplace_back(MemberList::Member{"send", TypeProvider::function(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send, false, StateMutability::NonPayable)}); + members.emplace_back(MemberList::Member{"transfer", TypeProvider::function(strings{"uint"}, strings(), FunctionType::Kind::Transfer, false, StateMutability::NonPayable)}); } return members; } @@ -632,11 +545,11 @@ TypeResult IntegerType::unaryOperatorResult(Token _operator) const { // "delete" is ok for all integer types if (_operator == Token::Delete) - return TypeResult{make_shared()}; + return TypeResult{TypeProvider::emptyTuple()}; // we allow -, ++ and -- else if (_operator == Token::Sub || _operator == Token::Inc || _operator == Token::Dec || _operator == Token::BitNot) - return TypeResult{shared_from_this()}; + return TypeResult{this}; else return TypeResult::err(""); } @@ -671,40 +584,40 @@ bigint IntegerType::maxValue() const return (bigint(1) << m_bits) - 1; } -TypeResult IntegerType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other) const { if ( _other->category() != Category::RationalNumber && _other->category() != Category::FixedPoint && _other->category() != category() ) - return TypePointer(); + return nullptr; if (TokenTraits::isShiftOp(_operator)) { // Shifts are not symmetric with respect to the type if (isValidShiftAndAmountType(_operator, *_other)) - return shared_from_this(); + return this; else - return TypePointer(); + return nullptr; } - auto commonType = Type::commonType(shared_from_this(), _other); //might be an integer or fixed point + auto commonType = Type::commonType(this, _other); //might be an integer or fixed point if (!commonType) - return TypePointer(); + return nullptr; // All integer types can be compared if (TokenTraits::isCompareOp(_operator)) return commonType; if (TokenTraits::isBooleanOp(_operator)) - return TypePointer(); - if (auto intType = dynamic_pointer_cast(commonType)) + return nullptr; + if (auto intType = dynamic_cast(commonType)) { if (Token::Exp == _operator && intType->isSigned()) return TypeResult::err("Exponentiation is not allowed for signed integer types."); } - else if (auto fixType = dynamic_pointer_cast(commonType)) + else if (dynamic_cast(commonType)) if (Token::Exp == _operator) - return TypePointer(); + return nullptr; return commonType; } @@ -749,15 +662,15 @@ TypeResult FixedPointType::unaryOperatorResult(Token _operator) const { case Token::Delete: // "delete" is ok for all fixed types - return TypeResult(make_shared()); + return TypeResult{TypeProvider::emptyTuple()}; case Token::Add: case Token::Sub: case Token::Inc: case Token::Dec: // for fixed, we allow +, -, ++ and -- - return shared_from_this(); + return this; default: - return TypePointer(); + return nullptr; } } @@ -792,24 +705,24 @@ bigint FixedPointType::minIntegerValue() const return bigint(0); } -TypeResult FixedPointType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FixedPointType::binaryOperatorResult(Token _operator, Type const* _other) const { - auto commonType = Type::commonType(shared_from_this(), _other); + auto commonType = Type::commonType(this, _other); if (!commonType) - return TypePointer(); + return nullptr; // All fixed types can be compared if (TokenTraits::isCompareOp(_operator)) return commonType; if (TokenTraits::isBitOp(_operator) || TokenTraits::isBooleanOp(_operator) || _operator == Token::Exp) - return TypePointer(); + return nullptr; return commonType; } -std::shared_ptr FixedPointType::asIntegerType() const +IntegerType const* FixedPointType::asIntegerType() const { - return make_shared(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned); + return TypeProvider::integer(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned); } tuple RationalNumberType::parseRational(string const& _value) @@ -855,25 +768,6 @@ tuple RationalNumberType::parseRational(string const& _value) } } -TypePointer RationalNumberType::forLiteral(Literal const& _literal) -{ - solAssert(_literal.token() == Token::Number, ""); - tuple validLiteral = isValidLiteral(_literal); - if (get<0>(validLiteral)) - { - TypePointer compatibleBytesType; - if (_literal.isHexNumber()) - { - size_t const digitCount = _literal.valueWithoutUnderscores().length() - 2; - if (digitCount % 2 == 0 && (digitCount / 2) <= 32) - compatibleBytesType = make_shared(digitCount / 2); - } - - return make_shared(get<1>(validLiteral), compatibleBytesType); - } - return TypePointer(); -} - tuple RationalNumberType::isValidLiteral(Literal const& _literal) { rational value; @@ -1030,7 +924,7 @@ TypeResult RationalNumberType::unaryOperatorResult(Token _operator) const { case Token::BitNot: if (isFractional()) - return TypePointer(); + return nullptr; value = ~m_value.numerator(); break; case Token::Add: @@ -1040,24 +934,24 @@ TypeResult RationalNumberType::unaryOperatorResult(Token _operator) const value = -(m_value); break; case Token::After: - return shared_from_this(); + return this; default: - return TypePointer(); + return nullptr; } - return TypeResult(make_shared(value)); + return TypeResult{TypeProvider::rationalNumber(value)}; } -TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* _other) const { if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint) { - auto commonType = Type::commonType(shared_from_this(), _other); + auto commonType = Type::commonType(this, _other); if (!commonType) - return TypePointer(); + return nullptr; return commonType->binaryOperatorResult(_operator, _other); } else if (_other->category() != category()) - return TypePointer(); + return nullptr; RationalNumberType const& other = dynamic_cast(*_other); if (TokenTraits::isCompareOp(_operator)) @@ -1068,7 +962,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer TypePointer thisMobile = mobileType(); TypePointer otherMobile = other.mobileType(); if (!thisMobile || !otherMobile) - return TypePointer(); + return nullptr; return thisMobile->binaryOperatorResult(_operator, otherMobile); } else @@ -1080,17 +974,17 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer //bit operations will only be enabled for integers and fixed types that resemble integers case Token::BitOr: if (fractional) - return TypePointer(); + return nullptr; value = m_value.numerator() | other.m_value.numerator(); break; case Token::BitXor: if (fractional) - return TypePointer(); + return nullptr; value = m_value.numerator() ^ other.m_value.numerator(); break; case Token::BitAnd: if (fractional) - return TypePointer(); + return nullptr; value = m_value.numerator() & other.m_value.numerator(); break; case Token::Add: @@ -1104,13 +998,13 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer break; case Token::Div: if (other.m_value == rational(0)) - return TypePointer(); + return nullptr; else value = m_value / other.m_value; break; case Token::Mod: if (other.m_value == rational(0)) - return TypePointer(); + return nullptr; else if (fractional) { rational tempValue = m_value / other.m_value; @@ -1122,7 +1016,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer case Token::Exp: { if (other.isFractional()) - return TypePointer(); + return nullptr; solAssert(other.m_value.denominator() == 1, ""); bigint const& exp = other.m_value.numerator(); @@ -1140,7 +1034,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer else { if (abs(exp) > numeric_limits::max()) - return TypePointer(); // This will need too much memory to represent. + return nullptr; // This will need too much memory to represent. uint32_t absExp = bigint(abs(exp)).convert_to(); @@ -1170,18 +1064,18 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer case Token::SHL: { if (fractional) - return TypePointer(); + return nullptr; else if (other.m_value < 0) - return TypePointer(); + return nullptr; else if (other.m_value > numeric_limits::max()) - return TypePointer(); + return nullptr; if (m_value.numerator() == 0) value = 0; else { uint32_t exponent = other.m_value.numerator().convert_to(); if (!fitsPrecisionBase2(abs(m_value.numerator()), exponent)) - return TypePointer(); + return nullptr; value = m_value.numerator() * boost::multiprecision::pow(bigint(2), exponent); } break; @@ -1191,11 +1085,11 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer case Token::SAR: { if (fractional) - return TypePointer(); + return nullptr; else if (other.m_value < 0) - return TypePointer(); + return nullptr; else if (other.m_value > numeric_limits::max()) - return TypePointer(); + return nullptr; if (m_value.numerator() == 0) value = 0; else @@ -1221,14 +1115,14 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer break; } default: - return TypePointer(); + return nullptr; } // verify that numerator and denominator fit into 4096 bit after every operation if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096) return TypeResult::err("Precision of rational constants is limited to 4096 bits."); - return TypeResult(make_shared(value)); + return TypeResult{TypeProvider::rationalNumber(value)}; } } @@ -1310,7 +1204,7 @@ TypePointer RationalNumberType::mobileType() const return fixedPointType(); } -shared_ptr RationalNumberType::integerType() const +IntegerType const* RationalNumberType::integerType() const { solAssert(!isFractional(), "integerType() called for fractional number."); bigint value = m_value.numerator(); @@ -1318,15 +1212,15 @@ shared_ptr RationalNumberType::integerType() const if (negative) // convert to positive number of same bit requirements value = ((0 - value) - 1) << 1; if (value > u256(-1)) - return shared_ptr(); + return nullptr; else - return make_shared( + return TypeProvider::integer( max(bytesRequired(value), 1u) * 8, negative ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned ); } -shared_ptr RationalNumberType::fixedPointType() const +FixedPointType const* RationalNumberType::fixedPointType() const { bool negative = (m_value < 0); unsigned fractionalDigits = 0; @@ -1342,7 +1236,8 @@ shared_ptr RationalNumberType::fixedPointType() const } if (value > maxValue) - return shared_ptr(); + return nullptr; + // This means we round towards zero for positive and negative values. bigint v = value.numerator() / value.denominator(); @@ -1352,12 +1247,12 @@ shared_ptr RationalNumberType::fixedPointType() const v = (v - 1) << 1; if (v > u256(-1)) - return shared_ptr(); + return nullptr; unsigned totalBits = max(bytesRequired(v), 1u) * 8; solAssert(totalBits <= 256, ""); - return make_shared( + return TypeProvider::fixedPoint( totalBits, fractionalDigits, negative ? FixedPointType::Modifier::Signed : FixedPointType::Modifier::Unsigned ); @@ -1368,6 +1263,11 @@ StringLiteralType::StringLiteralType(Literal const& _literal): { } +StringLiteralType::StringLiteralType(string const& _value): + m_value{_value} +{ +} + BoolResult StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (auto fixedBytes = dynamic_cast(&_convertTo)) @@ -1407,7 +1307,7 @@ std::string StringLiteralType::toString(bool) const TypePointer StringLiteralType::mobileType() const { - return make_shared(DataLocation::Memory, true); + return TypeProvider::stringMemory(); } bool StringLiteralType::isValidUTF8() const @@ -1443,37 +1343,37 @@ TypeResult FixedBytesType::unaryOperatorResult(Token _operator) const { // "delete" and "~" is okay for FixedBytesType if (_operator == Token::Delete) - return TypeResult(make_shared()); + return TypeResult{TypeProvider::emptyTuple()}; else if (_operator == Token::BitNot) - return shared_from_this(); + return this; - return TypePointer(); + return nullptr; } -TypeResult FixedBytesType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FixedBytesType::binaryOperatorResult(Token _operator, Type const* _other) const { if (TokenTraits::isShiftOp(_operator)) { if (isValidShiftAndAmountType(_operator, *_other)) - return shared_from_this(); + return this; else - return TypePointer(); + return nullptr; } - auto commonType = dynamic_pointer_cast(Type::commonType(shared_from_this(), _other)); + auto commonType = dynamic_cast(Type::commonType(this, _other)); if (!commonType) - return TypePointer(); + return nullptr; // FixedBytes can be compared and have bitwise operators applied to them if (TokenTraits::isCompareOp(_operator) || TokenTraits::isBitOp(_operator)) return TypeResult(commonType); - return TypePointer(); + return nullptr; } MemberList::MemberMap FixedBytesType::nativeMembers(ContractDefinition const*) const { - return MemberList::MemberMap{MemberList::Member{"length", make_shared(8)}}; + return MemberList::MemberMap{MemberList::Member{"length", TypeProvider::uint(8)}}; } string FixedBytesType::richIdentifier() const @@ -1503,18 +1403,32 @@ u256 BoolType::literalValue(Literal const* _literal) const TypeResult BoolType::unaryOperatorResult(Token _operator) const { if (_operator == Token::Delete) - return TypeResult(make_shared()); - return (_operator == Token::Not) ? shared_from_this() : TypePointer(); + return TypeProvider::emptyTuple(); + else if (_operator == Token::Not) + return this; + else + return nullptr; } -TypeResult BoolType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult BoolType::binaryOperatorResult(Token _operator, Type const* _other) const { if (category() != _other->category()) - return TypePointer(); + return nullptr; if (_operator == Token::Equal || _operator == Token::NotEqual || _operator == Token::And || _operator == Token::Or) return _other; else - return TypePointer(); + return nullptr; +} + +Type const* ContractType::encodingType() const +{ + if (isSuper()) + return nullptr; + + if (isPayable()) + return TypeProvider::payableAddress(); + else + return TypeProvider::address(); } BoolResult ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -1550,38 +1464,39 @@ bool ContractType::isPayable() const TypeResult ContractType::unaryOperatorResult(Token _operator) const { if (isSuper()) - return TypePointer{}; - return _operator == Token::Delete ? make_shared() : TypePointer(); + return nullptr; + else if (_operator == Token::Delete) + return TypeProvider::emptyTuple(); + else + return nullptr; +} + +Type const* ReferenceType::withLocation(DataLocation _location, bool _isPointer) const +{ + return TypeProvider::withLocation(this, _location, _isPointer); } TypeResult ReferenceType::unaryOperatorResult(Token _operator) const { if (_operator != Token::Delete) - return TypePointer(); + return nullptr; // delete can be used on everything except calldata references or storage pointers // (storage references are ok) switch (location()) { case DataLocation::CallData: - return TypePointer(); + return nullptr; case DataLocation::Memory: - return TypeResult(make_shared()); + return TypeProvider::emptyTuple(); case DataLocation::Storage: - return m_isPointer ? TypePointer() : make_shared(); + return m_isPointer ? nullptr : TypeProvider::emptyTuple(); } - return TypePointer(); -} - -TypePointer ReferenceType::copyForLocationIfReference(DataLocation _location, TypePointer const& _type) -{ - if (auto type = dynamic_cast(_type.get())) - return type->copyForLocation(_location, false); - return _type; + return nullptr; } -TypePointer ReferenceType::copyForLocationIfReference(TypePointer const& _type) const +TypePointer ReferenceType::copyForLocationIfReference(Type const* _type) const { - return copyForLocationIfReference(m_location, _type); + return TypeProvider::withLocationIfReference(m_location, _type); } string ReferenceType::stringForReferencePart() const @@ -1619,6 +1534,21 @@ string ReferenceType::identifierLocationSuffix() const return id; } +ArrayType::ArrayType(DataLocation _location, bool _isString): + ReferenceType(_location), + m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), + m_baseType{TypeProvider::byte()} +{ +} + +void ArrayType::clearCache() const +{ + Type::clearCache(); + + m_interfaceType.reset(); + m_interfaceType_library.reset(); +} + BoolResult ArrayType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) @@ -1646,8 +1576,8 @@ BoolResult ArrayType::isImplicitlyConvertibleTo(Type const& _convertTo) const // require that the base type is the same, not only convertible. // This disallows assignment of nested dynamic arrays from storage to memory for now. if ( - *copyForLocationIfReference(location(), baseType()) != - *copyForLocationIfReference(location(), convertTo.baseType()) + *TypeProvider::withLocationIfReference(location(), baseType()) != + *TypeProvider::withLocationIfReference(location(), convertTo.baseType()) ) return false; if (isDynamicallySized() != convertTo.isDynamicallySized()) @@ -1714,7 +1644,7 @@ bool ArrayType::operator==(Type const& _other) const bool ArrayType::validForCalldata() const { - if (auto arrayBaseType = dynamic_cast(baseType().get())) + if (auto arrayBaseType = dynamic_cast(baseType())) if (!arrayBaseType->validForCalldata()) return false; return unlimitedCalldataEncodedSize(true) <= numeric_limits::max(); @@ -1831,17 +1761,17 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const MemberList::MemberMap members; if (!isString()) { - members.emplace_back("length", make_shared(256)); + members.emplace_back("length", TypeProvider::uint256()); if (isDynamicallySized() && location() == DataLocation::Storage) { - members.emplace_back("push", make_shared( + members.emplace_back("push", TypeProvider::function( TypePointers{baseType()}, - TypePointers{make_shared(256)}, + TypePointers{TypeProvider::uint256()}, strings{string()}, strings{string()}, isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush )); - members.emplace_back("pop", make_shared( + members.emplace_back("pop", TypeProvider::function( TypePointers{}, TypePointers{}, strings{}, @@ -1856,17 +1786,17 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const TypePointer ArrayType::encodingType() const { if (location() == DataLocation::Storage) - return make_shared(256); + return TypeProvider::uint256(); else - return this->copyForLocation(DataLocation::Memory, true); + return TypeProvider::withLocation(this, DataLocation::Memory, true); } TypePointer ArrayType::decodingType() const { if (location() == DataLocation::Storage) - return make_shared(256); + return TypeProvider::uint256(); else - return shared_from_this(); + return this; } TypeResult ArrayType::interfaceType(bool _inLibrary) const @@ -1886,13 +1816,13 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const result = baseInterfaceType; } else if (_inLibrary && location() == DataLocation::Storage) - result = shared_from_this(); + result = this; else if (m_arrayKind != ArrayKind::Ordinary) - result = this->copyForLocation(DataLocation::Memory, true); + result = TypeProvider::withLocation(this, DataLocation::Memory, true); else if (isDynamicallySized()) - result = TypePointer{make_shared(DataLocation::Memory, baseInterfaceType)}; + result = TypeProvider::array(DataLocation::Memory, baseInterfaceType); else - result = TypePointer{make_shared(DataLocation::Memory, baseInterfaceType, m_length)}; + result = TypeProvider::array(DataLocation::Memory, baseInterfaceType, m_length); if (_inLibrary) m_interfaceType_library = result; @@ -1911,9 +1841,9 @@ u256 ArrayType::memorySize() const return u256(size); } -TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const +std::unique_ptr ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const { - auto copy = make_shared(_location); + auto copy = make_unique(_location); copy->m_isPointer = _isPointer; copy->m_arrayKind = m_arrayKind; copy->m_baseType = copy->copyForLocationIfReference(m_baseType); @@ -1964,13 +1894,13 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con if (!function->isVisibleInDerivedContracts() || !function->isImplemented()) continue; - auto functionType = make_shared(*function, true); + auto functionType = TypeProvider::function(*function, true); bool functionWithEqualArgumentsFound = false; for (auto const& member: members) { if (member.name != function->name()) continue; - auto memberType = dynamic_cast(member.type.get()); + auto memberType = dynamic_cast(member.type); solAssert(!!memberType, "Override changes type."); if (!memberType->hasEqualParameterTypes(*functionType)) continue; @@ -1993,7 +1923,7 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con return members; } -shared_ptr const& ContractType::newExpressionType() const +FunctionType const* ContractType::newExpressionType() const { if (!m_constructorType) m_constructorType = FunctionType::newExpressionType(m_contract); @@ -2020,6 +1950,22 @@ vector> ContractType::stateVar return variablesAndOffsets; } +void StructType::clearCache() const +{ + Type::clearCache(); + + m_interfaceType.reset(); + m_interfaceType_library.reset(); +} + +Type const* StructType::encodingType() const +{ + if (location() != DataLocation::Storage) + return this; + + return TypeProvider::uint256(); +} + BoolResult StructType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) @@ -2165,10 +2111,10 @@ TypeResult StructType::interfaceType(bool _inLibrary) const return; } - Type const* memberType = variable->annotation().type.get(); + Type const* memberType = variable->annotation().type; while (dynamic_cast(memberType)) - memberType = dynamic_cast(memberType)->baseType().get(); + memberType = dynamic_cast(memberType)->baseType(); if (StructType const* innerStruct = dynamic_cast(memberType)) if ( @@ -2205,9 +2151,9 @@ TypeResult StructType::interfaceType(bool _inLibrary) const if (!result.message().empty()) m_interfaceType_library = result; else if (location() == DataLocation::Storage) - m_interfaceType_library = shared_from_this(); + m_interfaceType_library = this; else - m_interfaceType_library = copyForLocation(DataLocation::Memory, true); + m_interfaceType_library = TypeProvider::withLocation(this, DataLocation::Memory, true); if (m_recursive.get()) m_interfaceType = TypeResult::err(recursiveErrMsg); @@ -2220,14 +2166,14 @@ TypeResult StructType::interfaceType(bool _inLibrary) const else if (!result.message().empty()) m_interfaceType = result; else - m_interfaceType = copyForLocation(DataLocation::Memory, true); + m_interfaceType = TypeProvider::withLocation(this, DataLocation::Memory, true); return *m_interfaceType; } -TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const +std::unique_ptr StructType::copyForLocation(DataLocation _location, bool _isPointer) const { - auto copy = make_shared(m_struct, _location); + auto copy = make_unique(m_struct, _location); copy->m_isPointer = _isPointer; return copy; } @@ -2264,11 +2210,11 @@ FunctionTypePointer StructType::constructorType() const if (!member.type->canLiveOutsideStorage()) continue; paramNames.push_back(member.name); - paramTypes.push_back(copyForLocationIfReference(DataLocation::Memory, member.type)); + paramTypes.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, member.type)); } - return make_shared( + return TypeProvider::function( paramTypes, - TypePointers{copyForLocation(DataLocation::Memory, false)}, + TypePointers{TypeProvider::withLocation(this, DataLocation::Memory, false)}, paramNames, strings(1, ""), FunctionType::Kind::Internal @@ -2312,9 +2258,14 @@ set StructType::membersMissingInMemory() const return missing; } +TypePointer EnumType::encodingType() const +{ + return TypeProvider::uint(8 * storageBytes()); +} + TypeResult EnumType::unaryOperatorResult(Token _operator) const { - return _operator == Token::Delete ? make_shared() : TypePointer(); + return _operator == Token::Delete ? TypeProvider::emptyTuple() : nullptr; } string EnumType::richIdentifier() const @@ -2437,16 +2388,16 @@ TypePointer TupleType::mobileType() const { auto mt = c->mobileType(); if (!mt) - return TypePointer(); + return nullptr; mobiles.push_back(mt); } else - mobiles.push_back(TypePointer()); + mobiles.push_back(nullptr); } - return make_shared(mobiles); + return TypeProvider::tuple(move(mobiles)); } -TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) const +TypePointer TupleType::closestTemporaryType(Type const* _targetType) const { solAssert(!!_targetType, ""); TypePointers const& targetComponents = dynamic_cast(*_targetType).components(); @@ -2460,7 +2411,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons solAssert(tempComponents[i], ""); } } - return make_shared(tempComponents); + return TypeProvider::tuple(move(tempComponents)); } FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): @@ -2502,36 +2453,36 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): while (true) { - if (auto mappingType = dynamic_cast(returnType.get())) + if (auto mappingType = dynamic_cast(returnType)) { m_parameterTypes.push_back(mappingType->keyType()); m_parameterNames.emplace_back(""); returnType = mappingType->valueType(); } - else if (auto arrayType = dynamic_cast(returnType.get())) + else if (auto arrayType = dynamic_cast(returnType)) { if (arrayType->isByteArray()) // Return byte arrays as whole. break; returnType = arrayType->baseType(); m_parameterNames.emplace_back(""); - m_parameterTypes.push_back(make_shared(256)); + m_parameterTypes.push_back(TypeProvider::uint256()); } else break; } - if (auto structType = dynamic_cast(returnType.get())) + if (auto structType = dynamic_cast(returnType)) { for (auto const& member: structType->members(nullptr)) { solAssert(member.type, ""); if (member.type->category() != Category::Mapping) { - if (auto arrayType = dynamic_cast(member.type.get())) + if (auto arrayType = dynamic_cast(member.type)) if (!arrayType->isByteArray()) continue; - m_returnParameterTypes.push_back(ReferenceType::copyForLocationIfReference( + m_returnParameterTypes.push_back(TypeProvider::withLocationIfReference( DataLocation::Memory, member.type )); @@ -2541,7 +2492,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): } else { - m_returnParameterTypes.push_back(ReferenceType::copyForLocationIfReference( + m_returnParameterTypes.push_back(TypeProvider::withLocationIfReference( DataLocation::Memory, returnType )); @@ -2638,9 +2589,9 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c stateMutability = StateMutability::Payable; } - return make_shared( + return TypeProvider::function( parameters, - TypePointers{make_shared(_contract)}, + TypePointers{TypeProvider::contract(_contract)}, parameterNames, strings{""}, Kind::Creation, @@ -2670,7 +2621,7 @@ TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const ) for (auto& param: returnParameterTypes) if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage)) - param = make_shared(); + param = TypeProvider::inaccessibleDynamic(); return returnParameterTypes; } @@ -2753,7 +2704,7 @@ bool FunctionType::operator==(Type const& _other) const BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (m_kind == Kind::External && _convertTo == AddressType::address()) + if (m_kind == Kind::External && _convertTo == *TypeProvider::address()) return true; return _convertTo.category() == category(); } @@ -2786,18 +2737,18 @@ BoolResult FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const TypeResult FunctionType::unaryOperatorResult(Token _operator) const { if (_operator == Token::Delete) - return TypeResult(make_shared()); - return TypePointer(); + return TypeResult(TypeProvider::emptyTuple()); + return nullptr; } -TypeResult FunctionType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FunctionType::binaryOperatorResult(Token _operator, Type const* _other) const { if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) - return TypePointer(); + return nullptr; FunctionType const& other = dynamic_cast(*_other); if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1) - return commonType(shared_from_this(), _other); - return TypePointer(); + return commonType(this, _other); + return nullptr; } string FunctionType::canonicalName() const @@ -2904,30 +2855,25 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const { // Note that m_declaration might also be a state variable! solAssert(m_declaration, "Declaration needed to determine interface function type."); - bool isLibraryFunction = dynamic_cast(*m_declaration->scope()).isLibrary(); + bool isLibraryFunction = kind() != Kind::Event && dynamic_cast(*m_declaration->scope()).isLibrary(); - TypePointers paramTypes; - TypePointers retParamTypes; + Result paramTypes = + transformParametersToExternal(m_parameterTypes, isLibraryFunction); + + if (!paramTypes.message().empty()) + return FunctionTypePointer(); + + Result retParamTypes = + transformParametersToExternal(m_returnParameterTypes, isLibraryFunction); + + if (!retParamTypes.message().empty()) + return FunctionTypePointer(); - for (auto type: m_parameterTypes) - { - if (auto ext = type->interfaceType(isLibraryFunction).get()) - paramTypes.push_back(ext); - else - return FunctionTypePointer(); - } - for (auto type: m_returnParameterTypes) - { - if (auto ext = type->interfaceType(isLibraryFunction).get()) - retParamTypes.push_back(ext); - else - return FunctionTypePointer(); - } auto variable = dynamic_cast(m_declaration); - if (variable && retParamTypes.empty()) + if (variable && retParamTypes.get().empty()) return FunctionTypePointer(); - return make_shared( + return TypeProvider::function( paramTypes, retParamTypes, m_parameterNames, @@ -2952,13 +2898,13 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { MemberList::MemberMap members; if (m_kind == Kind::External) - members.emplace_back("selector", make_shared(4)); + members.emplace_back("selector", TypeProvider::fixedBytes(4)); if (m_kind != Kind::BareDelegateCall) { if (isPayable()) members.emplace_back( "value", - make_shared( + TypeProvider::function( parseElementaryTypeVector({"uint"}), TypePointers{copyAndSetGasOrValue(false, true)}, strings(1, ""), @@ -2975,7 +2921,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con if (m_kind != Kind::Creation) members.emplace_back( "gas", - make_shared( + TypeProvider::function( parseElementaryTypeVector({"uint"}), TypePointers{copyAndSetGasOrValue(true, false)}, strings(1, ""), @@ -2999,22 +2945,22 @@ TypePointer FunctionType::encodingType() const { // Only external functions can be encoded, internal functions cannot leave code boundaries. if (m_kind == Kind::External) - return shared_from_this(); + return this; else - return TypePointer(); + return nullptr; } TypeResult FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_kind == Kind::External) - return shared_from_this(); + return this; else return TypeResult::err("Internal type is not allowed for public or external functions."); } bool FunctionType::canTakeArguments( FuncCallArguments const& _arguments, - TypePointer const& _selfType + Type const* _selfType ) const { solAssert(!bound() || _selfType, ""); @@ -3032,7 +2978,7 @@ bool FunctionType::canTakeArguments( _arguments.types.cbegin(), _arguments.types.cend(), paramTypes.cbegin(), - [](TypePointer const& argumentType, TypePointer const& parameterType) + [](Type const* argumentType, Type const* parameterType) { return argumentType->isImplicitlyConvertibleTo(*parameterType); } @@ -3069,7 +3015,7 @@ bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const m_parameterTypes.cbegin(), m_parameterTypes.cend(), _other.m_parameterTypes.cbegin(), - [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; } + [](Type const* _a, Type const* _b) -> bool { return *_a == *_b; } ); } @@ -3081,7 +3027,7 @@ bool FunctionType::hasEqualReturnTypes(FunctionType const& _other) const m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), _other.m_returnParameterTypes.cbegin(), - [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; } + [](Type const* _a, Type const* _b) -> bool { return *_a == *_b; } ); } @@ -3139,13 +3085,15 @@ string FunctionType::externalSignature() const // "inLibrary" is only relevant if this is not an event. bool const inLibrary = kind() != Kind::Event && dynamic_cast(*m_declaration->scope()).isLibrary(); - FunctionTypePointer external = interfaceFunctionType(); - solAssert(!!external, "External function type requested."); - auto parameterTypes = external->parameterTypes(); - auto typeStrings = parameterTypes | boost::adaptors::transformed([&](TypePointer _t) -> string + + auto extParams = transformParametersToExternal(m_parameterTypes, inLibrary); + + solAssert(extParams.message().empty(), extParams.message()); + + auto typeStrings = extParams.get() | boost::adaptors::transformed([&](TypePointer _t) -> string { - solAssert(_t, "Parameter should have external type."); string typeName = _t->signatureInExternalFunction(inLibrary); + if (inLibrary && _t->dataStoredIn(DataLocation::Storage)) typeName += " storage"; return typeName; @@ -3183,13 +3131,13 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) TypePointers pointers; pointers.reserve(_types.size()); for (string const& type: _types) - pointers.push_back(Type::fromElementaryTypeName(type)); + pointers.push_back(TypeProvider::fromElementaryTypeName(type)); return pointers; } TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const { - return make_shared( + return TypeProvider::function( m_parameterTypes, m_returnParameterTypes, m_parameterNames, @@ -3212,9 +3160,9 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun TypePointers parameterTypes; for (auto const& t: m_parameterTypes) { - auto refType = dynamic_cast(t.get()); + auto refType = dynamic_cast(t); if (refType && refType->location() == DataLocation::CallData) - parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, true)); + parameterTypes.push_back(TypeProvider::withLocation(refType, DataLocation::Memory, true)); else parameterTypes.push_back(t); } @@ -3229,7 +3177,7 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun kind = Kind::DelegateCall; } - return make_shared( + return TypeProvider::function( parameterTypes, m_returnParameterTypes, m_parameterNames, @@ -3244,7 +3192,7 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun ); } -TypePointer const& FunctionType::selfType() const +Type const* FunctionType::selfType() const { solAssert(bound(), "Function is not bound."); solAssert(m_parameterTypes.size() > 0, "Function has no self type."); @@ -3280,6 +3228,11 @@ bool FunctionType::padArguments() const return true; } +Type const* MappingType::encodingType() const +{ + return TypeProvider::integer(256, IntegerType::Modifier::Unsigned); +} + string MappingType::richIdentifier() const { return "t_mapping" + identifierList(m_keyType, m_valueType); @@ -3320,7 +3273,7 @@ TypeResult MappingType::interfaceType(bool _inLibrary) const else return TypeResult::err("Only libraries are allowed to use the mapping type in public or external functions."); - return shared_from_this(); + return this; } string TypeType::richIdentifier() const @@ -3343,7 +3296,7 @@ u256 TypeType::storageSize() const unsigned TypeType::sizeOnStack() const { - if (auto contractType = dynamic_cast(m_actualType.get())) + if (auto contractType = dynamic_cast(m_actualType)) if (contractType->contractDefinition().isLibrary()) return 1; return 0; @@ -3387,7 +3340,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current else if (m_actualType->category() == Category::Enum) { EnumDefinition const& enumDef = dynamic_cast(*m_actualType).enumDefinition(); - auto enumType = make_shared(enumDef); + auto enumType = TypeProvider::enumType(enumDef); for (ASTPointer const& enumValue: enumDef.members()) members.emplace_back(enumValue->name(), enumType); } @@ -3421,7 +3374,7 @@ bool ModifierType::operator==(Type const& _other) const if (m_parameterTypes.size() != other.m_parameterTypes.size()) return false; - auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; + auto typeCompare = [](Type const* _a, Type const* _b) -> bool { return *_a == *_b; }; if (!equal( m_parameterTypes.cbegin(), @@ -3467,14 +3420,6 @@ string ModuleType::toString(bool) const return string("module \"") + m_sourceUnit.annotation().path + string("\""); } -shared_ptr MagicType::metaType(TypePointer _type) -{ - solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now."); - auto t = make_shared(Kind::MetaType); - t->m_typeArgument = std::move(_type); - return t; -} - string MagicType::richIdentifier() const { switch (m_kind) @@ -3508,65 +3453,65 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const { case Kind::Block: return MemberList::MemberMap({ - {"coinbase", make_shared(StateMutability::Payable)}, - {"timestamp", make_shared(256)}, - {"blockhash", make_shared(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, - {"difficulty", make_shared(256)}, - {"number", make_shared(256)}, - {"gaslimit", make_shared(256)} + {"coinbase", TypeProvider::payableAddress()}, + {"timestamp", TypeProvider::uint256()}, + {"blockhash", TypeProvider::function(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, + {"difficulty", TypeProvider::uint256()}, + {"number", TypeProvider::uint256()}, + {"gaslimit", TypeProvider::uint256()} }); case Kind::Message: return MemberList::MemberMap({ - {"sender", make_shared(StateMutability::Payable)}, - {"gas", make_shared(256)}, - {"value", make_shared(256)}, - {"data", make_shared(DataLocation::CallData)}, - {"sig", make_shared(4)} + {"sender", TypeProvider::payableAddress()}, + {"gas", TypeProvider::uint256()}, + {"value", TypeProvider::uint256()}, + {"data", TypeProvider::array(DataLocation::CallData)}, + {"sig", TypeProvider::fixedBytes(4)} }); case Kind::Transaction: return MemberList::MemberMap({ - {"origin", make_shared(StateMutability::Payable)}, - {"gasprice", make_shared(256)} + {"origin", TypeProvider::payableAddress()}, + {"gasprice", TypeProvider::uint256()} }); case Kind::ABI: return MemberList::MemberMap({ - {"encode", make_shared( - TypePointers(), - TypePointers{make_shared(DataLocation::Memory)}, + {"encode", TypeProvider::function( + TypePointers{}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, strings{}, strings{1, ""}, FunctionType::Kind::ABIEncode, true, StateMutability::Pure )}, - {"encodePacked", make_shared( - TypePointers(), - TypePointers{make_shared(DataLocation::Memory)}, + {"encodePacked", TypeProvider::function( + TypePointers{}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, strings{}, strings{1, ""}, FunctionType::Kind::ABIEncodePacked, true, StateMutability::Pure )}, - {"encodeWithSelector", make_shared( - TypePointers{make_shared(4)}, - TypePointers{make_shared(DataLocation::Memory)}, + {"encodeWithSelector", TypeProvider::function( + TypePointers{TypeProvider::fixedBytes(4)}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, strings{1, ""}, strings{1, ""}, FunctionType::Kind::ABIEncodeWithSelector, true, StateMutability::Pure )}, - {"encodeWithSignature", make_shared( - TypePointers{make_shared(DataLocation::Memory, true)}, - TypePointers{make_shared(DataLocation::Memory)}, + {"encodeWithSignature", TypeProvider::function( + TypePointers{TypeProvider::array(DataLocation::Memory, true)}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, strings{1, ""}, strings{1, ""}, FunctionType::Kind::ABIEncodeWithSignature, true, StateMutability::Pure )}, - {"decode", make_shared( + {"decode", TypeProvider::function( TypePointers(), TypePointers(), strings{}, @@ -3585,9 +3530,9 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const ContractDefinition const& contract = dynamic_cast(*m_typeArgument).contractDefinition(); if (contract.canBeDeployed()) return MemberList::MemberMap({ - {"creationCode", make_shared(DataLocation::Memory)}, - {"runtimeCode", make_shared(DataLocation::Memory)}, - {"name", make_shared(DataLocation::Memory, true)}, + {"creationCode", TypeProvider::array(DataLocation::Memory)}, + {"runtimeCode", TypeProvider::array(DataLocation::Memory)}, + {"name", TypeProvider::stringMemory()}, }); else return {}; @@ -3623,3 +3568,8 @@ TypePointer MagicType::typeArgument() const solAssert(m_typeArgument, ""); return m_typeArgument; } + +TypePointer InaccessibleDynamicType::decodingType() const +{ + return TypeProvider::integer(256, IntegerType::Modifier::Unsigned); +} diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 5fe1df7d08b4..11c9175d89f8 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -32,7 +32,6 @@ #include #include -#include #include #include @@ -45,10 +44,11 @@ namespace dev namespace solidity { +class TypeProvider; class Type; // forward class FunctionType; // forward -using TypePointer = std::shared_ptr; -using FunctionTypePointer = std::shared_ptr; +using TypePointer = Type const*; +using FunctionTypePointer = FunctionType const*; using TypePointers = std::vector; using rational = boost::rational; using TypeResult = Result; @@ -94,7 +94,7 @@ class MemberList public: struct Member { - Member(std::string const& _name, TypePointer const& _type, Declaration const* _declaration = nullptr): + Member(std::string const& _name, Type const* _type, Declaration const* _declaration = nullptr): name(_name), type(_type), declaration(_declaration) @@ -102,17 +102,18 @@ class MemberList } std::string name; - TypePointer type; + Type const* type; Declaration const* declaration = nullptr; }; using MemberMap = std::vector; explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} + void combine(MemberList const& _other); TypePointer memberType(std::string const& _name) const { - TypePointer type; + TypePointer type = nullptr; for (auto const& it: m_memberTypes) if (it.name == _name) { @@ -148,10 +149,16 @@ static_assert(std::is_nothrow_move_constructible::value, "MemberList /** * Abstract base class that forms the root of the type hierarchy. */ -class Type: private boost::noncopyable, public std::enable_shared_from_this +class Type { public: + Type() = default; + Type(Type const&) = delete; + Type(Type&&) = delete; + Type& operator=(Type const&) = delete; + Type& operator=(Type&&) = delete; virtual ~Type() = default; + enum class Category { Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, @@ -160,20 +167,8 @@ class Type: private boost::noncopyable, public std::enable_shared_from_thisdataStoredIn(DataLocation::Storage) ? mobileType() : _targetType; } @@ -298,7 +293,7 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this addr(std::make_shared(StateMutability::NonPayable)); return *addr; } - static AddressType& addressPayable() { static std::shared_ptr addr(std::make_shared(StateMutability::Payable)); return *addr; } + explicit AddressType(StateMutability _stateMutability); Category category() const override { return Category::Address; } - explicit AddressType(StateMutability _stateMutability); - std::string richIdentifier() const override; BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; bool operator==(Type const& _other) const override; @@ -362,8 +357,8 @@ class AddressType: public Type u256 literalValue(Literal const* _literal) const override; - TypePointer encodingType() const override { return shared_from_this(); } - TypeResult interfaceType(bool) const override { return shared_from_this(); } + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } StateMutability stateMutability(void) const { return m_stateMutability; } @@ -382,17 +377,15 @@ class IntegerType: public Type Unsigned, Signed }; - static IntegerType& uint256() { static std::shared_ptr uint256(std::make_shared(256)); return *uint256; } + explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned); Category category() const override { return Category::Integer; } - explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned); - std::string richIdentifier() const override; BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; bool operator==(Type const& _other) const override; @@ -403,8 +396,8 @@ class IntegerType: public Type std::string toString(bool _short) const override; - TypePointer encodingType() const override { return shared_from_this(); } - TypeResult interfaceType(bool) const override { return shared_from_this(); } + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } unsigned numBits() const { return m_bits; } bool isSigned() const { return m_modifier == Modifier::Signed; } @@ -413,8 +406,8 @@ class IntegerType: public Type bigint maxValue() const; private: - unsigned m_bits; - Modifier m_modifier; + unsigned const m_bits; + Modifier const m_modifier; }; /** @@ -427,15 +420,15 @@ class FixedPointType: public Type { Unsigned, Signed }; - Category category() const override { return Category::FixedPoint; } explicit FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, Modifier _modifier = Modifier::Unsigned); + Category category() const override { return Category::FixedPoint; } std::string richIdentifier() const override; BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; bool operator==(Type const& _other) const override; @@ -446,8 +439,8 @@ class FixedPointType: public Type std::string toString(bool _short) const override; - TypePointer encodingType() const override { return shared_from_this(); } - TypeResult interfaceType(bool) const override { return shared_from_this(); } + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } /// Number of bits used for this type in total. unsigned numBits() const { return m_totalBits; } @@ -462,7 +455,7 @@ class FixedPointType: public Type bigint minIntegerValue() const; /// @returns the smallest integer type that can hold this type with fractional parts shifted to integers. - std::shared_ptr asIntegerType() const; + IntegerType const* asIntegerType() const; private: unsigned m_totalBits; @@ -478,18 +471,16 @@ class FixedPointType: public Type class RationalNumberType: public Type { public: + explicit RationalNumberType(rational const& _value, Type const* _compatibleBytesType = nullptr): + m_value(_value), m_compatibleBytesType(_compatibleBytesType) + {} Category category() const override { return Category::RationalNumber; } - static TypePointer forLiteral(Literal const& _literal); - - explicit RationalNumberType(rational const& _value, TypePointer const& _compatibleBytesType = TypePointer()): - m_value(_value), m_compatibleBytesType(_compatibleBytesType) - {} BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; @@ -502,11 +493,11 @@ class RationalNumberType: public Type TypePointer mobileType() const override; /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. - std::shared_ptr integerType() const; + IntegerType const* integerType() const; /// @returns the smallest fixed type that can hold the value or incurs the least precision loss, /// unless the value was truncated, then a suitable type will be chosen to indicate such event. /// If the integer part does not fit, returns an empty pointer. - std::shared_ptr fixedPointType() const; + FixedPointType const* fixedPointType() const; /// @returns true if the value is not an integer. bool isFractional() const { return m_value.denominator() != 1; } @@ -517,6 +508,9 @@ class RationalNumberType: public Type /// @returns true if the value is zero. bool isZero() const { return m_value == 0; } + /// @returns true if the literal is a valid integer. + static std::tuple isValidLiteral(Literal const& _literal); + private: rational m_value; @@ -524,9 +518,6 @@ class RationalNumberType: public Type /// Empty for all rationals that are not directly parsed from hex literals. TypePointer m_compatibleBytesType; - /// @returns true if the literal is a valid integer. - static std::tuple isValidLiteral(Literal const& _literal); - /// @returns true if the literal is a valid rational number. static std::tuple parseRational(std::string const& _value); @@ -541,14 +532,15 @@ class RationalNumberType: public Type class StringLiteralType: public Type { public: - Category category() const override { return Category::StringLiteral; } - explicit StringLiteralType(Literal const& _literal); + explicit StringLiteralType(std::string const& _value); + + Category category() const override { return Category::StringLiteral; } BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; - TypeResult binaryOperatorResult(Token, TypePointer const&) const override + TypeResult binaryOperatorResult(Token, Type const*) const override { - return TypePointer(); + return nullptr; } std::string richIdentifier() const override; @@ -575,16 +567,16 @@ class StringLiteralType: public Type class FixedBytesType: public Type { public: - Category category() const override { return Category::FixedBytes; } - explicit FixedBytesType(unsigned _bytes); + Category category() const override { return Category::FixedBytes; } + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } unsigned storageBytes() const override { return m_bytes; } @@ -593,8 +585,8 @@ class FixedBytesType: public Type std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); } MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; - TypePointer encodingType() const override { return shared_from_this(); } - TypeResult interfaceType(bool) const override { return shared_from_this(); } + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } unsigned numBytes() const { return m_bytes; } @@ -611,7 +603,7 @@ class BoolType: public Type Category category() const override { return Category::Bool; } std::string richIdentifier() const override { return "t_bool"; } TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; } unsigned storageBytes() const override { return 1; } @@ -620,8 +612,8 @@ class BoolType: public Type std::string toString(bool) const override { return "bool"; } u256 literalValue(Literal const* _literal) const override; - TypePointer encodingType() const override { return shared_from_this(); } - TypeResult interfaceType(bool) const override { return shared_from_this(); } + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } }; /** @@ -630,22 +622,24 @@ class BoolType: public Type */ class ReferenceType: public Type { -public: +protected: explicit ReferenceType(DataLocation _location): m_location(_location) {} + +public: DataLocation location() const { return m_location; } TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token, TypePointer const&) const override + TypeResult binaryOperatorResult(Token, Type const*) const override { - return TypePointer(); + return nullptr; } unsigned memoryHeadSize() const override { return 32; } /// @returns a copy of this type with location (recursively) changed to @a _location, /// whereas isPointer is only shallowly changed - the deep copy is always a bound reference. - virtual TypePointer copyForLocation(DataLocation _location, bool _isPointer) const = 0; + virtual std::unique_ptr copyForLocation(DataLocation _location, bool _isPointer) const = 0; - TypePointer mobileType() const override { return copyForLocation(m_location, true); } + TypePointer mobileType() const override { return withLocation(m_location, true); } bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } bool hasSimpleZeroValueInMemory() const override { return false; } @@ -660,13 +654,10 @@ class ReferenceType: public Type return location() == _other.location() && isPointer() == _other.isPointer(); } - /// @returns a copy of @a _type having the same location as this (and is not a pointer type) - /// if _type is a reference type and an unmodified copy of _type otherwise. - /// This function is mostly useful to modify inner types appropriately. - static TypePointer copyForLocationIfReference(DataLocation _location, TypePointer const& _type); + Type const* withLocation(DataLocation _location, bool _isPointer) const; protected: - TypePointer copyForLocationIfReference(TypePointer const& _type) const; + Type const* copyForLocationIfReference(Type const* _type) const; /// @returns a human-readable description of the reference part of the type. std::string stringForReferencePart() const; /// @returns the suffix computed from the reference part to be used by identifier(); @@ -686,32 +677,26 @@ class ReferenceType: public Type class ArrayType: public ReferenceType { public: - static ArrayType& bytesMemory() { static std::shared_ptr addr(std::make_shared(DataLocation::Memory)); return *addr; } - static ArrayType& stringMemory() { static std::shared_ptr addr(std::make_shared(DataLocation::Memory, true)); return *addr; } - - Category category() const override { return Category::Array; } - /// Constructor for a byte array ("bytes") and string. - explicit ArrayType(DataLocation _location, bool _isString = false): - ReferenceType(_location), - m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), - m_baseType(std::make_shared(1)) - { - } + explicit ArrayType(DataLocation _location, bool _isString = false); + /// Constructor for a dynamically sized array type ("type[]") - ArrayType(DataLocation _location, TypePointer const& _baseType): + ArrayType(DataLocation _location, Type const* _baseType): ReferenceType(_location), m_baseType(copyForLocationIfReference(_baseType)) { } + /// Constructor for a fixed-size array type ("type[20]") - ArrayType(DataLocation _location, TypePointer const& _baseType, u256 const& _length): + ArrayType(DataLocation _location, Type const* _baseType, u256 const& _length): ReferenceType(_location), m_baseType(copyForLocationIfReference(_baseType)), m_hasDynamicLength(false), m_length(_length) {} + Category category() const override { return Category::Array; } + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; @@ -737,11 +722,11 @@ class ArrayType: public ReferenceType bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } /// @returns true if this is a string bool isString() const { return m_arrayKind == ArrayKind::String; } - TypePointer const& baseType() const { solAssert(!!m_baseType, ""); return m_baseType;} + Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; } u256 const& length() const { return m_length; } u256 memorySize() const; - TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; + std::unique_ptr copyForLocation(DataLocation _location, bool _isPointer) const override; /// The offset to advance in calldata to move from one array element to the next. unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataEncodedSize(); } @@ -750,6 +735,8 @@ class ArrayType: public ReferenceType /// The offset to advance in storage to move from one array element to the next. unsigned storageStride() const { return isByteArray() ? 1 : m_baseType->storageBytes(); } + void clearCache() const override; + private: /// String is interpreted as a subtype of Bytes. enum class ArrayKind { Ordinary, Bytes, String }; @@ -758,7 +745,7 @@ class ArrayType: public ReferenceType ///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays. ArrayKind m_arrayKind = ArrayKind::Ordinary; - TypePointer m_baseType; + Type const* m_baseType; bool m_hasDynamicLength = true; u256 m_length; mutable boost::optional m_interfaceType; @@ -771,9 +758,10 @@ class ArrayType: public ReferenceType class ContractType: public Type { public: - Category category() const override { return Category::Contract; } explicit ContractType(ContractDefinition const& _contract, bool _super = false): m_contract(_contract), m_super(_super) {} + + Category category() const override { return Category::Contract; } /// Contracts can be implicitly converted only to base contracts. BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; /// Contracts can only be explicitly converted to address types and base contracts. @@ -795,17 +783,14 @@ class ContractType: public Type std::string canonicalName() const override; MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; - TypePointer encodingType() const override - { - if (isSuper()) - return TypePointer{}; - return std::make_shared(isPayable() ? StateMutability::Payable : StateMutability::NonPayable); - } + + Type const* encodingType() const override; + TypeResult interfaceType(bool _inLibrary) const override { if (isSuper()) - return TypePointer{}; - return _inLibrary ? shared_from_this() : encodingType(); + return nullptr; + return _inLibrary ? this : encodingType(); } /// See documentation of m_super @@ -817,7 +802,7 @@ class ContractType: public Type ContractDefinition const& contractDefinition() const { return m_contract; } /// Returns the function type of the constructor modified to return an object of the contract's type. - FunctionTypePointer const& newExpressionType() const; + FunctionType const* newExpressionType() const; /// @returns a list of all state variables (including inherited) of the contract and their /// offsets in storage. @@ -828,7 +813,7 @@ class ContractType: public Type /// If true, this is a special "super" type of m_contract containing only members that m_contract inherited bool m_super = false; /// Type of the constructor, @see constructorType. Lazily initialized. - mutable FunctionTypePointer m_constructorType; + mutable FunctionType const* m_constructorType = nullptr; }; /** @@ -837,9 +822,10 @@ class ContractType: public Type class StructType: public ReferenceType { public: - Category category() const override { return Category::Struct; } explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage): ReferenceType(_location), m_struct(_struct) {} + + Category category() const override { return Category::Struct; } BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; @@ -851,10 +837,8 @@ class StructType: public ReferenceType std::string toString(bool _short) const override; MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; - TypePointer encodingType() const override - { - return location() == DataLocation::Storage ? std::make_shared(256) : shared_from_this(); - } + + Type const* encodingType() const override; TypeResult interfaceType(bool _inLibrary) const override; bool recursive() const @@ -867,14 +851,14 @@ class StructType: public ReferenceType return m_recursive.get(); } - TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; + std::unique_ptr copyForLocation(DataLocation _location, bool _isPointer) const override; std::string canonicalName() const override; std::string signatureInExternalFunction(bool _structsByName) const override; /// @returns a function that performs the type conversion between a list of struct members /// and a memory struct of this type. - FunctionTypePointer constructorType() const; + FunctionType const* constructorType() const; std::pair const& storageOffsetsOfMember(std::string const& _name) const; u256 memoryOffsetOfMember(std::string const& _name) const; @@ -886,6 +870,9 @@ class StructType: public ReferenceType TypePointers memoryMemberTypes() const; /// @returns the set of all members that are removed in the memory version (typically mappings). std::set membersMissingInMemory() const; + + void clearCache() const override; + private: StructDefinition const& m_struct; // Caches for interfaceType(bool) @@ -900,8 +887,9 @@ class StructType: public ReferenceType class EnumType: public Type { public: - Category category() const override { return Category::Enum; } explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {} + + Category category() const override { return Category::Enum; } TypeResult unaryOperatorResult(Token _operator) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; @@ -917,13 +905,10 @@ class EnumType: public Type bool isValueType() const override { return true; } BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer encodingType() const override - { - return std::make_shared(8 * int(storageBytes())); - } + TypePointer encodingType() const override; TypeResult interfaceType(bool _inLibrary) const override { - return _inLibrary ? shared_from_this() : encodingType(); + return _inLibrary ? this : encodingType(); } EnumDefinition const& enumDefinition() const { return m_enum; } @@ -942,12 +927,14 @@ class EnumType: public Type class TupleType: public Type { public: + explicit TupleType(std::vector _types = {}): m_components(std::move(_types)) {} + Category category() const override { return Category::Tuple; } - explicit TupleType(std::vector const& _types = std::vector()): m_components(_types) {} + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; - TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } std::string toString(bool) const override; bool canBeStored() const override { return false; } u256 storageSize() const override; @@ -956,7 +943,7 @@ class TupleType: public Type bool hasSimpleZeroValueInMemory() const override { return false; } TypePointer mobileType() const override; /// Converts components to their temporary types and performs some wildcard matching. - TypePointer closestTemporaryType(TypePointer const& _targetType) const override; + TypePointer closestTemporaryType(Type const* _targetType) const override; std::vector const& components() const { return m_components; } @@ -1017,8 +1004,6 @@ class FunctionType: public Type MetaType ///< type(...) }; - Category category() const override { return Category::Function; } - /// Creates the type of a function. explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); /// Creates the accessor function type of a state variable. @@ -1046,9 +1031,6 @@ class FunctionType: public Type { } - /// @returns the type of the "new Contract" function, i.e. basically the constructor. - static FunctionTypePointer newExpressionType(ContractDefinition const& _contract); - /// Detailed constructor, use with care. FunctionType( TypePointers const& _parameterTypes, @@ -1089,6 +1071,11 @@ class FunctionType: public Type ); } + Category category() const override { return Category::Function; } + + /// @returns the type of the "new Contract" function, i.e. basically the constructor. + static FunctionTypePointer newExpressionType(ContractDefinition const& _contract); + TypePointers parameterTypes() const; std::vector parameterNames() const; TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; } @@ -1097,14 +1084,14 @@ class FunctionType: public Type TypePointers returnParameterTypesWithoutDynamicTypes() const; std::vector const& returnParameterNames() const { return m_returnParameterNames; } /// @returns the "self" parameter type for a bound function - TypePointer const& selfType() const; + Type const* selfType() const; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token, TypePointer const&) const override; + TypeResult binaryOperatorResult(Token, Type const*) const override; std::string canonicalName() const override; std::string toString(bool _short) const override; unsigned calldataEncodedSize(bool _padded) const override; @@ -1133,7 +1120,7 @@ class FunctionType: public Type /// expression the function is called on. bool canTakeArguments( FuncCallArguments const& _arguments, - TypePointer const& _selfType = TypePointer() + Type const* _selfType = nullptr ) const; /// @returns true if the types of parameters are equal (does not check return parameter types) @@ -1229,27 +1216,25 @@ class FunctionType: public Type class MappingType: public Type { public: - Category category() const override { return Category::Mapping; } - MappingType(TypePointer const& _keyType, TypePointer const& _valueType): + MappingType(Type const* _keyType, Type const* _valueType): m_keyType(_keyType), m_valueType(_valueType) {} + Category category() const override { return Category::Mapping; } + std::string richIdentifier() const override; bool operator==(Type const& _other) const override; std::string toString(bool _short) const override; std::string canonicalName() const override; bool canLiveOutsideStorage() const override { return false; } - TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } - TypePointer encodingType() const override - { - return std::make_shared(256); - } + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + Type const* encodingType() const override; TypeResult interfaceType(bool _inLibrary) const override; bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; } /// Cannot be stored in memory, but just in case. bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } - TypePointer const& keyType() const { return m_keyType; } - TypePointer const& valueType() const { return m_valueType; } + Type const* keyType() const { return m_keyType; } + Type const* valueType() const { return m_valueType; } private: TypePointer m_keyType; @@ -1264,11 +1249,12 @@ class MappingType: public Type class TypeType: public Type { public: + explicit TypeType(Type const* _actualType): m_actualType(_actualType) {} + Category category() const override { return Category::TypeType; } - explicit TypeType(TypePointer const& _actualType): m_actualType(_actualType) {} - TypePointer const& actualType() const { return m_actualType; } + Type const* actualType() const { return m_actualType; } - TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } std::string richIdentifier() const override; bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } @@ -1290,10 +1276,11 @@ class TypeType: public Type class ModifierType: public Type { public: - Category category() const override { return Category::Modifier; } explicit ModifierType(ModifierDefinition const& _modifier); - TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + Category category() const override { return Category::Modifier; } + + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } bool canBeStored() const override { return false; } u256 storageSize() const override; bool canLiveOutsideStorage() const override { return false; } @@ -1315,11 +1302,11 @@ class ModifierType: public Type class ModuleType: public Type { public: - Category category() const override { return Category::Module; } - explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {} - TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + Category category() const override { return Category::Module; } + + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } std::string richIdentifier() const override; bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } @@ -1347,15 +1334,16 @@ class MagicType: public Type ABI, ///< "abi" MetaType ///< "type(...)" }; - Category category() const override { return Category::Magic; } +public: explicit MagicType(Kind _kind): m_kind(_kind) {} - /// Factory function for meta type - static std::shared_ptr metaType(TypePointer _type); + explicit MagicType(Type const* _metaTypeArg): m_kind{Kind::MetaType}, m_typeArgument{_metaTypeArg} {} + + Category category() const override { return Category::Magic; } - TypeResult binaryOperatorResult(Token, TypePointer const&) const override + TypeResult binaryOperatorResult(Token, Type const*) const override { - return TypePointer(); + return nullptr; } std::string richIdentifier() const override; @@ -1376,7 +1364,6 @@ class MagicType: public Type Kind m_kind; /// Contract type used for contract metadata magic. TypePointer m_typeArgument; - }; /** @@ -1391,7 +1378,7 @@ class InaccessibleDynamicType: public Type std::string richIdentifier() const override { return "t_inaccessible"; } BoolResult isImplicitlyConvertibleTo(Type const&) const override { return false; } BoolResult isExplicitlyConvertibleTo(Type const&) const override { return false; } - TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } bool canBeStored() const override { return false; } bool canLiveOutsideStorage() const override { return false; } @@ -1399,7 +1386,7 @@ class InaccessibleDynamicType: public Type unsigned sizeOnStack() const override { return 1; } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } std::string toString(bool) const override { return "inaccessible dynamic type"; } - TypePointer decodingType() const override { return std::make_shared(256); } + TypePointer decodingType() const override; }; } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 47475ecedc1f..be9a67177c4d 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -82,7 +82,7 @@ string ABIFunctions::tupleEncoder( ( add(headStart, )) )") ); - string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); + string values = m_utils.suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); elementTempl("values", values.empty() ? "" : values + ", "); elementTempl("pos", to_string(headPos)); elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); @@ -91,7 +91,7 @@ string ABIFunctions::tupleEncoder( stackPos += sizeOnStack; } solAssert(headPos == headSize_, ""); - string valueParams = suffixedVariableNameList("value", stackPos, 0); + string valueParams = m_utils.suffixedVariableNameList("value", stackPos, 0); templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); templ("encodeElements", encodeElements); @@ -147,7 +147,7 @@ string ABIFunctions::tupleEncoderPacked( pos := add(pos, ) )") ); - string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); + string values = m_utils.suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); elementTempl("values", values.empty() ? "" : values + ", "); if (!dynamic) elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false))); @@ -155,7 +155,7 @@ string ABIFunctions::tupleEncoderPacked( encodeElements += elementTempl.render(); stackPos += sizeOnStack; } - string valueParams = suffixedVariableNameList("value", stackPos, 0); + string valueParams = m_utils.suffixedVariableNameList("value", stackPos, 0); templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); templ("encodeElements", encodeElements); @@ -255,97 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const return suffix; } - -string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) -{ - string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> cleaned { - - } - )"); - templ("functionName", functionName); - switch (_type.category()) - { - case Type::Category::Address: - templ("body", "cleaned := " + cleanupFunction(IntegerType(160), _revertOnFailure) + "(value)"); - break; - case Type::Category::Integer: - { - IntegerType const& type = dynamic_cast(_type); - if (type.numBits() == 256) - templ("body", "cleaned := value"); - else if (type.isSigned()) - templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); - else - templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); - break; - } - case Type::Category::RationalNumber: - templ("body", "cleaned := value"); - break; - case Type::Category::Bool: - templ("body", "cleaned := iszero(iszero(value))"); - break; - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; - case Type::Category::Array: - case Type::Category::Struct: - case Type::Category::Mapping: - solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); - templ("body", "cleaned := value"); - break; - case Type::Category::FixedBytes: - { - FixedBytesType const& type = dynamic_cast(_type); - if (type.numBytes() == 32) - templ("body", "cleaned := value"); - else if (type.numBytes() == 0) - // This is disallowed in the type system. - solAssert(false, ""); - else - { - size_t numBits = type.numBytes() * 8; - u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); - templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); - } - break; - } - case Type::Category::Contract: - { - AddressType addressType(dynamic_cast(_type).isPayable() ? - StateMutability::Payable : - StateMutability::NonPayable - ); - templ("body", "cleaned := " + cleanupFunction(addressType, _revertOnFailure) + "(value)"); - break; - } - case Type::Category::Enum: - { - size_t members = dynamic_cast(_type).numberOfMembers(); - solAssert(members > 0, "empty enum should have caused a parser error."); - Whiskers w("if iszero(lt(value, )) { } cleaned := value"); - w("members", to_string(members)); - if (_revertOnFailure) - w("failure", "revert(0, 0)"); - else - w("failure", "invalid()"); - templ("body", w.render()); - break; - } - case Type::Category::InaccessibleDynamic: - templ("body", "cleaned := 0"); - break; - default: - solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); - } - - return templ.render(); - }); -} - string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) { solAssert(_type.isValueType(), ""); @@ -379,186 +288,6 @@ string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFu }); } -string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) -{ - string functionName = - "convert_" + - _from.identifier() + - "_to_" + - _to.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> converted { - - } - )"); - templ("functionName", functionName); - string body; - auto toCategory = _to.category(); - auto fromCategory = _from.category(); - switch (fromCategory) - { - case Type::Category::Address: - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(IntegerType(160), _to)) - .render(); - break; - case Type::Category::Integer: - case Type::Category::RationalNumber: - case Type::Category::Contract: - { - if (RationalNumberType const* rational = dynamic_cast(&_from)) - solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); - if (toCategory == Type::Category::FixedBytes) - { - solAssert( - fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, - "Invalid conversion to FixedBytesType requested." - ); - FixedBytesType const& toBytesType = dynamic_cast(_to); - body = - Whiskers("converted := ((value))") - ("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8)) - ("clean", cleanupFunction(_from)) - .render(); - } - else if (toCategory == Type::Category::Enum) - { - solAssert(_from.mobileType(), ""); - body = - Whiskers("converted := ((value))") - ("cleanEnum", cleanupFunction(_to)) - // "mobileType()" returns integer type for rational - ("cleanInt", cleanupFunction(*_from.mobileType())) - .render(); - } - else if (toCategory == Type::Category::FixedPoint) - solUnimplemented("Not yet implemented - FixedPointType."); - else if (toCategory == Type::Category::Address) - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(_from, IntegerType(160))) - .render(); - else - { - solAssert( - toCategory == Type::Category::Integer || - toCategory == Type::Category::Contract, - ""); - IntegerType const addressType(160); - IntegerType const& to = - toCategory == Type::Category::Integer ? - dynamic_cast(_to) : - addressType; - - // Clean according to the "to" type, except if this is - // a widening conversion. - IntegerType const* cleanupType = &to; - if (fromCategory != Type::Category::RationalNumber) - { - IntegerType const& from = - fromCategory == Type::Category::Integer ? - dynamic_cast(_from) : - addressType; - if (to.numBits() > from.numBits()) - cleanupType = &from; - } - body = - Whiskers("converted := (value)") - ("cleanInt", cleanupFunction(*cleanupType)) - .render(); - } - break; - } - case Type::Category::Bool: - { - solAssert(_from == _to, "Invalid conversion for bool."); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(_from)) - .render(); - break; - } - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; - case Type::Category::Array: - solUnimplementedAssert(false, "Array conversion not implemented."); - break; - case Type::Category::Struct: - solUnimplementedAssert(false, "Struct conversion not implemented."); - break; - case Type::Category::FixedBytes: - { - FixedBytesType const& from = dynamic_cast(_from); - if (toCategory == Type::Category::Integer) - body = - Whiskers("converted := ((value))") - ("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8)) - ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) - .render(); - else if (toCategory == Type::Category::Address) - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(_from, IntegerType(160))) - .render(); - else - { - // clear for conversion to longer bytes - solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(from)) - .render(); - } - break; - } - case Type::Category::Function: - { - solAssert(false, "Conversion should not be called for function types."); - break; - } - case Type::Category::Enum: - { - solAssert(toCategory == Type::Category::Integer || _from == _to, ""); - EnumType const& enumType = dynamic_cast(_from); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(enumType)) - .render(); - break; - } - case Type::Category::Tuple: - { - solUnimplementedAssert(false, "Tuple conversion not implemented."); - break; - } - default: - solAssert(false, ""); - } - - solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); - templ("body", body); - return templ.render(); - }); -} - -string ABIFunctions::cleanupCombinedExternalFunctionIdFunction() -{ - string functionName = "cleanup_combined_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (addr_and_selector) -> cleaned { - cleaned := (addr_and_selector) - } - )") - ("functionName", functionName) - ("clean", cleanupFunction(FixedBytesType(24))) - .render(); - }); -} - string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, @@ -576,19 +305,31 @@ string ABIFunctions::abiEncodingFunction( solAssert(_from.category() == Type::Category::Array, ""); solAssert(to.dataStoredIn(DataLocation::Memory), ""); ArrayType const& fromArray = dynamic_cast(_from); - if (fromArray.location() == DataLocation::CallData) - return abiEncodingFunctionCalldataArray(fromArray, *toArray, _options); - else if (!fromArray.isByteArray() && ( - fromArray.location() == DataLocation::Memory || - fromArray.baseType()->storageBytes() > 16 - )) - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); - else if (fromArray.location() == DataLocation::Memory) - return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options); - else if (fromArray.location() == DataLocation::Storage) - return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options); - else - solAssert(false, ""); + + switch (fromArray.location()) + { + case DataLocation::CallData: + if ( + fromArray.isByteArray() || + *fromArray.baseType() == *TypeProvider::uint256() || + *fromArray.baseType() == FixedBytesType(32) + ) + return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options); + else + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + case DataLocation::Memory: + if (fromArray.isByteArray()) + return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options); + else + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + case DataLocation::Storage: + if (fromArray.baseType()->storageBytes() <= 16) + return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options); + else + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + default: + solAssert(false, ""); + } } else if (auto const* toStruct = dynamic_cast(&to)) { @@ -628,16 +369,16 @@ string ABIFunctions::abiEncodingFunction( // possible for library calls where we just forward the storage reference solAssert(_options.encodeAsLibraryTypes, ""); solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested."); - solAssert(to == IntegerType::uint256(), ""); + solAssert(to == *TypeProvider::uint256(), ""); templ("cleanupConvert", "value"); } else { string cleanupConvert; if (_from == to) - cleanupConvert = cleanupFunction(_from) + "(value)"; + cleanupConvert = m_utils.cleanupFunction(_from) + "(value)"; else - cleanupConvert = conversionFunction(_from, to) + "(value)"; + cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)"; if (!_options.padded) cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; templ("cleanupConvert", cleanupConvert); @@ -659,7 +400,7 @@ string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction( _targetType.identifier() + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { - string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options)); + string values = m_utils.suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options)); string encoder = abiEncodingFunction(_givenType, _targetType, _options); if (_targetType.isDynamicallyEncoded()) return Whiskers(R"( @@ -690,19 +431,24 @@ string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction( }); } -string ABIFunctions::abiEncodingFunctionCalldataArray( +string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( Type const& _from, Type const& _to, EncodingOptions const& _options ) { - solAssert(_to.isDynamicallySized(), ""); solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type."); solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type."); auto const& fromArrayType = dynamic_cast(_from); auto const& toArrayType = dynamic_cast(_to); solAssert(fromArrayType.location() == DataLocation::CallData, ""); + solAssert( + fromArrayType.isByteArray() || + *fromArrayType.baseType() == *TypeProvider::uint256() || + *fromArrayType.baseType() == FixedBytesType(32), + ""); + solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), ""); solAssert( *fromArrayType.copyForLocation(DataLocation::Memory, true) == @@ -717,24 +463,54 @@ string ABIFunctions::abiEncodingFunctionCalldataArray( _to.identifier() + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { - solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently."); - // TODO if this is not a byte array, we might just copy byte-by-byte anyway, - // because the encoding is position-independent, but we have to check that. - Whiskers templ(R"( - // -> - function (start, length, pos) -> end { - pos := (pos, length) - (start, pos, length) - end := add(pos, ) - } - )"); - templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options)); - templ("functionName", functionName); - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("copyFun", m_utils.copyToMemoryFunction(true)); - templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); - return templ.render(); + bool needsPadding = _options.padded && fromArrayType.isByteArray(); + if (fromArrayType.isDynamicallySized()) + { + Whiskers templ(R"( + // -> + function (start, length, pos) -> end { + pos := (pos, length) + + (start, pos, length) + end := add(pos, ) + } + )"); + templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options)); + templ("functionName", functionName); + if (fromArrayType.isByteArray() || fromArrayType.calldataStride() == 1) + templ("scaleLengthByStride", ""); + else + templ("scaleLengthByStride", + Whiskers(R"( + if gt(length, ) { revert(0, 0) } + length := mul(length, ) + )") + ("stride", toCompactHexWithPrefix(fromArrayType.calldataStride())) + ("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride())) + .render() + ); + templ("readableTypeNameFrom", _from.toString(true)); + templ("readableTypeNameTo", _to.toString(true)); + templ("copyFun", m_utils.copyToMemoryFunction(true)); + templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length"); + return templ.render(); + } + else + { + solAssert(fromArrayType.calldataStride() == 32, ""); + Whiskers templ(R"( + // -> + function (start, pos) { + (start, pos, ) + } + )"); + templ("functionName", functionName); + templ("readableTypeNameFrom", _from.toString(true)); + templ("readableTypeNameTo", _to.toString(true)); + templ("copyFun", m_utils.copyToMemoryFunction(true)); + templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride())); + return templ.render(); + } }); } @@ -753,29 +529,34 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.length() == _to.length(), ""); - solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), ""); solAssert(!_from.isByteArray(), ""); - solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, ""); + if (_from.dataStoredIn(DataLocation::Storage)) + solAssert(_from.baseType()->storageBytes() > 16, ""); return createFunction(functionName, [&]() { bool dynamic = _to.isDynamicallyEncoded(); bool dynamicBase = _to.baseType()->isDynamicallyEncoded(); - bool inMemory = _from.dataStoredIn(DataLocation::Memory); bool const usesTail = dynamicBase && !_options.dynamicInplace; + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; + subOptions.padded = true; + string elementValues = m_utils.suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions)); Whiskers templ( usesTail ? R"( // -> - function (value, pos) { - let length := (value) + function (value, pos) { + pos := (pos, length) let headStart := pos let tail := add(pos, mul(length, 0x20)) - let srcPtr := (value) + let baseRef := (value) + let srcPtr := baseRef for { let i := 0 } lt(i, length) { i := add(i, 1) } { mstore(pos, sub(tail, headStart)) - tail := (, tail) + let := + tail := (, tail) srcPtr := (srcPtr) pos := add(pos, 0x20) } @@ -785,13 +566,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( )" : R"( // -> - function (value, pos) { - let length := (value) + function (value, pos) { + pos := (pos, length) - let srcPtr := (value) + let baseRef := (value) + let srcPtr := baseRef for { let i := 0 } lt(i, length) { i := add(i, 1) } { - pos := (, pos) + let := + pos := (, pos) srcPtr := (srcPtr) } @@ -799,27 +582,43 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( )" ); templ("functionName", functionName); + templ("elementValues", elementValues); + bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized(); + if (lengthAsArgument) + { + templ("maybeLength", " length,"); + templ("declareLength", ""); + } + else + { + templ("maybeLength", ""); + templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)"); + } templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from)); - EncodingOptions subOptions(_options); - subOptions.encodeFunctionFromStack = false; - subOptions.padded = true; templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); - if (inMemory) - templ("arrayElementAccess", "mload(srcPtr)"); - else if (_from.baseType()->isValueType()) + switch (_from.location()) { - solAssert(_from.dataStoredIn(DataLocation::Storage), ""); - templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); + case DataLocation::Memory: + templ("arrayElementAccess", "mload(srcPtr)"); + break; + case DataLocation::Storage: + if (_from.baseType()->isValueType()) + templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); + else + templ("arrayElementAccess", "srcPtr"); + break; + case DataLocation::CallData: + templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)"); + break; + default: + solAssert(false, ""); } - else - templ("arrayElementAccess", "srcPtr"); templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); return templ.render(); }); @@ -1028,11 +827,9 @@ string ABIFunctions::abiEncodingFunctionStruct( _to.identifier() + _options.toFunctionNameSuffix(); - solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported."); solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); return createFunction(functionName, [&]() { - bool fromStorage = _from.location() == DataLocation::Storage; bool dynamic = _to.isDynamicallyEncoded(); Whiskers templ(R"( // -> @@ -1043,7 +840,7 @@ string ABIFunctions::abiEncodingFunctionStruct( { // - let memberValue := + let := } @@ -1061,7 +858,7 @@ string ABIFunctions::abiEncodingFunctionStruct( else templ("assignEnd", ""); // to avoid multiple loads from the same slot for subsequent members - templ("init", fromStorage ? "let slotValue := 0" : ""); + templ("init", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : ""); u256 previousSlotOffset(-1); u256 encodingOffset = 0; vector> members; @@ -1081,32 +878,45 @@ string ABIFunctions::abiEncodingFunctionStruct( members.push_back({}); members.back()["preprocess"] = ""; - if (fromStorage) + switch (_from.location()) { - solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); - u256 storageSlotOffset; - size_t intraSlotOffset; - tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); - if (memberTypeFrom->isValueType()) + case DataLocation::Storage: { - if (storageSlotOffset != previousSlotOffset) + solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); + u256 storageSlotOffset; + size_t intraSlotOffset; + tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); + if (memberTypeFrom->isValueType()) + { + if (storageSlotOffset != previousSlotOffset) + { + members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; + previousSlotOffset = storageSlotOffset; + } + members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; + } + else { - members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; - previousSlotOffset = storageSlotOffset; + solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); + solAssert(intraSlotOffset == 0, ""); + members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; } - members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; + break; } - else + case DataLocation::Memory: { - solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); - solAssert(intraSlotOffset == 0, ""); - members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; + string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); + members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; + break; } - } - else - { - string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); - members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; + case DataLocation::CallData: + { + string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name)); + members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))"; + break; + } + default: + solAssert(false, ""); } EncodingOptions subOptions(_options); @@ -1114,10 +924,14 @@ string ABIFunctions::abiEncodingFunctionStruct( // Like with arrays, struct members are always padded. subOptions.padded = true; + string memberValues = m_utils.suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions)); + members.back()["memberValues"] = memberValues; + string encode; if (_options.dynamicInplace) - encode = Whiskers{"pos := (memberValue, pos)"} + encode = Whiskers{"pos := (, pos)"} ("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions)) + ("memberValues", memberValues) .render(); else { @@ -1125,10 +939,11 @@ string ABIFunctions::abiEncodingFunctionStruct( dynamicMember ? string(R"( mstore(add(pos, ), sub(tail, pos)) - tail := (memberValue, tail) + tail := (, tail) )") : - "(memberValue, add(pos, ))" + "(, add(pos, ))" ); + encodeTempl("memberValues", memberValues); encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); @@ -1248,7 +1063,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType( } )") ("functionName", functionName) - ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + ("cleanExtFun", m_utils.cleanupFunction(_to)) .render(); }); } @@ -1262,7 +1077,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo TypePointer decodingType = _type.decodingType(); solAssert(decodingType, ""); - if (auto arrayType = dynamic_cast(decodingType.get())) + if (auto arrayType = dynamic_cast(decodingType)) { if (arrayType->dataStoredIn(DataLocation::CallData)) { @@ -1274,7 +1089,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo else return abiDecodingFunctionArray(*arrayType, _fromMemory); } - else if (auto const* structType = dynamic_cast(decodingType.get())) + else if (auto const* structType = dynamic_cast(decodingType)) { if (structType->dataStoredIn(DataLocation::CallData)) { @@ -1284,7 +1099,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo else return abiDecodingFunctionStruct(*structType, _fromMemory); } - else if (auto const* functionType = dynamic_cast(decodingType.get())) + else if (auto const* functionType = dynamic_cast(decodingType)) return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); else return abiDecodingFunctionValueType(_type, _fromMemory); @@ -1306,14 +1121,15 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM return createFunction(functionName, [&]() { Whiskers templ(R"( function (offset, end) -> value { - value := ((offset)) + value := (offset) + (value) } )"); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); - // Cleanup itself should use the type and not decodingType, because e.g. + // Validation should use the type and not decodingType, because e.g. // the decoding type of an enum is a plain int. - templ("cleanup", cleanupFunction(_type, true)); + templ("validator", m_utils.validatorFunction(_type, true)); return templ.render(); }); @@ -1560,11 +1376,11 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, { return Whiskers(R"( function (offset, end) -> addr, function_selector { - addr, function_selector := ((offset)) + addr, function_selector := ((offset, end)) } )") ("functionName", functionName) - ("load", _fromMemory ? "mload" : "calldataload") + ("decodeFun", abiDecodingFunctionFunctionType(_type, _fromMemory, false)) ("splitExtFun", m_utils.splitExternalFunctionIdFunction()) .render(); } @@ -1572,18 +1388,18 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, { return Whiskers(R"( function (offset, end) -> fun { - fun := ((offset)) + fun := (offset) + (fun) } )") ("functionName", functionName) ("load", _fromMemory ? "mload" : "calldataload") - ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + ("validateExtFun", m_utils.validatorFunction(_type, true)) .render(); } }); } - string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); @@ -1629,6 +1445,80 @@ string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset, }); } +string ABIFunctions::calldataAccessFunction(Type const& _type) +{ + solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), ""); + string functionName = "calldata_access_" + _type.identifier(); + return createFunction(functionName, [&]() { + if (_type.isDynamicallyEncoded()) + { + unsigned int baseEncodedSize = _type.calldataEncodedSize(); + solAssert(baseEncodedSize > 1, ""); + Whiskers w(R"( + function (base_ref, ptr) -> { + let rel_offset_of_tail := calldataload(ptr) + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { revert(0, 0) } + value := add(rel_offset_of_tail, base_ref) + + } + )"); + if (_type.isDynamicallySized()) + { + auto const* arrayType = dynamic_cast(&_type); + solAssert(!!arrayType, ""); + unsigned int calldataStride = arrayType->calldataStride(); + w("handleLength", Whiskers(R"( + length := calldataload(value) + value := add(value, 0x20) + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } + )")("calldataStride", toCompactHexWithPrefix(calldataStride)).render()); + w("return", "value, length"); + } + else + { + w("handleLength", ""); + w("return", "value"); + } + w("neededLength", toCompactHexWithPrefix(baseEncodedSize)); + w("functionName", functionName); + return w.render(); + } + else if (_type.isValueType()) + { + string decodingFunction; + if (auto const* functionType = dynamic_cast(&_type)) + decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false); + else + decodingFunction = abiDecodingFunctionValueType(_type, false); + // Note that the second argument to the decoding function should be discarded after inlining. + return Whiskers(R"( + function (baseRef, ptr) -> value { + value := (ptr, add(ptr, 32)) + } + )") + ("functionName", functionName) + ("decodingFunction", decodingFunction) + .render(); + } + else + { + solAssert( + _type.category() == Type::Category::Array || + _type.category() == Type::Category::Struct, + "" + ); + return Whiskers(R"( + function (baseRef, ptr) -> value { + value := ptr + } + )") + ("functionName", functionName) + .render(); + } + }); +} + string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) { string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); @@ -1679,24 +1569,6 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes) return headSize; } -string ABIFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) -{ - string result; - if (_startSuffix < _endSuffix) - { - result = _baseName + to_string(_startSuffix++); - while (_startSuffix < _endSuffix) - result += ", " + _baseName + to_string(_startSuffix++); - } - else if (_endSuffix < _startSuffix) - { - result = _baseName + to_string(_endSuffix++); - while (_endSuffix < _startSuffix) - result = _baseName + to_string(_endSuffix++) + ", " + result; - } - return result; -} - size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions const& _options) { if (_type.category() == Type::Category::Function && !_options.encodeFunctionFromStack) diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 5c5b69637ff6..1858917fa117 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -41,7 +41,7 @@ class Type; class ArrayType; class StructType; class FunctionType; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; using TypePointers = std::vector; /** @@ -128,12 +128,6 @@ class ABIFunctions std::string toFunctionNameSuffix() const; }; - /// @returns the name of the cleanup function for the given type and - /// adds its implementation to the requested functions. - /// @param _revertOnFailure if true, causes revert on invalid data, - /// otherwise an assertion failure. - std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false); - /// Performs cleanup after reading from a potentially compressed storage slot. /// The function does not perform any validation, it just masks or sign-extends /// higher order bytes or left-aligns (in case of bytesNN). @@ -143,13 +137,6 @@ class ABIFunctions /// single variable. std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes); - /// @returns the name of the function that converts a value of type @a _from - /// to a value of type @a _to. The resulting vale is guaranteed to be in range - /// (i.e. "clean"). Asserts on failure. - std::string conversionFunction(Type const& _from, Type const& _to); - - std::string cleanupCombinedExternalFunctionIdFunction(); - /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. /// @param _fromStack if false, the input value was just loaded from storage @@ -168,7 +155,9 @@ class ABIFunctions EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given calldata array. - std::string abiEncodingFunctionCalldataArray( + /// Uses calldatacopy and does not perform cleanup or validation and can therefore only + /// be used for byte arrays and arrays with the base type uint256 or bytes32. + std::string abiEncodingFunctionCalldataArrayWithoutCleanup( Type const& _givenType, Type const& _targetType, EncodingOptions const& _options @@ -255,6 +244,9 @@ class ABIFunctions /// single variable. std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); + /// @returns the name of a function that retrieves an element from calldata. + std::string calldataAccessFunction(Type const& _type); + /// @returns the name of a function used during encoding that stores the length /// if the array is dynamically sized (and the options do not request in-place encoding). /// It returns the new encoding position. @@ -275,12 +267,6 @@ class ABIFunctions /// @returns the size of the static part of the encoding of the given types. static size_t headSize(TypePointers const& _targetTypes); - /// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed - /// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix, - /// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix. - /// If @a _startSuffix == @a _endSuffix, the empty string is returned. - static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix); - /// @returns the number of variables needed to store a type. /// This is one for almost all types. The exception being dynamically sized calldata arrays or /// external function types (if we are encoding from stack, i.e. _options.encodeFunctionFromStack diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 5c4a65323027..08d30f537210 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -32,8 +33,9 @@ using namespace std; using namespace dev; +using namespace dev::eth; using namespace langutil; -using namespace solidity; +using namespace dev::solidity; void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const { @@ -43,7 +45,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack layout: [source_ref] [source length] target_ref (top) solAssert(_targetType.location() == DataLocation::Storage, ""); - TypePointer uint256 = make_shared(256); + TypePointer uint256 = TypeProvider::uint256(); TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType(); TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType(); @@ -73,8 +75,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons } // stack: target_ref source_ref source_length - TypePointer targetType = _targetType.shared_from_this(); - TypePointer sourceType = _sourceType.shared_from_this(); + TypePointer targetType = &_targetType; + TypePointer sourceType = &_sourceType; m_context.callLowLevelFunction( "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), 3, @@ -335,7 +337,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context << Instruction::DUP3 << Instruction::DUP5; accessIndex(_sourceType, false); MemoryItem(m_context, *_sourceType.baseType(), true).retrieveValue(SourceLocation(), true); - if (auto baseArray = dynamic_cast(_sourceType.baseType().get())) + if (auto baseArray = dynamic_cast(_sourceType.baseType())) copyArrayToMemory(*baseArray, _padToWordBoundaries); else utils.storeInMemoryDynamic(*_sourceType.baseType()); @@ -492,7 +494,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord else m_context << Instruction::DUP2 << u256(0); StorageItem(m_context, *_sourceType.baseType()).retrieveValue(SourceLocation(), true); - if (auto baseArray = dynamic_cast(_sourceType.baseType().get())) + if (auto baseArray = dynamic_cast(_sourceType.baseType())) copyArrayToMemory(*baseArray, _padToWordBoundaries); else utils.storeInMemoryDynamic(*_sourceType.baseType()); @@ -529,7 +531,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord void ArrayUtils::clearArray(ArrayType const& _typeIn) const { - TypePointer type = _typeIn.shared_from_this(); + TypePointer type = &_typeIn; m_context.callLowLevelFunction( "$clearArray_" + _typeIn.identifier(), 2, @@ -583,7 +585,7 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::ADD << Instruction::SWAP1; if (_type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(make_shared(256)); + ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); else ArrayUtils(_context).clearStorageLoop(_type.baseType()); _context << Instruction::POP; @@ -624,7 +626,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const << Instruction::SWAP1; // stack: data_pos_end data_pos if (_type.storageStride() < 32) - clearStorageLoop(make_shared(256)); + clearStorageLoop(TypeProvider::uint256()); else clearStorageLoop(_type.baseType()); // cleanup @@ -634,7 +636,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const { - TypePointer type = _typeIn.shared_from_this(); + TypePointer type = &_typeIn; m_context.callLowLevelFunction( "$resizeDynamicArray_" + _typeIn.identifier(), 2, @@ -731,7 +733,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; // stack: ref new_length current_length first_word data_location_end data_location - ArrayUtils(_context).clearStorageLoop(make_shared(256)); + ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); _context << Instruction::POP; // stack: ref new_length current_length first_word solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); @@ -770,7 +772,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const _context << Instruction::SWAP2 << Instruction::ADD; // stack: ref new_length delete_end delete_start if (_type.storageStride() < 32) - ArrayUtils(_context).clearStorageLoop(make_shared(256)); + ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); else ArrayUtils(_context).clearStorageLoop(_type.baseType()); @@ -910,7 +912,7 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const } } -void ArrayUtils::clearStorageLoop(TypePointer const& _type) const +void ArrayUtils::clearStorageLoop(TypePointer _type) const { m_context.callLowLevelFunction( "$clearStorageLoop_" + _type->identifier(), diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index af8e3e96615a..4c382a5ca113 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -32,7 +32,7 @@ namespace solidity class CompilerContext; class Type; class ArrayType; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; /** * Class that provides code generation for handling arrays. @@ -81,7 +81,7 @@ class ArrayUtils /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). /// Stack pre: end_ref start_ref /// Stack post: end_ref - void clearStorageLoop(TypePointer const& _type) const; + void clearStorageLoop(TypePointer _type) const; /// Converts length to size (number of storage slots or calldata/memory bytes). /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. /// Stack pre: length diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ef642becf540..153ee5b8a583 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -53,11 +53,9 @@ using namespace std; using namespace langutil; - -namespace dev -{ -namespace solidity -{ +using namespace dev::eth; +using namespace dev; +using namespace dev::solidity; void CompilerContext::addStateVariable( VariableDeclaration const& _declaration, @@ -398,10 +396,7 @@ void CompilerContext::appendInlineAssembly( _assembly + "\n" "------------------ Errors: ----------------\n"; for (auto const& error: errorReporter.errors()) - message += SourceReferenceFormatter::formatExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + message += SourceReferenceFormatter::formatErrorInformation(*error); message += "-------------------------------------------\n"; solAssert(false, message); @@ -554,6 +549,3 @@ void CompilerContext::FunctionCompilationQueue::startFunction(Declaration const& m_functionsToCompile.pop(); m_alreadyCompiledFunctions.insert(&_function); } - -} -} diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 3744913c327a..869dd0dc80dd 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -203,7 +203,7 @@ class CompilerContext /// Append elements to the current instruction list and adjust @a m_stackOffset. CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm->append(_item); return *this; } - CompilerContext& operator<<(Instruction _instruction) { m_asm->append(_instruction); return *this; } + CompilerContext& operator<<(dev::eth::Instruction _instruction) { m_asm->append(_instruction); return *this; } CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 3d207b8e382c..d6be20328839 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -31,11 +32,9 @@ using namespace std; using namespace langutil; - -namespace dev -{ -namespace solidity -{ +using namespace dev; +using namespace dev::eth; +using namespace dev::solidity; unsigned const CompilerUtils::dataStartOffset = 4; size_t const CompilerUtils::freeMemoryPointer = 64; @@ -85,13 +84,13 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer() void CompilerUtils::revertWithStringData(Type const& _argumentType) { - solAssert(_argumentType.isImplicitlyConvertibleTo(*Type::fromElementaryTypeName("string memory")), ""); + solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory")), ""); fetchFreeMemoryPointer(); m_context << (u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256("Error(string)")))) << (256 - 32)); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(4) << Instruction::ADD; // Stack: - abiEncode({_argumentType.shared_from_this()}, {make_shared(DataLocation::Memory, true)}); + abiEncode({&_argumentType}, {TypeProvider::array(DataLocation::Memory, true)}); toSizeAfterFreeMemoryPointer(); m_context << Instruction::REVERT; } @@ -187,7 +186,7 @@ void CompilerUtils::loadFromMemoryDynamic( void CompilerUtils::storeInMemory(unsigned _offset) { - unsigned numBytes = prepareMemoryStore(IntegerType::uint256(), true); + unsigned numBytes = prepareMemoryStore(*TypeProvider::uint256(), true); if (numBytes > 0) m_context << u256(_offset) << Instruction::MSTORE; } @@ -201,7 +200,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound ref->location() == DataLocation::Memory, "Only in-memory reference type can be stored." ); - storeInMemoryDynamic(IntegerType::uint256(), _padToWordBoundaries); + storeInMemoryDynamic(*TypeProvider::uint256(), _padToWordBoundaries); } else if (auto str = dynamic_cast(&_type)) { @@ -313,11 +312,11 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem else { // first load from calldata and potentially convert to memory if arrayType is memory - TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false); + TypePointer calldataType = TypeProvider::withLocation(&arrayType, DataLocation::CallData, false); if (calldataType->isDynamicallySized()) { // put on stack: data_pointer length - loadFromMemoryDynamic(IntegerType::uint256(), !_fromMemory); + loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory); m_context << Instruction::SWAP1; // stack: input_end base_offset next_pointer data_offset m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"}); @@ -328,7 +327,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem {"input_end", "base_offset", "next_ptr", "array_head_ptr"} ); // retrieve length - loadFromMemoryDynamic(IntegerType::uint256(), !_fromMemory, true); + loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true); // stack: input_end base_offset next_pointer array_length data_pointer m_context << Instruction::SWAP2; // stack: input_end base_offset data_pointer array_length next_pointer @@ -456,7 +455,7 @@ void CompilerUtils::encodeToMemory( type = _givenTypes[i]; // delay conversion else convertType(*_givenTypes[i], *targetType, true); - if (auto arrayType = dynamic_cast(type.get())) + if (auto arrayType = dynamic_cast(type)) ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); else storeInMemoryDynamic(*type, _padToWordBoundaries); @@ -484,7 +483,7 @@ void CompilerUtils::encodeToMemory( { auto const& strType = dynamic_cast(*_givenTypes[i]); m_context << u256(strType.value().size()); - storeInMemoryDynamic(IntegerType::uint256(), true); + storeInMemoryDynamic(*TypeProvider::uint256(), true); // stack: ... storeInMemoryDynamic(strType, _padToWordBoundaries); } @@ -499,7 +498,7 @@ void CompilerUtils::encodeToMemory( m_context << dupInstruction(1 + arrayType.sizeOnStack()); ArrayUtils(m_context).retrieveLength(arrayType, 1); // stack: ... - storeInMemoryDynamic(IntegerType::uint256(), true); + storeInMemoryDynamic(*TypeProvider::uint256(), true); // stack: ... // copy the new memory pointer m_context << swapInstruction(arrayType.sizeOnStack() + 1) << Instruction::POP; @@ -870,7 +869,7 @@ void CompilerUtils::convertType( allocateMemory(storageSize); // stack: mempos m_context << Instruction::DUP1 << u256(data.size()); - storeInMemoryDynamic(IntegerType::uint256()); + storeInMemoryDynamic(*TypeProvider::uint256()); // stack: mempos datapos storeStringData(data); } @@ -919,7 +918,7 @@ void CompilerUtils::convertType( if (targetType.isDynamicallySized()) { m_context << Instruction::DUP2; - storeInMemoryDynamic(IntegerType::uint256()); + storeInMemoryDynamic(*TypeProvider::uint256()); } // stack: (variably sized) if (targetType.baseType()->isValueType()) @@ -989,32 +988,48 @@ void CompilerUtils::convertType( switch (typeOnStack.location()) { case DataLocation::Storage: - // stack: - allocateMemory(typeOnStack.memorySize()); - m_context << Instruction::SWAP1 << Instruction::DUP2; - // stack: - for (auto const& member: typeOnStack.members(nullptr)) + { + auto conversionImpl = + [typeOnStack = &typeOnStack, targetType = &targetType](CompilerContext& _context) { - if (!member.type->canLiveOutsideStorage()) - continue; - pair const& offsets = typeOnStack.storageOffsetsOfMember(member.name); - m_context << offsets.first << Instruction::DUP3 << Instruction::ADD; - m_context << u256(offsets.second); - StorageItem(m_context, *member.type).retrieveValue(SourceLocation(), true); - TypePointer targetMemberType = targetType.memberType(member.name); - solAssert(!!targetMemberType, "Member not found in target type."); - convertType(*member.type, *targetMemberType, true); - storeInMemoryDynamic(*targetMemberType, true); - } - m_context << Instruction::POP << Instruction::POP; + CompilerUtils utils(_context); + // stack: + utils.allocateMemory(typeOnStack->memorySize()); + _context << Instruction::SWAP1 << Instruction::DUP2; + // stack: + for (auto const& member: typeOnStack->members(nullptr)) + { + if (!member.type->canLiveOutsideStorage()) + continue; + pair const& offsets = typeOnStack->storageOffsetsOfMember(member.name); + _context << offsets.first << Instruction::DUP3 << Instruction::ADD; + _context << u256(offsets.second); + StorageItem(_context, *member.type).retrieveValue(SourceLocation(), true); + TypePointer targetMemberType = targetType->memberType(member.name); + solAssert(!!targetMemberType, "Member not found in target type."); + utils.convertType(*member.type, *targetMemberType, true); + utils.storeInMemoryDynamic(*targetMemberType, true); + } + _context << Instruction::POP << Instruction::POP; + }; + if (typeOnStack.recursive()) + m_context.callLowLevelFunction( + "$convertRecursiveArrayStorageToMemory_" + typeOnStack.identifier() + "_to_" + targetType.identifier(), + 1, + 1, + conversionImpl + ); + else + conversionImpl(m_context); break; + } case DataLocation::CallData: { solUnimplementedAssert(!typeOnStack.isDynamicallyEncoded(), ""); m_context << Instruction::DUP1; m_context << Instruction::CALLDATASIZE; m_context << Instruction::SUB; - abiDecode({targetType.shared_from_this()}, false); + abiDecode({&targetType}, false); break; } case DataLocation::Memory: @@ -1128,6 +1143,14 @@ void CompilerUtils::pushZeroValue(Type const& _type) m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { _context.appendInvalid(); }); + if (CompilerContext* runCon = m_context.runtimeContext()) + { + leftShiftNumberOnStack(32); + m_context << runCon->lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { + _context.appendInvalid(); + }).toSubAssemblyTag(m_context.runtimeSub()); + m_context << Instruction::OR; + } return; } } @@ -1147,7 +1170,7 @@ void CompilerUtils::pushZeroValue(Type const& _type) return; } - TypePointer type = _type.shared_from_this(); + TypePointer type = &_type; m_context.callLowLevelFunction( "$pushZeroValue_" + referenceType->identifier(), 0, @@ -1157,13 +1180,13 @@ void CompilerUtils::pushZeroValue(Type const& _type) utils.allocateMemory(max(32u, type->calldataEncodedSize())); _context << Instruction::DUP1; - if (auto structType = dynamic_cast(type.get())) + if (auto structType = dynamic_cast(type)) for (auto const& member: structType->members(nullptr)) { utils.pushZeroValue(*member.type); utils.storeInMemoryDynamic(*member.type); } - else if (auto arrayType = dynamic_cast(type.get())) + else if (auto arrayType = dynamic_cast(type)) { solAssert(!arrayType->isDynamicallySized(), ""); if (arrayType->length() > 0) @@ -1260,10 +1283,10 @@ void CompilerUtils::popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jum m_context.adjustStackOffset(amount); } -unsigned CompilerUtils::sizeOnStack(vector> const& _variableTypes) +unsigned CompilerUtils::sizeOnStack(vector const& _variableTypes) { unsigned size = 0; - for (shared_ptr const& type: _variableTypes) + for (Type const* const& type: _variableTypes) size += type->sizeOnStack(); return size; } @@ -1306,7 +1329,7 @@ void CompilerUtils::storeStringData(bytesConstRef _data) for (unsigned i = 0; i < _data.size(); i += 32) { m_context << h256::Arith(h256(_data.cropped(i), h256::AlignLeft)); - storeInMemoryDynamic(IntegerType::uint256()); + storeInMemoryDynamic(*TypeProvider::uint256()); } m_context << Instruction::POP; } @@ -1414,6 +1437,3 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) return numBytes; } - -} -} diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 988ac389740e..8c9f1dc4373e 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -23,6 +23,8 @@ #pragma once #include +#include +#include #include namespace dev { @@ -80,7 +82,7 @@ class CompilerUtils /// @returns the number of bytes consumed in memory. unsigned loadFromMemory( unsigned _offset, - Type const& _type = IntegerType::uint256(), + Type const& _type = *TypeProvider::uint256(), bool _fromCalldata = false, bool _padToWords = false ); @@ -264,7 +266,7 @@ class CompilerUtils template static unsigned sizeOnStack(std::vector const& _variables); - static unsigned sizeOnStack(std::vector> const& _variableTypes); + static unsigned sizeOnStack(std::vector const& _variableTypes); /// Helper function to shift top value on the stack to the left. /// Stack pre: diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 2e533894218c..52f81cc3b190 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -39,6 +40,7 @@ using namespace std; using namespace dev; using namespace langutil; +using namespace dev::eth; using namespace dev::solidity; namespace @@ -870,7 +872,7 @@ bool ContractCompiler::visit(Return const& _return) TypePointer expectedType; if (expression->annotation().type->category() == Type::Category::Tuple || types.size() != 1) - expectedType = make_shared(types); + expectedType = TypeProvider::tuple(move(types)); else expectedType = types.front(); compileExpression(*expression, expectedType); @@ -914,7 +916,7 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar CompilerUtils utils(m_context); compileExpression(*expression); TypePointers valueTypes; - if (auto tupleType = dynamic_cast(expression->annotation().type.get())) + if (auto tupleType = dynamic_cast(expression->annotation().type)) valueTypes = tupleType->components(); else valueTypes = TypePointers{expression->annotation().type}; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 986d6b8c7fef..bde4a167b14e 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -39,11 +40,10 @@ using namespace std; using namespace langutil; +using namespace dev; +using namespace dev::eth; +using namespace dev::solidity; -namespace dev -{ -namespace solidity -{ void ExpressionCompiler::compile(Expression const& _expression) { @@ -103,7 +103,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& for (size_t i = 0; i < paramTypes.size(); ++i) { - if (auto mappingType = dynamic_cast(returnType.get())) + if (auto mappingType = dynamic_cast(returnType)) { solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); @@ -153,7 +153,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context << u256(0); returnType = mappingType->valueType(); } - else if (auto arrayType = dynamic_cast(returnType.get())) + else if (auto arrayType = dynamic_cast(returnType)) { // pop offset m_context << Instruction::POP; @@ -177,7 +177,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& unsigned retSizeOnStack = 0; auto returnTypes = accessorType.returnParameterTypes(); solAssert(returnTypes.size() >= 1, ""); - if (StructType const* structType = dynamic_cast(returnType.get())) + if (StructType const* structType = dynamic_cast(returnType)) { // remove offset m_context << Instruction::POP; @@ -187,7 +187,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { if (returnTypes[i]->category() == Type::Category::Mapping) continue; - if (auto arrayType = dynamic_cast(returnTypes[i].get())) + if (auto arrayType = dynamic_cast(returnTypes[i])) if (!arrayType->isByteArray()) continue; pair const& offsets = structType->storageOffsetsOfMember(names[i]); @@ -497,7 +497,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) functionType = structType.constructorType(); } else - functionType = dynamic_pointer_cast(_functionCall.expression().annotation().type); + functionType = dynamic_cast(_functionCall.expression().annotation().type); TypePointers parameterTypes = functionType->parameterTypes(); vector> const& callArguments = _functionCall.arguments(); @@ -648,7 +648,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.expression().accept(*this); arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, IntegerType::uint256(), true); + utils().convertType(*arguments.front()->annotation().type, *TypeProvider::uint256(), true); // Note that function is not the original function, but the ".gas" function. // Its values of gasSet and valueSet is equal to the original function's though. unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); @@ -732,9 +732,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments.front()->accept(*this); // Optimization: If type is bytes or string, then do not encode, // but directly compute keccak256 on memory. - if (*argType == ArrayType::bytesMemory() || *argType == ArrayType::stringMemory()) + if (*argType == *TypeProvider::bytesMemory() || *argType == *TypeProvider::stringMemory()) { - ArrayUtils(m_context).retrieveLength(ArrayType::bytesMemory()); + ArrayUtils(m_context).retrieveLength(*TypeProvider::bytesMemory()); m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD; } else @@ -781,7 +781,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { ++numIndexed; arguments[arg - 1]->accept(*this); - if (auto const& referenceType = dynamic_pointer_cast(paramTypes[arg - 1])) + if (auto const& referenceType = dynamic_cast(paramTypes[arg - 1])) { utils().fetchFreeMemoryPointer(); utils().packedEncode( @@ -836,13 +836,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::MulMod: { arguments[2]->accept(*this); - utils().convertType(*arguments[2]->annotation().type, IntegerType::uint256()); + utils().convertType(*arguments[2]->annotation().type, *TypeProvider::uint256()); m_context << Instruction::DUP1 << Instruction::ISZERO; m_context.appendConditionalInvalid(); for (unsigned i = 1; i < 3; i ++) { arguments[2 - i]->accept(*this); - utils().convertType(*arguments[2 - i]->annotation().type, IntegerType::uint256()); + utils().convertType(*arguments[2 - i]->annotation().type, *TypeProvider::uint256()); } if (function.kind() == FunctionType::Kind::AddMod) m_context << Instruction::ADDMOD; @@ -873,10 +873,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(function.parameterTypes().size() == 1, ""); solAssert(!!function.parameterTypes()[0], ""); TypePointer paramType = function.parameterTypes()[0]; - shared_ptr arrayType = + ArrayType const* arrayType = function.kind() == FunctionType::Kind::ArrayPush ? - make_shared(DataLocation::Storage, paramType) : - make_shared(DataLocation::Storage); + TypeProvider::array(DataLocation::Storage, paramType) : + TypeProvider::array(DataLocation::Storage); // stack: ArrayReference arguments[0]->accept(*this); @@ -928,7 +928,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // Fetch requested length. arguments[0]->accept(*this); - utils().convertType(*arguments[0]->annotation().type, IntegerType::uint256()); + utils().convertType(*arguments[0]->annotation().type, *TypeProvider::uint256()); // Stack: requested_length utils().fetchFreeMemoryPointer(); @@ -1058,11 +1058,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature) { // hash the signature - if (auto const* stringType = dynamic_cast(selectorType.get())) + if (auto const* stringType = dynamic_cast(selectorType)) { FixedHash<4> hash(dev::keccak256(stringType->value())); m_context << (u256(FixedHash<4>::Arith(hash)) << (256 - 32)); - dataOnStack = make_shared(4); + dataOnStack = TypeProvider::fixedBytes(4); } else { @@ -1073,7 +1073,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::KECCAK256; // stack: - dataOnStack = make_shared(32); + dataOnStack = TypeProvider::fixedBytes(32); } } else @@ -1104,7 +1104,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments.front()->accept(*this); TypePointer firstArgType = arguments.front()->annotation().type; TypePointers targetTypes; - if (TupleType const* targetTupleType = dynamic_cast(_functionCall.annotation().type.get())) + if (TupleType const* targetTupleType = dynamic_cast(_functionCall.annotation().type)) targetTypes = targetTupleType->components(); else targetTypes = TypePointers{_functionCall.annotation().type}; @@ -1115,7 +1115,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().abiDecode(targetTypes, false); else { - utils().convertType(*firstArgType, ArrayType::bytesMemory()); + utils().convertType(*firstArgType, *TypeProvider::bytesMemory()); m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; m_context << Instruction::SWAP1 << Instruction::MLOAD; // stack now: @@ -1146,7 +1146,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); // Check whether the member is a bound function. ASTString const& member = _memberAccess.memberName(); - if (auto funType = dynamic_cast(_memberAccess.annotation().type.get())) + if (auto funType = dynamic_cast(_memberAccess.annotation().type)) if (funType->bound()) { _memberAccess.expression().accept(*this); @@ -1175,14 +1175,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) // Special processing for TypeType because we do not want to visit the library itself // for internal functions, or enum/struct definitions. - if (TypeType const* type = dynamic_cast(_memberAccess.expression().annotation().type.get())) + if (TypeType const* type = dynamic_cast(_memberAccess.expression().annotation().type)) { - if (dynamic_cast(type->actualType().get())) + if (dynamic_cast(type->actualType())) { solAssert(_memberAccess.annotation().type, "_memberAccess has no type"); if (auto variable = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) appendVariable(*variable, static_cast(_memberAccess)); - else if (auto funType = dynamic_cast(_memberAccess.annotation().type.get())) + else if (auto funType = dynamic_cast(_memberAccess.annotation().type)) { switch (funType->kind()) { @@ -1224,14 +1224,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "unsupported member function"); } } - else if (dynamic_cast(_memberAccess.annotation().type.get())) + else if (dynamic_cast(_memberAccess.annotation().type)) { // no-op } else _memberAccess.expression().accept(*this); } - else if (auto enumType = dynamic_cast(type->actualType().get())) + else if (auto enumType = dynamic_cast(type->actualType())) { _memberAccess.expression().accept(*this); m_context << enumType->memberValue(_memberAccess.memberName()); @@ -1289,7 +1289,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) identifier = FunctionType(*function).externalIdentifier(); else solAssert(false, "Contract member is neither variable nor function."); - utils().convertType(type, type.isPayable() ? AddressType::addressPayable() : AddressType::address(), true); + utils().convertType(type, type.isPayable() ? *TypeProvider::payableAddress() : *TypeProvider::address(), true); m_context << identifier; } else @@ -1307,7 +1307,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) { utils().convertType( *_memberAccess.expression().annotation().type, - AddressType::address(), + *TypeProvider::address(), true ); m_context << Instruction::BALANCE; @@ -1324,7 +1324,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if ((set{"call", "callcode", "delegatecall", "staticcall"}).count(member)) utils().convertType( *_memberAccess.expression().annotation().type, - AddressType::address(), + *TypeProvider::address(), true ); else @@ -1401,12 +1401,17 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; utils().storeStringData(contract.name()); } + else if ((set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member)) + { + // no-op + } else solAssert(false, "Unknown magic member."); break; case Type::Category::Struct: { StructType const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + TypePointer const& memberType = _memberAccess.annotation().type; switch (type.location()) { case DataLocation::Storage: @@ -1419,7 +1424,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case DataLocation::Memory: { m_context << type.memoryOffsetOfMember(member) << Instruction::ADD; - setLValue(_memberAccess, *_memberAccess.annotation().type); + setLValue(_memberAccess, *memberType); break; } case DataLocation::CallData: @@ -1428,21 +1433,28 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) { m_context << Instruction::DUP1; m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; - CompilerUtils(m_context).accessCalldataTail(*_memberAccess.annotation().type); + CompilerUtils(m_context).accessCalldataTail(*memberType); } else { m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; // For non-value types the calldata offset is returned directly. - if (_memberAccess.annotation().type->isValueType()) + if (memberType->isValueType()) { - solAssert(_memberAccess.annotation().type->calldataEncodedSize() > 0, ""); - CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false); + solAssert(memberType->calldataEncodedSize() > 0, ""); + solAssert(memberType->storageBytes() <= 32, ""); + if (memberType->storageBytes() < 32 && m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + { + m_context << u256(32); + CompilerUtils(m_context).abiDecodeV2({memberType}, false); + } + else + CompilerUtils(m_context).loadFromMemoryDynamic(*memberType, true, true, false); } else solAssert( - _memberAccess.annotation().type->category() == Type::Category::Array || - _memberAccess.annotation().type->category() == Type::Category::Struct, + memberType->category() == Type::Category::Array || + memberType->category() == Type::Category::Struct, "" ); } @@ -1535,7 +1547,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) TypePointers{keyType} ); m_context << Instruction::SWAP1; - utils().storeInMemoryDynamic(IntegerType::uint256()); + utils().storeInMemoryDynamic(*TypeProvider::uint256()); utils().toSizeAfterFreeMemoryPointer(); } else @@ -1544,7 +1556,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression()); m_context << Instruction::SWAP1; solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - utils().storeInMemoryDynamic(IntegerType::uint256()); + utils().storeInMemoryDynamic(*TypeProvider::uint256()); m_context << u256(0); } m_context << Instruction::KECCAK256; @@ -1557,7 +1569,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) solAssert(_indexAccess.indexExpression(), "Index expression expected."); _indexAccess.indexExpression()->accept(*this); - utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true); + utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true); // stack layout: [] switch (arrayType.location()) { @@ -1589,12 +1601,25 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) { ArrayUtils(m_context).accessIndex(arrayType, true); if (arrayType.baseType()->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic( - *arrayType.baseType(), - true, - !arrayType.isByteArray(), - false - ); + { + solAssert(arrayType.baseType()->storageBytes() <= 32, ""); + if ( + !arrayType.isByteArray() && + arrayType.baseType()->storageBytes() < 32 && + m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) + ) + { + m_context << u256(32); + CompilerUtils(m_context).abiDecodeV2({arrayType.baseType()}, false); + } + else + CompilerUtils(m_context).loadFromMemoryDynamic( + *arrayType.baseType(), + true, + !arrayType.isByteArray(), + false + ); + } else solAssert( arrayType.baseType()->category() == Type::Category::Struct || @@ -1611,7 +1636,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) solAssert(_indexAccess.indexExpression(), "Index expression expected."); _indexAccess.indexExpression()->accept(*this); - utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true); + utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true); // stack layout: // check out-of-bounds access m_context << u256(fixedBytesType.numBytes()); @@ -2191,7 +2216,7 @@ void ExpressionCompiler::appendExternalFunctionCall( needToUpdateFreeMemoryPtr = true; else for (auto const& retType: returnTypes) - if (dynamic_cast(retType.get())) + if (dynamic_cast(retType)) needToUpdateFreeMemoryPtr = true; // Stack: return_data_start @@ -2269,6 +2294,3 @@ CompilerUtils ExpressionCompiler::utils() { return CompilerUtils(m_context); } - -} -} diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index d3fe4e4394bf..a49963f69a50 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -29,12 +29,13 @@ using namespace std; using namespace dev; +using namespace dev::eth; +using namespace dev::solidity; using namespace langutil; -using namespace solidity; StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): - LValue(_compilerContext, _declaration.annotation().type.get()), + LValue(_compilerContext, _declaration.annotation().type), m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)), m_size(m_dataType->sizeOnStack()) { @@ -418,11 +419,8 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const } } -/// Used in StorageByteArrayElement -static FixedBytesType byteType(1); - StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): - LValue(_compilerContext, &byteType) + LValue(_compilerContext, TypeProvider::byte()) { } @@ -474,7 +472,7 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer } StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType): - LValue(_compilerContext, _arrayType.memberType("length").get()), + LValue(_compilerContext, _arrayType.memberType("length")), m_arrayType(_arrayType) { solAssert(m_arrayType.isDynamicallySized(), ""); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 489980f967af..7ede6d6f0a52 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -145,7 +145,7 @@ string YulUtilFunctions::leftAlignFunction(Type const& _type) templ("body", "aligned := value"); break; case Type::Category::Contract: - templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)"); + templ("body", "aligned := " + leftAlignFunction(*TypeProvider::address()) + "(value)"); break; case Type::Category::Enum: { @@ -252,6 +252,36 @@ string YulUtilFunctions::roundUpFunction() }); } +string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits) +{ + solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); + string functionName = "checked_add_uint_" + to_string(_bits); + return m_functionCollector->createFunction(functionName, [&]() { + if (_bits < 256) + return + Whiskers(R"( + function (x, y) -> sum { + let mask := + sum := add(and(x, mask), and(y, mask)) + if and(sum, not(mask)) { revert(0, 0) } + } + )") + ("functionName", functionName) + ("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) + .render(); + else + return + Whiskers(R"( + function (x, y) -> sum { + sum := add(x, y) + if lt(sum, x) { revert(0, 0) } + } + )") + ("functionName", functionName) + .render(); + }); +} + string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) { string functionName = "array_length_" + _type.identifier(); @@ -330,40 +360,53 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) { string functionName = "array_dataslot_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { - if (_type.dataStoredIn(DataLocation::Memory)) - { - if (_type.isDynamicallySized()) - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := add(memPtr, 0x20) - } - )") - ("functionName", functionName) - .render(); - else - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := memPtr - } - )") - ("functionName", functionName) - .render(); - } - else if (_type.dataStoredIn(DataLocation::Storage)) + switch (_type.location()) { - if (_type.isDynamicallySized()) - { - Whiskers w(R"( - function (slot) -> dataSlot { - mstore(0, slot) - dataSlot := keccak256(0, 0x20) - } - )"); - w("functionName", functionName); - return w.render(); - } - else + case DataLocation::Memory: + if (_type.isDynamicallySized()) + return Whiskers(R"( + function (memPtr) -> dataPtr { + dataPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + else + return Whiskers(R"( + function (memPtr) -> dataPtr { + dataPtr := memPtr + } + )") + ("functionName", functionName) + .render(); + case DataLocation::Storage: + if (_type.isDynamicallySized()) + { + Whiskers w(R"( + function (slot) -> dataSlot { + mstore(0, slot) + dataSlot := keccak256(0, 0x20) + } + )"); + w("functionName", functionName); + return w.render(); + } + else + { + Whiskers w(R"( + function (slot) -> dataSlot { + dataSlot := slot + } + )"); + w("functionName", functionName); + return w.render(); + } + case DataLocation::CallData: { + // Calldata arrays are stored as offset of the data area and length + // on the stack, so the offset already points to the data area. + // This might change, if calldata arrays are stored in a single + // stack slot at some point. Whiskers w(R"( function (slot) -> dataSlot { dataSlot := slot @@ -372,11 +415,8 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) w("functionName", functionName); return w.render(); } - } - else - { - // Not used for calldata - solAssert(false, ""); + default: + solAssert(false, ""); } }); } @@ -384,36 +424,40 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); - solAssert( - _type.location() == DataLocation::Memory || - _type.location() == DataLocation::Storage, - "" - ); - solAssert( - _type.location() == DataLocation::Memory || - _type.baseType()->storageBytes() > 16, - "" - ); + if (_type.dataStoredIn(DataLocation::Storage)) + solAssert(_type.baseType()->storageBytes() > 16, ""); string functionName = "array_nextElement_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { - if (_type.location() == DataLocation::Memory) - return Whiskers(R"( - function (memPtr) -> nextPtr { - nextPtr := add(memPtr, 0x20) - } - )") - ("functionName", functionName) - .render(); - else if (_type.location() == DataLocation::Storage) - return Whiskers(R"( - function (slot) -> nextSlot { - nextSlot := add(slot, 1) - } - )") - ("functionName", functionName) - .render(); - else - solAssert(false, ""); + switch (_type.location()) + { + case DataLocation::Memory: + return Whiskers(R"( + function (memPtr) -> nextPtr { + nextPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + case DataLocation::Storage: + return Whiskers(R"( + function (slot) -> nextSlot { + nextSlot := add(slot, 1) + } + )") + ("functionName", functionName) + .render(); + case DataLocation::CallData: + return Whiskers(R"( + function (calldataPtr) -> nextPtr { + nextPtr := add(calldataPtr, ) + } + )") + ("stride", toCompactHexWithPrefix(_type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize())) + ("functionName", functionName) + .render(); + default: + solAssert(false, ""); + } }); } @@ -436,3 +480,322 @@ string YulUtilFunctions::allocationFunction() }); } +string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) +{ + string functionName = + "convert_" + + _from.identifier() + + "_to_" + + _to.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> converted { + + } + )"); + templ("functionName", functionName); + string body; + auto toCategory = _to.category(); + auto fromCategory = _from.category(); + switch (fromCategory) + { + case Type::Category::Address: + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(IntegerType(160), _to)) + .render(); + break; + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::Contract: + { + if (RationalNumberType const* rational = dynamic_cast(&_from)) + solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); + if (toCategory == Type::Category::FixedBytes) + { + solAssert( + fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, + "Invalid conversion to FixedBytesType requested." + ); + FixedBytesType const& toBytesType = dynamic_cast(_to); + body = + Whiskers("converted := ((value))") + ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) + ("clean", cleanupFunction(_from)) + .render(); + } + else if (toCategory == Type::Category::Enum) + { + solAssert(_from.mobileType(), ""); + body = + Whiskers("converted := ((value))") + ("cleanEnum", cleanupFunction(_to)) + // "mobileType()" returns integer type for rational + ("cleanInt", cleanupFunction(*_from.mobileType())) + .render(); + } + else if (toCategory == Type::Category::FixedPoint) + solUnimplemented("Not yet implemented - FixedPointType."); + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); + else + { + solAssert( + toCategory == Type::Category::Integer || + toCategory == Type::Category::Contract, + ""); + IntegerType const addressType(160); + IntegerType const& to = + toCategory == Type::Category::Integer ? + dynamic_cast(_to) : + addressType; + + // Clean according to the "to" type, except if this is + // a widening conversion. + IntegerType const* cleanupType = &to; + if (fromCategory != Type::Category::RationalNumber) + { + IntegerType const& from = + fromCategory == Type::Category::Integer ? + dynamic_cast(_from) : + addressType; + if (to.numBits() > from.numBits()) + cleanupType = &from; + } + body = + Whiskers("converted := (value)") + ("cleanInt", cleanupFunction(*cleanupType)) + .render(); + } + break; + } + case Type::Category::Bool: + { + solAssert(_from == _to, "Invalid conversion for bool."); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(_from)) + .render(); + break; + } + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + solUnimplementedAssert(false, "Array conversion not implemented."); + break; + case Type::Category::Struct: + solUnimplementedAssert(false, "Struct conversion not implemented."); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& from = dynamic_cast(_from); + if (toCategory == Type::Category::Integer) + body = + Whiskers("converted := ((value))") + ("shift", shiftRightFunction(256 - from.numBytes() * 8)) + ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) + .render(); + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); + else + { + // clear for conversion to longer bytes + solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(from)) + .render(); + } + break; + } + case Type::Category::Function: + { + solAssert(false, "Conversion should not be called for function types."); + break; + } + case Type::Category::Enum: + { + solAssert(toCategory == Type::Category::Integer || _from == _to, ""); + EnumType const& enumType = dynamic_cast(_from); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(enumType)) + .render(); + break; + } + case Type::Category::Tuple: + { + solUnimplementedAssert(false, "Tuple conversion not implemented."); + break; + } + default: + solAssert(false, ""); + } + + solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); + templ("body", body); + return templ.render(); + }); +} + +string YulUtilFunctions::cleanupFunction(Type const& _type) +{ + string functionName = string("cleanup_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> cleaned { + + } + )"); + templ("functionName", functionName); + switch (_type.category()) + { + case Type::Category::Address: + templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); + break; + case Type::Category::Integer: + { + IntegerType const& type = dynamic_cast(_type); + if (type.numBits() == 256) + templ("body", "cleaned := value"); + else if (type.isSigned()) + templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); + else + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); + break; + } + case Type::Category::RationalNumber: + templ("body", "cleaned := value"); + break; + case Type::Category::Bool: + templ("body", "cleaned := iszero(iszero(value))"); + break; + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Function: + solAssert(dynamic_cast(_type).kind() == FunctionType::Kind::External, ""); + templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); + break; + case Type::Category::Array: + case Type::Category::Struct: + case Type::Category::Mapping: + solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); + templ("body", "cleaned := value"); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& type = dynamic_cast(_type); + if (type.numBytes() == 32) + templ("body", "cleaned := value"); + else if (type.numBytes() == 0) + // This is disallowed in the type system. + solAssert(false, ""); + else + { + size_t numBits = type.numBytes() * 8; + u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); + } + break; + } + case Type::Category::Contract: + { + AddressType addressType(dynamic_cast(_type).isPayable() ? + StateMutability::Payable : + StateMutability::NonPayable + ); + templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); + break; + } + case Type::Category::Enum: + { + // Out of range enums cannot be truncated unambigiously and therefore it should be an error. + templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); + break; + } + case Type::Category::InaccessibleDynamic: + templ("body", "cleaned := 0"); + break; + default: + solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) +{ + string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) { + if iszero() { } + } + )"); + templ("functionName", functionName); + if (_revertOnFailure) + templ("failure", "revert(0, 0)"); + else + templ("failure", "invalid()"); + + switch (_type.category()) + { + case Type::Category::Address: + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::FixedPoint: + case Type::Category::Function: + case Type::Category::Array: + case Type::Category::Struct: + case Type::Category::Mapping: + case Type::Category::FixedBytes: + case Type::Category::Contract: + { + templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); + break; + } + case Type::Category::Enum: + { + size_t members = dynamic_cast(_type).numberOfMembers(); + solAssert(members > 0, "empty enum should have caused a parser error."); + templ("condition", "lt(value, " + to_string(members) + ")"); + break; + } + case Type::Category::InaccessibleDynamic: + templ("condition", "1"); + break; + default: + solAssert(false, "Validation of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) +{ + string result; + if (_startSuffix < _endSuffix) + { + result = _baseName + to_string(_startSuffix++); + while (_startSuffix < _endSuffix) + result += ", " + _baseName + to_string(_startSuffix++); + } + else if (_endSuffix < _startSuffix) + { + result = _baseName + to_string(_endSuffix++); + while (_endSuffix < _startSuffix) + result = _baseName + to_string(_endSuffix++) + ", " + result; + } + return result; +} diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 92eb5532d738..8deb297140af 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -73,17 +73,19 @@ class YulUtilFunctions /// of 32 or the input if it is a multiple of 32. std::string roundUpFunction(); + std::string overflowCheckedUIntAddFunction(size_t _bits); + std::string arrayLengthFunction(ArrayType const& _type); /// @returns the name of a function that computes the number of bytes required /// to store an array in memory given its length (internally encoded, not ABI encoded). /// The function reverts for too large lengths. std::string arrayAllocationSizeFunction(ArrayType const& _type); /// @returns the name of a function that converts a storage slot number - /// or a memory pointer to the slot number / memory pointer for the data position of an array - /// which is stored in that slot / memory area. + /// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer + /// for the data position of an array which is stored in that slot / memory area / calldata area. std::string arrayDataAreaFunction(ArrayType const& _type); /// @returns the name of a function that advances an array data pointer to the next element. - /// Only works for memory arrays and storage arrays that store one item per slot. + /// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot. std::string nextArrayElementFunction(ArrayType const& _type); /// @returns the name of a function that allocates memory. @@ -92,6 +94,33 @@ class YulUtilFunctions /// Return value: pointer std::string allocationFunction(); + /// @returns the name of the function that converts a value of type @a _from + /// to a value of type @a _to. The resulting vale is guaranteed to be in range + /// (i.e. "clean"). Asserts on failure. + /// + /// This is used for data being encoded or general type conversions in the code. + std::string conversionFunction(Type const& _from, Type const& _to); + + /// @returns the name of the cleanup function for the given type and + /// adds its implementation to the requested functions. + /// The cleanup function defers to the validator function with "assert" + /// if there is no reasonable way to clean a value. + std::string cleanupFunction(Type const& _type); + + /// @returns the name of the validator function for the given type and + /// adds its implementation to the requested functions. + /// @param _revertOnFailure if true, causes revert on invalid data, + /// otherwise an assertion failure. + /// + /// This is used for data decoded from external sources. + std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); + + /// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed + /// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix, + /// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix. + /// If @a _startSuffix == @a _endSuffix, the empty string is returned. + static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix); + private: langutil::EVMVersion m_evmVersion; std::shared_ptr m_functionCollector; diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp new file mode 100644 index 000000000000..f0f076db60bb --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -0,0 +1,130 @@ +/* + 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 . +*/ +/** + * Class that contains contextual information during IR generation. + */ + +#include + +#include +#include + +#include + +using namespace dev; +using namespace dev::solidity; +using namespace std; + +string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl) +{ + solUnimplementedAssert( + _varDecl.annotation().type->sizeOnStack() == 1, + "Multi-slot types not yet implemented." + ); + + return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id()); +} + +string IRGenerationContext::variableName(VariableDeclaration const& _varDecl) +{ + solAssert( + m_localVariables.count(&_varDecl), + "Unknown variable: " + _varDecl.name() + ); + return m_localVariables[&_varDecl]; +} + +string IRGenerationContext::functionName(FunctionDefinition const& _function) +{ + // @TODO previously, we had to distinguish creation context and runtime context, + // but since we do not work with jump positions anymore, this should not be a problem, right? + return "fun_" + _function.name() + "_" + to_string(_function.id()); +} + +FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function) +{ + // @TODO previously, we had to distinguish creation context and runtime context, + // but since we do not work with jump positions anymore, this should not be a problem, right? + string name = _function.name(); + FunctionType functionType(_function); + for (auto const& contract: m_inheritanceHierarchy) + for (FunctionDefinition const* function: contract->definedFunctions()) + if ( + function->name() == name && + !function->isConstructor() && + FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType) + ) + return *function; + solAssert(false, "Super function " + name + " not found."); +} + +string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration) +{ + return functionName(virtualFunction(_functionDeclaration)); +} + +string IRGenerationContext::newYulVariable() +{ + return "_" + to_string(++m_varCounter); +} + +string IRGenerationContext::variable(Expression const& _expression) +{ + unsigned size = _expression.annotation().type->sizeOnStack(); + solUnimplementedAssert(size == 1, ""); + return "expr_" + to_string(_expression.id()); +} + +string IRGenerationContext::internalDispatch(size_t _in, size_t _out) +{ + // TODO can we limit the generated functions to only those visited + // in the expression context? What about creation / runtime context? + string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out); + return m_functions->createFunction(funName, [&]() { + Whiskers templ(R"( + function (fun ) -> { + switch fun + <#cases> + case + { + := () + } + + default { invalid() } + } + )"); + templ("functionName", funName); + templ("comma", _in > 0 ? "," : ""); + YulUtilFunctions utils(m_evmVersion, m_functions); + templ("in", utils.suffixedVariableNameList("in_", 0, _in)); + templ("out", utils.suffixedVariableNameList("out_", 0, _out)); + vector> functions; + for (auto const& contract: m_inheritanceHierarchy) + for (FunctionDefinition const* function: contract->definedFunctions()) + if ( + !function->isConstructor() && + function->parameters().size() == _in && + function->returnParameters().size() == _out + ) + functions.emplace_back(map { + { "funID", to_string(function->id()) }, + { "name", functionName(*function)} + }); + templ("cases", move(functions)); + return templ.render(); + }); +} diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h new file mode 100644 index 000000000000..d40a0f26061f --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -0,0 +1,87 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Class that contains contextual information during IR generation. + */ + +#pragma once + +#include + +#include + +#include + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class ContractDefinition; +class VariableDeclaration; +class FunctionDefinition; +class Expression; + +/** + * Class that contains contextual information during IR generation. + */ +class IRGenerationContext +{ +public: + IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_evmVersion(_evmVersion), + m_optimiserSettings(std::move(_optimiserSettings)), + m_functions(std::make_shared()) + {} + + std::shared_ptr functionCollector() const { return m_functions; } + + /// Sets the current inheritance hierarchy from derived to base. + void setInheritanceHierarchy(std::vector _hierarchy) + { + m_inheritanceHierarchy = std::move(_hierarchy); + } + + + std::string addLocalVariable(VariableDeclaration const& _varDecl); + std::string variableName(VariableDeclaration const& _varDecl); + std::string functionName(FunctionDefinition const& _function); + FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration); + std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration); + + std::string newYulVariable(); + /// @returns the variable (or comma-separated list of variables) that contain + /// the value of the given expression. + std::string variable(Expression const& _expression); + + std::string internalDispatch(size_t _in, size_t _out); + +private: + langutil::EVMVersion m_evmVersion; + OptimiserSettings m_optimiserSettings; + std::vector m_inheritanceHierarchy; + std::map m_localVariables; + std::shared_ptr m_functions; + size_t m_varCounter = 0; +}; + +} +} diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp new file mode 100644 index 000000000000..9b1ad3413bec --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -0,0 +1,253 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Alex Beregszaszi + * @date 2017 + * Component that translates Solidity code into Yul. + */ + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +pair IRGenerator::run(ContractDefinition const& _contract) +{ + // TODO Would be nice to pretty-print this while retaining comments. + string ir = generate(_contract); + + yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); + if (!asmStack.parseAndAnalyze("", ir)) + { + string errorMessage; + for (auto const& error: asmStack.errors()) + errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error); + solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir); + } + asmStack.optimize(); + + string warning = + "/*******************************************************\n" + " * WARNING *\n" + " * Solidity to Yul compilation is still EXPERIMENTAL *\n" + " * It can result in LOSS OF FUNDS or worse *\n" + " * !USE AT YOUR OWN RISK! *\n" + " *******************************************************/\n\n"; + + return {warning + ir, warning + asmStack.print()}; +} + +string IRGenerator::generate(ContractDefinition const& _contract) +{ + Whiskers t(R"( + object "" { + code { + + + + + } + object "" { + code { + + + + } + } + } + )"); + + resetContext(); + m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); + t("CreationObject", creationObjectName(_contract)); + t("memoryInit", memoryInit()); + t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : ""); + t("deploy", deployCode(_contract)); + t("functions", m_context.functionCollector()->requestedFunctions()); + + resetContext(); + m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); + t("RuntimeObject", runtimeObjectName(_contract)); + t("dispatch", dispatchRoutine(_contract)); + t("runtimeFunctions", m_context.functionCollector()->requestedFunctions()); + return t.render(); +} + +string IRGenerator::generate(Block const& _block) +{ + IRGeneratorForStatements generator(m_context, m_utils); + _block.accept(generator); + return generator.code(); +} + +string IRGenerator::generateFunction(FunctionDefinition const& _function) +{ + string functionName = m_context.functionName(_function); + return m_context.functionCollector()->createFunction(functionName, [&]() { + Whiskers t("\nfunction () {\n\n}\n"); + t("functionName", functionName); + string params; + for (auto const& varDecl: _function.parameters()) + params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); + t("params", params); + string retParams; + for (auto const& varDecl: _function.returnParameters()) + retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); + t("returns", retParams.empty() ? "" : " -> " + retParams); + t("body", generate(_function.body())); + return t.render(); + }); +} + +string IRGenerator::constructorCode(FunctionDefinition const& _constructor) +{ + string out; + if (!_constructor.isPayable()) + out = callValueCheck(); + + solUnimplemented("Constructors are not yet implemented."); + + return out; +} + +string IRGenerator::deployCode(ContractDefinition const& _contract) +{ + Whiskers t(R"X( + codecopy(0, dataoffset(""), datasize("")) + return(0, datasize("")) + )X"); + t("object", runtimeObjectName(_contract)); + return t.render(); +} + +string IRGenerator::callValueCheck() +{ + return "if callvalue() { revert(0, 0) }"; +} + +string IRGenerator::creationObjectName(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + to_string(_contract.id()); +} + +string IRGenerator::runtimeObjectName(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + to_string(_contract.id()) + "_deployed"; +} + +string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) +{ + Whiskers t(R"X( + if iszero(lt(calldatasize(), 4)) + { + let selector := (calldataload(0)) + switch selector + <#cases> + case + { + // + + (4, calldatasize()) + () + let memPos := (0) + let memEnd := (memPos ) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + + )X"); + t("shr224", m_utils.shiftRightFunction(224)); + vector> functions; + for (auto const& function: _contract.interfaceFunctions()) + { + functions.push_back({}); + map& templ = functions.back(); + templ["functionSelector"] = "0x" + function.first.hex(); + FunctionTypePointer const& type = function.second; + templ["functionName"] = type->externalSignature(); + templ["callValueCheck"] = type->isPayable() ? "" : callValueCheck(); + + unsigned paramVars = make_shared(type->parameterTypes())->sizeOnStack(); + unsigned retVars = make_shared(type->returnParameterTypes())->sizeOnStack(); + templ["assignToParams"] = paramVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("param_", 0, paramVars) + " := "; + templ["assignToRetParams"] = retVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("ret_", 0, retVars) + " := "; + + ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector()); + templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes()); + templ["params"] = m_utils.suffixedVariableNameList("param_", 0, paramVars); + templ["retParams"] = m_utils.suffixedVariableNameList("ret_", retVars, 0); + templ["function"] = generateFunction(dynamic_cast(type->declaration())); + templ["allocate"] = m_utils.allocationFunction(); + templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), false); + templ["comma"] = retVars == 0 ? "" : ", "; + } + t("cases", functions); + if (FunctionDefinition const* fallback = _contract.fallbackFunction()) + { + string fallbackCode; + if (!fallback->isPayable()) + fallbackCode += callValueCheck(); + fallbackCode += generateFunction(*fallback) + "() stop()"; + + t("fallback", fallbackCode); + } + else + t("fallback", "revert(0, 0)"); + return t.render(); +} + +string IRGenerator::memoryInit() +{ + // This function should be called at the beginning of the EVM call frame + // and thus can assume all memory to be zero, including the contents of + // the "zero memory area" (the position CompilerUtils::zeroPointer points to). + return + Whiskers{"mstore(, )"} + ("memPtr", to_string(CompilerUtils::freeMemoryPointer)) + ("generalPurposeStart", to_string(CompilerUtils::generalPurposeMemoryStart)) + .render(); +} + +void IRGenerator::resetContext() +{ + solAssert( + m_context.functionCollector()->requestedFunctions().empty(), + "Reset context while it still had functions." + ); + m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); + m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); +} diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h new file mode 100644 index 000000000000..d78acfabe760 --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -0,0 +1,81 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Alex Beregszaszi + * @date 2017 + * Component that translates Solidity code into Yul. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class SourceUnit; + +class IRGenerator +{ +public: + IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_evmVersion(_evmVersion), + m_optimiserSettings(_optimiserSettings), + m_context(_evmVersion, std::move(_optimiserSettings)), + m_utils(_evmVersion, m_context.functionCollector()) + {} + + /// Generates and returns the IR code, in unoptimized and optimized form + /// (or just pretty-printed, depending on the optimizer settings). + std::pair run(ContractDefinition const& _contract); + +private: + std::string generate(ContractDefinition const& _contract); + std::string generate(Block const& _block); + + /// Generates code for and returns the name of the function. + std::string generateFunction(FunctionDefinition const& _function); + + std::string constructorCode(FunctionDefinition const& _constructor); + std::string deployCode(ContractDefinition const& _contract); + std::string callValueCheck(); + + std::string creationObjectName(ContractDefinition const& _contract); + std::string runtimeObjectName(ContractDefinition const& _contract); + + std::string dispatchRoutine(ContractDefinition const& _contract); + + std::string memoryInit(); + + void resetContext(); + + langutil::EVMVersion const m_evmVersion; + OptimiserSettings const m_optimiserSettings; + + IRGenerationContext m_context; + YulUtilFunctions m_utils; +}; + +} +} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp new file mode 100644 index 000000000000..42d92fbd5522 --- /dev/null +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -0,0 +1,315 @@ +/* + 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 . +*/ +/** + * Component that translates Solidity code into Yul at statement level and below. + */ + +#include + +#include +#include + +#include +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +namespace +{ + +struct CopyTranslate: public yul::ASTCopier +{ + using ExternalRefsMap = std::map; + + CopyTranslate(IRGenerationContext& _context, ExternalRefsMap const& _references): + m_context(_context), m_references(_references) {} + + using ASTCopier::operator(); + + yul::YulString translateIdentifier(yul::YulString _name) override + { + return yul::YulString{"usr$" + _name.str()}; + } + + yul::Identifier translate(yul::Identifier const& _identifier) override + { + if (!m_references.count(&_identifier)) + return ASTCopier::translate(_identifier); + + auto const& reference = m_references.at(&_identifier); + auto const varDecl = dynamic_cast(reference.declaration); + solUnimplementedAssert(varDecl, ""); + solUnimplementedAssert( + reference.isOffset == false && reference.isSlot == false, + "" + ); + + return yul::Identifier{ + _identifier.location, + yul::YulString{m_context.variableName(*varDecl)} + }; + } + +private: + IRGenerationContext& m_context; + ExternalRefsMap const& m_references; +}; + +} + + + +bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDeclStatement) +{ + for (auto const& decl: _varDeclStatement.declarations()) + if (decl) + m_context.addLocalVariable(*decl); + + if (Expression const* expression = _varDeclStatement.initialValue()) + { + solUnimplementedAssert(_varDeclStatement.declarations().size() == 1, ""); + + expression->accept(*this); + + VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front(); + m_code << + "let " << + m_context.variableName(varDecl) << + " := " << + expressionAsType(*expression, *varDecl.type()) << + "\n"; + } + else + for (auto const& decl: _varDeclStatement.declarations()) + if (decl) + m_code << "let " << m_context.variableName(*decl) << "\n"; + + return false; +} + +bool IRGeneratorForStatements::visit(Assignment const& _assignment) +{ + solUnimplementedAssert(_assignment.assignmentOperator() == Token::Assign, ""); + + _assignment.rightHandSide().accept(*this); + + // TODO proper lvalue handling + auto const& lvalue = dynamic_cast(_assignment.leftHandSide()); + string varName = m_context.variableName(dynamic_cast(*lvalue.annotation().referencedDeclaration)); + + m_code << + varName << + " := " << + expressionAsType(_assignment.rightHandSide(), *lvalue.annotation().type) << + "\n"; + m_code << "let " << m_context.variable(_assignment) << " := " << varName << "\n"; + + return false; +} + +bool IRGeneratorForStatements::visit(Return const&) +{ + solUnimplemented("Return not yet implemented in yul code generation"); +} + +void IRGeneratorForStatements::endVisit(BinaryOperation const& _binOp) +{ + solUnimplementedAssert(_binOp.getOperator() == Token::Add, ""); + solUnimplementedAssert(*_binOp.leftExpression().annotation().type == *_binOp.rightExpression().annotation().type, ""); + if (IntegerType const* type = dynamic_cast(_binOp.annotation().commonType)) + { + solUnimplementedAssert(!type->isSigned(), ""); + m_code << + "let " << + m_context.variable(_binOp) << + " := " << + m_utils.overflowCheckedUIntAddFunction(type->numBits()) << + "(" << + m_context.variable(_binOp.leftExpression()) << + ", " << + m_context.variable(_binOp.rightExpression()) << + ")\n"; + } + else + solUnimplementedAssert(false, ""); +} + +bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall) +{ + solUnimplementedAssert( + _functionCall.annotation().kind == FunctionCallKind::FunctionCall || + _functionCall.annotation().kind == FunctionCallKind::TypeConversion, + "This type of function call is not yet implemented" + ); + + TypePointer const funcType = _functionCall.expression().annotation().type; + + if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) + { + solAssert(funcType->category() == Type::Category::TypeType, "Expected category to be TypeType"); + solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion"); + _functionCall.arguments().front()->accept(*this); + + m_code << + "let " << + m_context.variable(_functionCall) << + " := " << + expressionAsType(*_functionCall.arguments().front(), *_functionCall.annotation().type) << + "\n"; + + return false; + } + + FunctionTypePointer functionType = dynamic_cast(funcType); + + TypePointers parameterTypes = functionType->parameterTypes(); + vector> const& callArguments = _functionCall.arguments(); + vector> const& callArgumentNames = _functionCall.names(); + if (!functionType->takesArbitraryParameters()) + solAssert(callArguments.size() == parameterTypes.size(), ""); + + vector> arguments; + if (callArgumentNames.empty()) + // normal arguments + arguments = callArguments; + else + // named arguments + for (auto const& parameterName: functionType->parameterNames()) + { + auto const it = std::find_if(callArgumentNames.cbegin(), callArgumentNames.cend(), [&](ASTPointer const& _argName) { + return *_argName == parameterName; + }); + + solAssert(it != callArgumentNames.cend(), ""); + arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]); + } + + solUnimplementedAssert(!functionType->bound(), ""); + switch (functionType->kind()) + { + case FunctionType::Kind::Internal: + { + vector args; + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + + if (functionType->takesArbitraryParameters()) + args.emplace_back(m_context.variable(*arguments[i])); + else + args.emplace_back(expressionAsType(*arguments[i], *parameterTypes[i])); + } + + if (auto identifier = dynamic_cast(&_functionCall.expression())) + { + solAssert(!functionType->bound(), ""); + if (auto functionDef = dynamic_cast(identifier->annotation().referencedDeclaration)) + { + // @TODO The function can very well return multiple vars. + m_code << + "let " << + m_context.variable(_functionCall) << + " := " << + m_context.virtualFunctionName(*functionDef) << + "(" << + joinHumanReadable(args) << + ")\n"; + return false; + } + } + + _functionCall.expression().accept(*this); + + // @TODO The function can very well return multiple vars. + args = vector{m_context.variable(_functionCall.expression())} + args; + m_code << + "let " << + m_context.variable(_functionCall) << + " := " << + m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) << + "(" << + joinHumanReadable(args) << + ")\n"; + break; + } + default: + solUnimplemented(""); + } + return false; +} + +bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) +{ + CopyTranslate bodyCopier{m_context, _inlineAsm.annotation().externalReferences}; + + yul::Statement modified = bodyCopier(_inlineAsm.operations()); + + solAssert(modified.type() == typeid(yul::Block), ""); + + m_code << yul::AsmPrinter()(boost::get(std::move(modified))) << "\n"; + return false; +} + +bool IRGeneratorForStatements::visit(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + string value; + if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) + value = to_string(m_context.virtualFunction(*functionDef).id()); + else if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) + value = m_context.variableName(*varDecl); + else + solUnimplemented(""); + m_code << "let " << m_context.variable(_identifier) << " := " << value << "\n"; + return false; +} + +bool IRGeneratorForStatements::visit(Literal const& _literal) +{ + TypePointer type = _literal.annotation().type; + + switch (type->category()) + { + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::Address: + m_code << "let " << m_context.variable(_literal) << " := " << toCompactHexWithPrefix(type->literalValue(&_literal)) << "\n"; + break; + case Type::Category::StringLiteral: + solUnimplemented(""); + break; // will be done during conversion + default: + solUnimplemented("Only integer, boolean and string literals implemented for now."); + } + return false; +} + +string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to) +{ + Type const& from = *_expression.annotation().type; + string varName = m_context.variable(_expression); + + if (from == _to) + return varName; + else + return m_utils.conversionFunction(from, _to) + "(" + std::move(varName) + ")"; +} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h new file mode 100644 index 000000000000..b40d50ff4397 --- /dev/null +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -0,0 +1,67 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Component that translates Solidity code into Yul at statement level and below. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ + +class IRGenerationContext; +class YulUtilFunctions; + +/** + * Component that translates Solidity's AST into Yul at statement level and below. + * It is an AST visitor that appends to an internal string buffer. + */ +class IRGeneratorForStatements: public ASTConstVisitor +{ +public: + IRGeneratorForStatements(IRGenerationContext& _context, YulUtilFunctions& _utils): + m_context(_context), + m_utils(_utils) + {} + + std::string code() const { return m_code.str(); } + + bool visit(VariableDeclarationStatement const& _variableDeclaration) override; + bool visit(Assignment const& _assignment) override; + bool visit(Return const& _return) override; + void endVisit(BinaryOperation const& _binOp) override; + bool visit(FunctionCall const& _funCall) override; + bool visit(InlineAssembly const& _inlineAsm) override; + bool visit(Identifier const& _identifier) override; + bool visit(Literal const& _literal) override; + +private: + /// @returns a Yul expression representing the current value of @a _expression, + /// converted to type @a _to if it does not yet have that type. + std::string expressionAsType(Expression const& _expression, Type const& _to); + + std::ostringstream m_code; + IRGenerationContext& m_context; + YulUtilFunctions& m_utils; +}; + +} +} diff --git a/libsolidity/formal/EncodingContext.cpp b/libsolidity/formal/EncodingContext.cpp new file mode 100644 index 000000000000..176cc3d124b5 --- /dev/null +++ b/libsolidity/formal/EncodingContext.cpp @@ -0,0 +1,85 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::smt; + +EncodingContext::EncodingContext(SolverInterface& _solver): + m_solver(_solver), + m_thisAddress(make_unique("this", m_solver)) +{ + auto sort = make_shared( + make_shared(smt::Kind::Int), + make_shared(smt::Kind::Int) + ); + m_balances = make_unique(sort, "balances", m_solver); +} + +void EncodingContext::reset() +{ + m_thisAddress->increaseIndex(); + m_balances->increaseIndex(); +} + +smt::Expression EncodingContext::thisAddress() +{ + return m_thisAddress->currentValue(); +} + +smt::Expression EncodingContext::balance() +{ + return balance(m_thisAddress->currentValue()); +} + +smt::Expression EncodingContext::balance(smt::Expression _address) +{ + return smt::Expression::select(m_balances->currentValue(), move(_address)); +} + +void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value) +{ + unsigned indexBefore = m_balances->index(); + addBalance(_from, 0 - _value); + addBalance(_to, move(_value)); + unsigned indexAfter = m_balances->index(); + solAssert(indexAfter > indexBefore, ""); + m_balances->increaseIndex(); + /// Do not apply the transfer operation if _from == _to. + auto newBalances = smt::Expression::ite( + move(_from) == move(_to), + m_balances->valueAtIndex(indexBefore), + m_balances->valueAtIndex(indexAfter) + ); + m_solver.addAssertion(m_balances->currentValue() == newBalances); +} + +void EncodingContext::addBalance(smt::Expression _address, smt::Expression _value) +{ + auto newBalances = smt::Expression::store( + m_balances->currentValue(), + _address, + balance(_address) + move(_value) + ); + m_balances->increaseIndex(); + m_solver.addAssertion(newBalances == m_balances->currentValue()); +} diff --git a/libsolidity/formal/EncodingContext.h b/libsolidity/formal/EncodingContext.h new file mode 100644 index 000000000000..df6e69c07b8e --- /dev/null +++ b/libsolidity/formal/EncodingContext.h @@ -0,0 +1,66 @@ +/* + 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 . +*/ + +#pragma once + +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +/** + * Stores the context of the SMT encoding. + */ +class EncodingContext +{ +public: + EncodingContext(SolverInterface& _solver); + + /// Resets the entire context. + void reset(); + + /// Value of `this` address. + smt::Expression thisAddress(); + + /// @returns the symbolic balance of address `this`. + smt::Expression balance(); + /// @returns the symbolic balance of an address. + smt::Expression balance(smt::Expression _address); + /// Transfer _value from _from to _to. + void transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value); + +private: + /// Adds _value to _account's balance. + void addBalance(smt::Expression _account, smt::Expression _value); + + SolverInterface& m_solver; + + /// Symbolic `this` address. + std::unique_ptr m_thisAddress; + + /// Symbolic balances. + std::unique_ptr m_balances; +}; + +} +} +} diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 7cdaa49f3d6d..9baab9b5470c 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -17,8 +17,8 @@ #include +#include #include -#include #include #include @@ -26,6 +26,7 @@ #include #include #include +#include using namespace std; using namespace dev; @@ -35,7 +36,8 @@ using namespace dev::solidity; SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): m_interface(make_shared(_smtlib2Responses)), m_errorReporterReference(_errorReporter), - m_errorReporter(m_smtErrors) + m_errorReporter(m_smtErrors), + m_context(*m_interface) { #if defined (HAVE_Z3) || defined (HAVE_CVC4) if (!_smtlib2Responses.empty()) @@ -50,7 +52,6 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _ void SMTChecker::analyze(SourceUnit const& _source, shared_ptr const& _scanner) { - m_variableUsage = make_shared(_source); m_scanner = _scanner; if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) _source.accept(*this); @@ -111,6 +112,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) if (isRootFunction()) { m_interface->reset(); + m_context.reset(); m_pathConditions.clear(); m_callStack.clear(); m_expressions.clear(); @@ -121,6 +123,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) initializeLocalVariables(_function); m_loopExecutionHappened = false; m_arrayAssignmentHappened = false; + m_externalFunctionCallHappened = false; } _function.parameterList().accept(*this); if (_function.returnParameterList()) @@ -191,6 +194,15 @@ void SMTChecker::endVisit(FunctionDefinition const&) m_modifierDepthStack.pop_back(); } +bool SMTChecker::visit(InlineAssembly const& _inlineAsm) +{ + m_errorReporter.warning( + _inlineAsm.location(), + "Assertion checker does not support inline assembly." + ); + return false; +} + bool SMTChecker::visit(IfStatement const& _node) { _node.condition().accept(*this); @@ -200,18 +212,18 @@ bool SMTChecker::visit(IfStatement const& _node) if (isRootFunction()) checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE."); - auto indicesEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition())); - vector touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement()); + auto indicesEndTrue = visitBranch(&_node.trueStatement(), expr(_node.condition())); + auto touchedVars = touchedVariables(_node.trueStatement()); decltype(indicesEndTrue) indicesEndFalse; if (_node.falseStatement()) { - indicesEndFalse = visitBranch(*_node.falseStatement(), !expr(_node.condition())); - touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement()); + indicesEndFalse = visitBranch(_node.falseStatement(), !expr(_node.condition())); + touchedVars += touchedVariables(*_node.falseStatement()); } else indicesEndFalse = copyVariableIndices(); - mergeVariables(touchedVariables, expr(_node.condition()), indicesEndTrue, indicesEndFalse); + mergeVariables(touchedVars, expr(_node.condition()), indicesEndTrue, indicesEndFalse); return false; } @@ -228,12 +240,12 @@ bool SMTChecker::visit(IfStatement const& _node) bool SMTChecker::visit(WhileStatement const& _node) { auto indicesBeforeLoop = copyVariableIndices(); - auto touchedVariables = m_variableUsage->touchedVariables(_node); - resetVariables(touchedVariables); + auto touchedVars = touchedVariables(_node); + resetVariables(touchedVars); decltype(indicesBeforeLoop) indicesAfterLoop; if (_node.isDoWhile()) { - indicesAfterLoop = visitBranch(_node.body()); + indicesAfterLoop = visitBranch(&_node.body()); // TODO the assertions generated in the body should still be active in the condition _node.condition().accept(*this); if (isRootFunction()) @@ -245,7 +257,7 @@ bool SMTChecker::visit(WhileStatement const& _node) if (isRootFunction()) checkBooleanNotConstant(_node.condition(), "While loop condition is always $VALUE."); - indicesAfterLoop = visitBranch(_node.body(), expr(_node.condition())); + indicesAfterLoop = visitBranch(&_node.body(), expr(_node.condition())); } // We reset the execution to before the loop @@ -256,7 +268,7 @@ bool SMTChecker::visit(WhileStatement const& _node) if (!_node.isDoWhile()) _node.condition().accept(*this); - mergeVariables(touchedVariables, expr(_node.condition()), indicesAfterLoop, copyVariableIndices()); + mergeVariables(touchedVars, expr(_node.condition()), indicesAfterLoop, copyVariableIndices()); m_loopExecutionHappened = true; return false; @@ -271,17 +283,13 @@ bool SMTChecker::visit(ForStatement const& _node) auto indicesBeforeLoop = copyVariableIndices(); // Do not reset the init expression part. - auto touchedVariables = - m_variableUsage->touchedVariables(_node.body()); + auto touchedVars = touchedVariables(_node.body()); if (_node.condition()) - touchedVariables += m_variableUsage->touchedVariables(*_node.condition()); + touchedVars += touchedVariables(*_node.condition()); if (_node.loopExpression()) - touchedVariables += m_variableUsage->touchedVariables(*_node.loopExpression()); - // Remove duplicates - std::sort(touchedVariables.begin(), touchedVariables.end()); - touchedVariables.erase(std::unique(touchedVariables.begin(), touchedVariables.end()), touchedVariables.end()); + touchedVars += touchedVariables(*_node.loopExpression()); - resetVariables(touchedVariables); + resetVariables(touchedVars); if (_node.condition()) { @@ -306,7 +314,7 @@ bool SMTChecker::visit(ForStatement const& _node) _node.condition()->accept(*this); auto forCondition = _node.condition() ? expr(*_node.condition()) : smt::Expression(true); - mergeVariables(touchedVariables, forCondition, indicesAfterLoop, copyVariableIndices()); + mergeVariables(touchedVars, forCondition, indicesAfterLoop, copyVariableIndices()); m_loopExecutionHappened = true; return false; @@ -333,27 +341,60 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) void SMTChecker::endVisit(Assignment const& _assignment) { - if (_assignment.assignmentOperator() != Token::Assign) + static map const compoundToArithmetic{ + {Token::AssignAdd, Token::Add}, + {Token::AssignSub, Token::Sub}, + {Token::AssignMul, Token::Mul}, + {Token::AssignDiv, Token::Div}, + {Token::AssignMod, Token::Mod} + }; + Token op = _assignment.assignmentOperator(); + if (op != Token::Assign && !compoundToArithmetic.count(op)) m_errorReporter.warning( _assignment.location(), - "Assertion checker does not yet implement compound assignment." + "Assertion checker does not yet implement this assignment operator." ); else if (!isSupportedType(_assignment.annotation().type->category())) m_errorReporter.warning( _assignment.location(), "Assertion checker does not yet implement type " + _assignment.annotation().type->toString() ); - else if (Identifier const* identifier = dynamic_cast(&_assignment.leftHandSide())) - { - VariableDeclaration const& decl = dynamic_cast(*identifier->annotation().referencedDeclaration); - solAssert(knownVariable(decl), ""); - assignment(decl, _assignment.rightHandSide(), _assignment.location()); - defineExpr(_assignment, expr(_assignment.rightHandSide())); - } - else if (dynamic_cast(&_assignment.leftHandSide())) + else if ( + dynamic_cast(&_assignment.leftHandSide()) || + dynamic_cast(&_assignment.leftHandSide()) + ) { - arrayIndexAssignment(_assignment); - defineExpr(_assignment, expr(_assignment.rightHandSide())); + boost::optional leftHandSide; + VariableDeclaration const* decl = nullptr; + auto identifier = dynamic_cast(&_assignment.leftHandSide()); + if (identifier) + { + decl = dynamic_cast(identifier->annotation().referencedDeclaration); + solAssert(decl, ""); + solAssert(knownVariable(*decl), ""); + leftHandSide = currentValue(*decl); + } + else + leftHandSide = expr(_assignment.leftHandSide()); + + solAssert(leftHandSide, ""); + smt::Expression rightHandSide = + compoundToArithmetic.count(op) ? + arithmeticOperation( + compoundToArithmetic.at(op), + *leftHandSide, + expr(_assignment.rightHandSide()), + _assignment.annotation().type, + _assignment.location() + ) : + expr(_assignment.rightHandSide()) + ; + defineExpr(_assignment, rightHandSide); + + if (identifier) + assignment(*decl, _assignment, _assignment.location()); + else + arrayIndexAssignment(_assignment.leftHandSide(), expr(_assignment)); } else m_errorReporter.warning( @@ -410,7 +451,7 @@ void SMTChecker::checkUnderOverflow() void SMTChecker::checkUnderflow(OverflowTarget& _target) { solAssert(_target.type != OverflowTarget::Type::Overflow, ""); - auto intType = dynamic_cast(_target.intType.get()); + auto intType = dynamic_cast(_target.intType); checkCondition( _target.path && _target.value < minValue(*intType), _target.location, @@ -423,7 +464,7 @@ void SMTChecker::checkUnderflow(OverflowTarget& _target) void SMTChecker::checkOverflow(OverflowTarget& _target) { solAssert(_target.type != OverflowTarget::Type::Underflow, ""); - auto intType = dynamic_cast(_target.intType.get()); + auto intType = dynamic_cast(_target.intType); checkCondition( _target.path && _target.value > maxValue(*intType), _target.location, @@ -452,21 +493,22 @@ void SMTChecker::endVisit(UnaryOperation const& _op) solAssert(isInteger(_op.annotation().type->category()), ""); solAssert(_op.subExpression().annotation().lValueRequested, ""); - if (Identifier const* identifier = dynamic_cast(&_op.subExpression())) + if (auto identifier = dynamic_cast(&_op.subExpression())) { - VariableDeclaration const& decl = dynamic_cast(*identifier->annotation().referencedDeclaration); - if (knownVariable(decl)) - { - auto innerValue = currentValue(decl); - auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; - assignment(decl, newValue, _op.location()); - defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); - } - else - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement such assignments." - ); + auto decl = dynamic_cast(identifier->annotation().referencedDeclaration); + solAssert(decl, ""); + solAssert(knownVariable(*decl), ""); + auto innerValue = currentValue(*decl); + auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; + defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); + assignment(*decl, newValue, _op.location()); + } + else if (dynamic_cast(&_op.subExpression())) + { + auto innerValue = expr(_op.subExpression()); + auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; + defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); + arrayIndexAssignment(_op.subExpression(), newValue); } else m_errorReporter.warning( @@ -502,20 +544,27 @@ bool SMTChecker::visit(UnaryOperation const& _op) bool SMTChecker::visit(BinaryOperation const& _op) { - return !shortcutRationalNumber(_op); + if (shortcutRationalNumber(_op)) + return false; + if (TokenTraits::isBooleanOp(_op.getOperator())) + { + booleanOperation(_op); + return false; + } + return true; } void SMTChecker::endVisit(BinaryOperation const& _op) { if (_op.annotation().type->category() == Type::Category::RationalNumber) return; + if (TokenTraits::isBooleanOp(_op.getOperator())) + return; if (TokenTraits::isArithmeticOp(_op.getOperator())) arithmeticOperation(_op); else if (TokenTraits::isCompareOp(_op.getOperator())) compareOperation(_op); - else if (TokenTraits::isBooleanOp(_op.getOperator())) - booleanOperation(_op); else m_errorReporter.warning( _op.location(), @@ -561,6 +610,13 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) popCallStack(); break; case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::Creation: + m_externalFunctionCallHappened = true; resetStateVariables(); resetStorageReferences(); break; @@ -573,6 +629,22 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) case FunctionType::Kind::MulMod: abstractFunctionCall(_funCall); break; + case FunctionType::Kind::Send: + case FunctionType::Kind::Transfer: + { + auto const& memberAccess = dynamic_cast(_funCall.expression()); + auto const& address = memberAccess.expression(); + auto const& value = args.at(0); + solAssert(value, ""); + + smt::Expression thisBalance = m_context.balance(); + setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), *m_interface); + checkCondition(thisBalance < expr(*value), _funCall.location(), "Insufficient funds", "address(this).balance", &thisBalance); + + m_context.transfer(m_context.thisAddress(), expr(address), expr(*value)); + createExpr(_funCall); + break; + } default: m_errorReporter.warning( _funCall.location(), @@ -616,20 +688,8 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) { - FunctionDefinition const* _funDef = nullptr; - Expression const* _calledExpr = &_funCall.expression(); - - if (TupleExpression const* _fun = dynamic_cast(&_funCall.expression())) - { - solAssert(_fun->components().size() == 1, ""); - _calledExpr = _fun->components().at(0).get(); - } - - if (Identifier const* _fun = dynamic_cast(_calledExpr)) - _funDef = dynamic_cast(_fun->annotation().referencedDeclaration); - else if (MemberAccess const* _fun = dynamic_cast(_calledExpr)) - _funDef = dynamic_cast(_fun->annotation().referencedDeclaration); - else + FunctionDefinition const* _funDef = inlinedFunctionCallToDefinition(_funCall); + if (!_funDef) { m_errorReporter.warning( _funCall.location(), @@ -637,7 +697,6 @@ void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) ); return; } - solAssert(_funDef, ""); if (visitedFunction(_funDef)) m_errorReporter.warning( @@ -645,14 +704,15 @@ void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) "Assertion checker does not support recursive function calls.", SecondarySourceLocation().append("Starting from function:", _funDef->location()) ); - else if (_funDef && _funDef->isImplemented()) + else { vector funArgs; - auto const& funType = dynamic_cast(_calledExpr->annotation().type.get()); + Expression const* calledExpr = &_funCall.expression(); + auto const& funType = dynamic_cast(calledExpr->annotation().type); solAssert(funType, ""); if (funType->bound()) { - auto const& boundFunction = dynamic_cast(_calledExpr); + auto const& boundFunction = dynamic_cast(calledExpr); solAssert(boundFunction, ""); funArgs.push_back(expr(boundFunction->expression())); } @@ -672,13 +732,6 @@ void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) defineExpr(_funCall, currentValue(*returnParams[0])); } } - else - { - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not support calls to functions without implementation." - ); - } } void SMTChecker::abstractFunctionCall(FunctionCall const& _funCall) @@ -705,6 +758,11 @@ void SMTChecker::endVisit(Identifier const& _identifier) defineExpr(_identifier, currentValue(*decl)); else if (_identifier.name() == "now") defineGlobalVariable(_identifier.name(), _identifier); + else if (_identifier.name() == "this") + { + defineExpr(_identifier, m_context.thisAddress()); + m_uninterpretedTerms.insert(&_identifier); + } else // TODO: handle MagicVariableDeclaration here m_errorReporter.warning( @@ -777,8 +835,8 @@ void SMTChecker::endVisit(Literal const& _literal) { if (type.category() == Type::Category::StringLiteral) { - auto stringType = make_shared(DataLocation::Memory, true); - auto stringLit = dynamic_cast(_literal.annotation().type.get()); + auto stringType = TypeProvider::stringMemory(); + auto stringLit = dynamic_cast(_literal.annotation().type); solAssert(stringLit, ""); auto result = newSymbolicVariable(*stringType, stringLit->richIdentifier(), *m_interface); m_expressions.emplace(&_literal, result.second); @@ -833,12 +891,23 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) { if (identifier && dynamic_cast(identifier->annotation().referencedDeclaration)) { - auto enumType = dynamic_cast(accessType.get()); + auto enumType = dynamic_cast(accessType); solAssert(enumType, ""); defineExpr(_memberAccess, enumType->memberValue(_memberAccess.memberName())); } return false; } + else if (exprType->category() == Type::Category::Address) + { + _memberAccess.expression().accept(*this); + if (_memberAccess.memberName() == "balance") + { + defineExpr(_memberAccess, m_context.balance(expr(_memberAccess.expression()))); + setSymbolicUnknownValue(*m_expressions[&_memberAccess], *m_interface); + m_uninterpretedTerms.insert(&_memberAccess); + return false; + } + } else m_errorReporter.warning( _memberAccess.location(), @@ -890,9 +959,9 @@ void SMTChecker::arrayAssignment() m_arrayAssignmentHappened = true; } -void SMTChecker::arrayIndexAssignment(Assignment const& _assignment) +void SMTChecker::arrayIndexAssignment(Expression const& _expr, smt::Expression const& _rightHandSide) { - auto const& indexAccess = dynamic_cast(_assignment.leftHandSide()); + auto const& indexAccess = dynamic_cast(_expr); if (auto const& id = dynamic_cast(&indexAccess.baseExpression())) { auto const& varDecl = dynamic_cast(*id->annotation().referencedDeclaration); @@ -913,13 +982,13 @@ void SMTChecker::arrayIndexAssignment(Assignment const& _assignment) return true; if (prefix->category() == Type::Category::Mapping) { - auto mapPrefix = dynamic_cast(prefix.get()); + auto mapPrefix = dynamic_cast(prefix); solAssert(mapPrefix, ""); prefix = mapPrefix->valueType(); } else { - auto arrayPrefix = dynamic_cast(prefix.get()); + auto arrayPrefix = dynamic_cast(prefix); solAssert(arrayPrefix, ""); prefix = arrayPrefix->baseType(); } @@ -930,7 +999,7 @@ void SMTChecker::arrayIndexAssignment(Assignment const& _assignment) smt::Expression store = smt::Expression::store( m_variables[&varDecl]->currentValue(), expr(*indexAccess.indexExpression()), - expr(_assignment.rightHandSide()) + _rightHandSide ); m_interface->addAssertion(newValue(varDecl) == store); // Update the SMT select value after the assignment, @@ -947,7 +1016,7 @@ void SMTChecker::arrayIndexAssignment(Assignment const& _assignment) ); else m_errorReporter.warning( - _assignment.location(), + _expr.location(), "Assertion checker does not yet implement this expression." ); } @@ -991,7 +1060,7 @@ bool SMTChecker::shortcutRationalNumber(Expression const& _expr) { if (_expr.annotation().type->category() == Type::Category::RationalNumber) { - auto rationalType = dynamic_cast(_expr.annotation().type.get()); + auto rationalType = dynamic_cast(_expr.annotation().type); solAssert(rationalType, ""); if (rationalType->isNegative()) defineExpr(_expr, smt::Expression(u2s(rationalType->literalValue(nullptr)))); @@ -1010,55 +1079,15 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) case Token::Sub: case Token::Mul: case Token::Div: + case Token::Mod: { - solAssert(_op.annotation().commonType, ""); - if (_op.annotation().commonType->category() != Type::Category::Integer) - { - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement this operator on non-integer types." - ); - break; - } - auto const& intType = dynamic_cast(*_op.annotation().commonType); - smt::Expression left(expr(_op.leftExpression())); - smt::Expression right(expr(_op.rightExpression())); - Token op = _op.getOperator(); - smt::Expression value( - op == Token::Add ? left + right : - op == Token::Sub ? left - right : - op == Token::Div ? division(left, right, intType) : - /*op == Token::Mul*/ left * right - ); - - if (_op.getOperator() == Token::Div) - { - checkCondition(right == 0, _op.location(), "Division by zero", "", &right); - m_interface->addAssertion(right != 0); - } - - addOverflowTarget( - OverflowTarget::Type::All, + defineExpr(_op, arithmeticOperation( + _op.getOperator(), + expr(_op.leftExpression()), + expr(_op.rightExpression()), _op.annotation().commonType, - value, _op.location() - ); - - smt::Expression intValueRange = (0 - minValue(intType)) + maxValue(intType) + 1; - defineExpr(_op, smt::Expression::ite( - value > maxValue(intType) || value < minValue(intType), - value % intValueRange, - value )); - if (intType.isSigned()) - { - defineExpr(_op, smt::Expression::ite( - expr(_op) > maxValue(intType), - expr(_op) - intValueRange, - expr(_op) - )); - } - break; } default: @@ -1069,6 +1098,65 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) } } +smt::Expression SMTChecker::arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + langutil::SourceLocation const& _location +) +{ + static set validOperators{ + Token::Add, + Token::Sub, + Token::Mul, + Token::Div, + Token::Mod + }; + solAssert(validOperators.count(_op), ""); + solAssert(_commonType, ""); + solAssert(_commonType->category() == Type::Category::Integer, ""); + + auto const& intType = dynamic_cast(*_commonType); + smt::Expression value( + _op == Token::Add ? _left + _right : + _op == Token::Sub ? _left - _right : + _op == Token::Div ? division(_left, _right, intType) : + _op == Token::Mul ? _left * _right : + /*_op == Token::Mod*/ _left % _right + ); + + if (_op == Token::Div || _op == Token::Mod) + { + checkCondition(_right == 0, _location, "Division by zero", "", &_right); + m_interface->addAssertion(_right != 0); + } + + addOverflowTarget( + OverflowTarget::Type::All, + _commonType, + value, + _location + ); + + smt::Expression intValueRange = (0 - minValue(intType)) + maxValue(intType) + 1; + value = smt::Expression::ite( + value > maxValue(intType) || value < minValue(intType), + value % intValueRange, + value + ); + if (intType.isSigned()) + { + value = smt::Expression::ite( + value > maxValue(intType), + value - intValueRange, + value + ); + } + + return value; +} + void SMTChecker::compareOperation(BinaryOperation const& _op) { solAssert(_op.annotation().commonType, ""); @@ -1114,16 +1202,26 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) if (_op.annotation().commonType->category() == Type::Category::Bool) { // @TODO check that both of them are not constant + _op.leftExpression().accept(*this); + auto touchedVars = touchedVariables(_op.leftExpression()); if (_op.getOperator() == Token::And) + { + auto indicesAfterSecond = visitBranch(&_op.rightExpression(), expr(_op.leftExpression())); + mergeVariables(touchedVars, !expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond); defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression())); + } else + { + auto indicesAfterSecond = visitBranch(&_op.rightExpression(), !expr(_op.leftExpression())); + mergeVariables(touchedVars, expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond); defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression())); + } } else m_errorReporter.warning( _op.location(), "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations" - ); + ); } smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type) @@ -1150,23 +1248,23 @@ void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expressio if (type->category() == Type::Category::Integer) addOverflowTarget(OverflowTarget::Type::All, type, _value, _location); else if (type->category() == Type::Category::Address) - addOverflowTarget(OverflowTarget::Type::All, make_shared(160), _value, _location); + addOverflowTarget(OverflowTarget::Type::All, TypeProvider::uint(160), _value, _location); else if (type->category() == Type::Category::Mapping) arrayAssignment(); m_interface->addAssertion(newValue(_variable) == _value); } -SMTChecker::VariableIndices SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +SMTChecker::VariableIndices SMTChecker::visitBranch(ASTNode const* _statement, smt::Expression _condition) { return visitBranch(_statement, &_condition); } -SMTChecker::VariableIndices SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) +SMTChecker::VariableIndices SMTChecker::visitBranch(ASTNode const* _statement, smt::Expression const* _condition) { auto indicesBeforeBranch = copyVariableIndices(); if (_condition) pushPathCondition(*_condition); - _statement.accept(*this); + _statement->accept(*this); if (_condition) popPathCondition(); auto indicesAfterBranch = copyVariableIndices(); @@ -1179,7 +1277,7 @@ void SMTChecker::checkCondition( SourceLocation const& _location, string const& _description, string const& _additionalValueName, - smt::Expression* _additionalValue + smt::Expression const* _additionalValue ) { m_interface->push(); @@ -1239,6 +1337,12 @@ void SMTChecker::checkCondition( " therefore all mapping information is erased after" " a mapping local variable/parameter is assigned.\n" "You can re-introduce information using require()."; + if (m_externalFunctionCallHappened) + extraComment += + "\nNote that external function calls are not inlined," + " even if the source code of the function is available." + " This is due to the possibility that the actual called contract" + " has the same ABI but implements the function differently."; SecondarySourceLocation secondaryLocation{}; secondaryLocation.append(extraComment, SourceLocation{}); @@ -1450,7 +1554,7 @@ void SMTChecker::resetStorageReferences() resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); } -void SMTChecker::resetVariables(vector _variables) +void SMTChecker::resetVariables(set const& _variables) { for (auto const* decl: _variables) resetVariable(*decl); @@ -1467,15 +1571,14 @@ void SMTChecker::resetVariables(function const TypePointer SMTChecker::typeWithoutPointer(TypePointer const& _type) { - if (auto refType = dynamic_cast(_type.get())) - return ReferenceType::copyForLocationIfReference(refType->location(), _type); + if (auto refType = dynamic_cast(_type)) + return TypeProvider::withLocationIfReference(refType->location(), _type); return _type; } -void SMTChecker::mergeVariables(vector const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) +void SMTChecker::mergeVariables(set const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) { - set uniqueVars(_variables.begin(), _variables.end()); - for (auto const* decl: uniqueVars) + for (auto const* decl: _variables) { solAssert(_indicesEndTrue.count(decl) && _indicesEndFalse.count(decl), ""); int trueIndex = _indicesEndTrue.at(decl); @@ -1673,3 +1776,38 @@ void SMTChecker::resetVariableIndices(VariableIndices const& _indices) for (auto const& var: _indices) m_variables.at(var.first)->index() = var.second; } + +FunctionDefinition const* SMTChecker::inlinedFunctionCallToDefinition(FunctionCall const& _funCall) +{ + if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + return nullptr; + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + if (funType.kind() != FunctionType::Kind::Internal) + return nullptr; + + FunctionDefinition const* funDef = nullptr; + Expression const* calledExpr = &_funCall.expression(); + + if (TupleExpression const* fun = dynamic_cast(&_funCall.expression())) + { + solAssert(fun->components().size() == 1, ""); + calledExpr = fun->components().front().get(); + } + + if (Identifier const* fun = dynamic_cast(calledExpr)) + funDef = dynamic_cast(fun->annotation().referencedDeclaration); + else if (MemberAccess const* fun = dynamic_cast(calledExpr)) + funDef = dynamic_cast(fun->annotation().referencedDeclaration); + + if (funDef && funDef->isImplemented()) + return funDef; + + return nullptr; +} + +set SMTChecker::touchedVariables(ASTNode const& _node) +{ + solAssert(!m_functionPath.empty(), ""); + return m_variableUsage.touchedVariables(_node, m_functionPath); +} diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 66f4397e8ba6..2f24e78b31d3 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -18,8 +18,10 @@ #pragma once +#include #include #include +#include #include #include @@ -41,8 +43,6 @@ namespace dev namespace solidity { -class VariableUsage; - class SMTChecker: private ASTConstVisitor { public: @@ -55,6 +55,10 @@ class SMTChecker: private ASTConstVisitor /// the constructor. std::vector unhandledQueries() { return m_interface->unhandledQueries(); } + /// @return the FunctionDefinition of a called function if possible and should inline, + /// otherwise nullptr. + static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall); + private: // TODO: Check that we do not have concurrent reads and writes to a variable, // because the order of expression evaluation is undefined @@ -83,11 +87,22 @@ class SMTChecker: private ASTConstVisitor void endVisit(Return const& _node) override; bool visit(MemberAccess const& _node) override; void endVisit(IndexAccess const& _node) override; + bool visit(InlineAssembly const& _node) override; /// Do not visit subtree if node is a RationalNumber. /// Symbolic _expr is the rational literal. bool shortcutRationalNumber(Expression const& _expr); void arithmeticOperation(BinaryOperation const& _op); + /// @returns _op(_left, _right). + /// Used by the function above, compound assignments and + /// unary increment/decrement. + smt::Expression arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + langutil::SourceLocation const& _location + ); void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); @@ -113,7 +128,7 @@ class SMTChecker: private ASTConstVisitor /// while aliasing is not supported. void arrayAssignment(); /// Handles assignment to SMT array index. - void arrayIndexAssignment(Assignment const& _assignment); + void arrayIndexAssignment(Expression const& _expr, smt::Expression const& _rightHandSide); /// Division expression in the given type. Requires special treatment because /// of rounding for signed division. @@ -128,8 +143,8 @@ class SMTChecker: private ASTConstVisitor /// Visits the branch given by the statement, pushes and pops the current path conditions. /// @param _condition if present, asserts that this condition is true within the branch. /// @returns the variable indices after visiting the branch. - VariableIndices visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); - VariableIndices visitBranch(Statement const& _statement, smt::Expression _condition); + VariableIndices visitBranch(ASTNode const* _statement, smt::Expression const* _condition = nullptr); + VariableIndices visitBranch(ASTNode const* _statement, smt::Expression _condition); /// Check that a condition can be satisfied. void checkCondition( @@ -137,7 +152,7 @@ class SMTChecker: private ASTConstVisitor langutil::SourceLocation const& _location, std::string const& _description, std::string const& _additionalValueName = "", - smt::Expression* _additionalValue = nullptr + smt::Expression const* _additionalValue = nullptr ); /// Checks that a boolean condition is not constant. Do not warn if the expression /// is a literal constant. @@ -164,7 +179,7 @@ class SMTChecker: private ASTConstVisitor location(_location), callStack(move(_callStack)) { - solAssert(dynamic_cast(intType.get()), ""); + solAssert(dynamic_cast(intType), ""); } }; @@ -186,7 +201,7 @@ class SMTChecker: private ASTConstVisitor void resetVariable(VariableDeclaration const& _variable); void resetStateVariables(); void resetStorageReferences(); - void resetVariables(std::vector _variables); + void resetVariables(std::set const& _variables); void resetVariables(std::function const& _filter); /// @returns the type without storage pointer information if it has it. TypePointer typeWithoutPointer(TypePointer const& _type); @@ -194,7 +209,7 @@ class SMTChecker: private ASTConstVisitor /// Given two different branches and the touched variables, /// merge the touched variables into after-branch ite variables /// using the branch condition as guard. - void mergeVariables(std::vector const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse); + void mergeVariables(std::set const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse); /// Tries to create an uninitialized variable and returns true on success. /// This fails if the type is not supported. bool createVariable(VariableDeclaration const& _varDecl); @@ -256,10 +271,14 @@ class SMTChecker: private ASTConstVisitor /// Resets the variable indices. void resetVariableIndices(VariableIndices const& _indices); + /// @returns variables that are touched in _node's subtree. + std::set touchedVariables(ASTNode const& _node); + std::shared_ptr m_interface; - std::shared_ptr m_variableUsage; + VariableUsage m_variableUsage; bool m_loopExecutionHappened = false; bool m_arrayAssignmentHappened = false; + bool m_externalFunctionCallHappened = false; // True if the "No SMT solver available" warning was already created. bool m_noSolverWarning = false; /// An Expression may have multiple smt::Expression due to @@ -267,6 +286,7 @@ class SMTChecker: private ASTConstVisitor std::unordered_map> m_expressions; std::unordered_map> m_variables; std::unordered_map> m_globalContext; + /// Stores the instances of an Uninterpreted Function applied to arguments. /// These may be direct application of UFs or Array index access. /// Used to retrieve models. @@ -298,6 +318,9 @@ class SMTChecker: private ASTConstVisitor /// when placeholder is visited. /// Needs to be a stack because of function calls. std::vector m_modifierDepthStack; + + /// Stores the context of the encoding. + smt::EncodingContext m_context; }; } diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 042a16ab6a2a..0c7f1dbdf028 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -297,6 +297,7 @@ class SolverInterface Expression newVariable(std::string _name, SortPointer _sort) { // Subclasses should do something here + solAssert(_sort, ""); declareVariable(_name, *_sort); return Expression(std::move(_name), {}, std::move(_sort)); } diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index e72e22339c69..268d4aedc0d0 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -115,11 +116,11 @@ pair> dev::solidity::newSymbolicVariable( { bool abstract = false; shared_ptr var; - TypePointer type = _type.shared_from_this(); + TypePointer type = &_type; if (!isSupportedTypeDeclaration(_type)) { abstract = true; - var = make_shared(make_shared(256), _uniqueName, _solver); + var = make_shared(TypeProvider::uint256(), _uniqueName, _solver); } else if (isBool(_type.category())) var = make_shared(type, _uniqueName, _solver); @@ -129,11 +130,11 @@ pair> dev::solidity::newSymbolicVariable( var = make_shared(type, _uniqueName, _solver); else if (isFixedBytes(_type.category())) { - auto fixedBytesType = dynamic_cast(type.get()); + auto fixedBytesType = dynamic_cast(type); solAssert(fixedBytesType, ""); var = make_shared(fixedBytesType->numBytes(), _uniqueName, _solver); } - else if (isAddress(_type.category())) + else if (isAddress(_type.category()) || isContract(_type.category())) var = make_shared(_uniqueName, _solver); else if (isEnum(_type.category())) var = make_shared(type, _uniqueName, _solver); @@ -142,7 +143,7 @@ pair> dev::solidity::newSymbolicVariable( auto rational = dynamic_cast(&_type); solAssert(rational, ""); if (rational->isFractional()) - var = make_shared(make_shared(256), _uniqueName, _solver); + var = make_shared(TypeProvider::uint256(), _uniqueName, _solver); else var = make_shared(type, _uniqueName, _solver); } @@ -185,6 +186,11 @@ bool dev::solidity::isAddress(Type::Category _category) return _category == Type::Category::Address; } +bool dev::solidity::isContract(Type::Category _category) +{ + return _category == Type::Category::Contract; +} + bool dev::solidity::isEnum(Type::Category _category) { return _category == Type::Category::Enum; @@ -196,6 +202,7 @@ bool dev::solidity::isNumber(Type::Category _category) isRational(_category) || isFixedBytes(_category) || isAddress(_category) || + isContract(_category) || isEnum(_category); } @@ -236,6 +243,7 @@ void dev::solidity::smt::setSymbolicZeroValue(SymbolicVariable const& _variable, void dev::solidity::smt::setSymbolicZeroValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface) { + solAssert(_type, ""); if (isInteger(_type->category())) _interface.addAssertion(_expr == 0); else if (isBool(_type->category())) @@ -249,16 +257,17 @@ void dev::solidity::smt::setSymbolicUnknownValue(SymbolicVariable const& _variab void dev::solidity::smt::setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface) { + solAssert(_type, ""); if (isEnum(_type->category())) { - auto enumType = dynamic_cast(_type.get()); + auto enumType = dynamic_cast(_type); solAssert(enumType, ""); _interface.addAssertion(_expr >= 0); _interface.addAssertion(_expr < enumType->numberOfMembers()); } else if (isInteger(_type->category())) { - auto intType = dynamic_cast(_type.get()); + auto intType = dynamic_cast(_type); solAssert(intType, ""); _interface.addAssertion(_expr >= minValue(*intType)); _interface.addAssertion(_expr <= maxValue(*intType)); diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 23a0e93d69cd..f487ae430539 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -44,6 +44,7 @@ bool isInteger(Type::Category _category); bool isRational(Type::Category _category); bool isFixedBytes(Type::Category _category); bool isAddress(Type::Category _category); +bool isContract(Type::Category _category); bool isEnum(Type::Category _category); bool isNumber(Type::Category _category); bool isBool(Type::Category _category); diff --git a/libsolidity/formal/SymbolicVariables.cpp b/libsolidity/formal/SymbolicVariables.cpp index bdc341c43c1e..cccaa61a008c 100644 --- a/libsolidity/formal/SymbolicVariables.cpp +++ b/libsolidity/formal/SymbolicVariables.cpp @@ -19,6 +19,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -26,14 +27,30 @@ using namespace dev::solidity; SymbolicVariable::SymbolicVariable( TypePointer _type, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): m_type(move(_type)), - m_uniqueName(_uniqueName), + m_uniqueName(move(_uniqueName)), m_interface(_interface), m_ssa(make_shared()) { + solAssert(m_type, ""); + m_sort = smtSort(*m_type); + solAssert(m_sort, ""); +} + +SymbolicVariable::SymbolicVariable( + smt::SortPointer _sort, + string _uniqueName, + smt::SolverInterface& _interface +): + m_sort(move(_sort)), + m_uniqueName(move(_uniqueName)), + m_interface(_interface), + m_ssa(make_shared()) +{ + solAssert(m_sort, ""); } smt::Expression SymbolicVariable::currentValue() const @@ -48,7 +65,7 @@ string SymbolicVariable::currentName() const smt::Expression SymbolicVariable::valueAtIndex(int _index) const { - return m_interface.newVariable(uniqueSymbol(_index), smtSort(*m_type)); + return m_interface.newVariable(uniqueSymbol(_index), m_sort); } string SymbolicVariable::uniqueSymbol(unsigned _index) const @@ -64,55 +81,55 @@ smt::Expression SymbolicVariable::increaseIndex() SymbolicBoolVariable::SymbolicBoolVariable( TypePointer _type, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicVariable(move(_type), _uniqueName, _interface) + SymbolicVariable(move(_type), move(_uniqueName), _interface) { solAssert(m_type->category() == Type::Category::Bool, ""); } SymbolicIntVariable::SymbolicIntVariable( TypePointer _type, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicVariable(move(_type), _uniqueName, _interface) + SymbolicVariable(move(_type), move(_uniqueName), _interface) { solAssert(isNumber(m_type->category()), ""); } SymbolicAddressVariable::SymbolicAddressVariable( - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicIntVariable(make_shared(160), _uniqueName, _interface) + SymbolicIntVariable(TypeProvider::uint(160), move(_uniqueName), _interface) { } SymbolicFixedBytesVariable::SymbolicFixedBytesVariable( unsigned _numBytes, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicIntVariable(make_shared(_numBytes * 8), _uniqueName, _interface) + SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), move(_uniqueName), _interface) { } SymbolicFunctionVariable::SymbolicFunctionVariable( TypePointer _type, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicVariable(move(_type), _uniqueName, _interface), - m_declaration(m_interface.newVariable(currentName(), smtSort(*m_type))) + SymbolicVariable(move(_type), move(_uniqueName), _interface), + m_declaration(m_interface.newVariable(currentName(), m_sort)) { solAssert(m_type->category() == Type::Category::Function, ""); } void SymbolicFunctionVariable::resetDeclaration() { - m_declaration = m_interface.newVariable(currentName(), smtSort(*m_type)); + m_declaration = m_interface.newVariable(currentName(), m_sort); } smt::Expression SymbolicFunctionVariable::increaseIndex() @@ -129,30 +146,30 @@ smt::Expression SymbolicFunctionVariable::operator()(vector _ar SymbolicMappingVariable::SymbolicMappingVariable( TypePointer _type, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicVariable(move(_type), _uniqueName, _interface) + SymbolicVariable(move(_type), move(_uniqueName), _interface) { solAssert(isMapping(m_type->category()), ""); } SymbolicArrayVariable::SymbolicArrayVariable( TypePointer _type, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicVariable(move(_type), _uniqueName, _interface) + SymbolicVariable(move(_type), move(_uniqueName), _interface) { solAssert(isArray(m_type->category()), ""); } SymbolicEnumVariable::SymbolicEnumVariable( TypePointer _type, - string const& _uniqueName, + string _uniqueName, smt::SolverInterface& _interface ): - SymbolicVariable(move(_type), _uniqueName, _interface) + SymbolicVariable(move(_type), move(_uniqueName), _interface) { solAssert(isEnum(m_type->category()), ""); } diff --git a/libsolidity/formal/SymbolicVariables.h b/libsolidity/formal/SymbolicVariables.h index 89df13f35f94..0c65a34a6ccb 100644 --- a/libsolidity/formal/SymbolicVariables.h +++ b/libsolidity/formal/SymbolicVariables.h @@ -37,7 +37,12 @@ class SymbolicVariable public: SymbolicVariable( TypePointer _type, - std::string const& _uniqueName, + std::string _uniqueName, + smt::SolverInterface& _interface + ); + SymbolicVariable( + smt::SortPointer _sort, + std::string _uniqueName, smt::SolverInterface& _interface ); @@ -60,6 +65,9 @@ class SymbolicVariable protected: std::string uniqueSymbol(unsigned _index) const; + /// SMT sort. + smt::SortPointer m_sort; + /// Solidity type, used for size and range in number types. TypePointer m_type; std::string m_uniqueName; smt::SolverInterface& m_interface; @@ -74,7 +82,7 @@ class SymbolicBoolVariable: public SymbolicVariable public: SymbolicBoolVariable( TypePointer _type, - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); }; @@ -87,7 +95,7 @@ class SymbolicIntVariable: public SymbolicVariable public: SymbolicIntVariable( TypePointer _type, - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); }; @@ -99,7 +107,7 @@ class SymbolicAddressVariable: public SymbolicIntVariable { public: SymbolicAddressVariable( - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); }; @@ -112,7 +120,7 @@ class SymbolicFixedBytesVariable: public SymbolicIntVariable public: SymbolicFixedBytesVariable( unsigned _numBytes, - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); }; @@ -125,7 +133,7 @@ class SymbolicFunctionVariable: public SymbolicVariable public: SymbolicFunctionVariable( TypePointer _type, - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); @@ -148,7 +156,7 @@ class SymbolicMappingVariable: public SymbolicVariable public: SymbolicMappingVariable( TypePointer _type, - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); }; @@ -161,7 +169,7 @@ class SymbolicArrayVariable: public SymbolicVariable public: SymbolicArrayVariable( TypePointer _type, - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); }; @@ -174,7 +182,7 @@ class SymbolicEnumVariable: public SymbolicVariable public: SymbolicEnumVariable( TypePointer _type, - std::string const& _uniqueName, + std::string _uniqueName, smt::SolverInterface& _interface ); }; diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp index 9282a56064f2..9f0575072270 100644 --- a/libsolidity/formal/VariableUsage.cpp +++ b/libsolidity/formal/VariableUsage.cpp @@ -17,63 +17,63 @@ #include -#include +#include + +#include using namespace std; using namespace dev; using namespace dev::solidity; -VariableUsage::VariableUsage(ASTNode const& _node) +void VariableUsage::endVisit(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + solAssert(declaration, ""); + if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) + if (_identifier.annotation().lValueRequested) + m_touchedVariables.insert(varDecl); +} + +void VariableUsage::endVisit(FunctionCall const& _funCall) { - auto nodeFun = [&](ASTNode const& n) -> bool - { - if (Identifier const* identifier = dynamic_cast(&n)) - { - Declaration const* declaration = identifier->annotation().referencedDeclaration; - solAssert(declaration, ""); - if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) - if ( - identifier->annotation().lValueRequested && - varDecl->annotation().type->isValueType() - ) - m_touchedVariable[&n] = varDecl; - } - return true; - }; - auto edgeFun = [&](ASTNode const& _parent, ASTNode const& _child) - { - if (m_touchedVariable.count(&_child) || m_children.count(&_child)) - m_children[&_parent].push_back(&_child); - }; + if (auto const& funDef = SMTChecker::inlinedFunctionCallToDefinition(_funCall)) + if (find(m_functionPath.begin(), m_functionPath.end(), funDef) == m_functionPath.end()) + funDef->accept(*this); +} - ASTReduce reducer(nodeFun, edgeFun); - _node.accept(reducer); +bool VariableUsage::visit(FunctionDefinition const& _function) +{ + m_functionPath.push_back(&_function); + return true; } -vector VariableUsage::touchedVariables(ASTNode const& _node) const +void VariableUsage::endVisit(FunctionDefinition const&) { - if (!m_children.count(&_node) && !m_touchedVariable.count(&_node)) - return {}; + solAssert(!m_functionPath.empty(), ""); + m_functionPath.pop_back(); +} - set touched; - vector toVisit; - toVisit.push_back(&_node); +void VariableUsage::endVisit(ModifierInvocation const& _modifierInv) +{ + auto const& modifierDef = dynamic_cast(_modifierInv.name()->annotation().referencedDeclaration); + if (modifierDef) + modifierDef->accept(*this); +} - while (!toVisit.empty()) - { - ASTNode const* n = toVisit.back(); - toVisit.pop_back(); - if (m_children.count(n)) - { - solAssert(!m_touchedVariable.count(n), ""); - toVisit += m_children.at(n); - } - else - { - solAssert(m_touchedVariable.count(n), ""); - touched.insert(m_touchedVariable.at(n)); - } - } +void VariableUsage::endVisit(PlaceholderStatement const&) +{ + solAssert(!m_functionPath.empty(), ""); + FunctionDefinition const* function = m_functionPath.back(); + solAssert(function, ""); + if (function->isImplemented()) + function->body().accept(*this); +} - return {touched.begin(), touched.end()}; +set VariableUsage::touchedVariables(ASTNode const& _node, vector const& _outerCallstack) +{ + m_touchedVariables.clear(); + m_functionPath.clear(); + m_functionPath += _outerCallstack; + _node.accept(*this); + return m_touchedVariables; } diff --git a/libsolidity/formal/VariableUsage.h b/libsolidity/formal/VariableUsage.h index dda13de25b7a..c824eae627e7 100644 --- a/libsolidity/formal/VariableUsage.h +++ b/libsolidity/formal/VariableUsage.h @@ -17,33 +17,35 @@ #pragma once -#include -#include +#include + #include +#include namespace dev { namespace solidity { -class ASTNode; -class VariableDeclaration; - /** - * This class collects information about which local variables of value type - * are modified in which parts of the AST. + * This class computes information about which variables are modified in a certain subtree. */ -class VariableUsage +class VariableUsage: private ASTConstVisitor { public: - explicit VariableUsage(ASTNode const& _node); - - std::vector touchedVariables(ASTNode const& _node) const; + /// @param _outerCallstack the current callstack in the callers context. + std::set touchedVariables(ASTNode const& _node, std::vector const& _outerCallstack); private: - // Variable touched by a specific AST node. - std::map m_touchedVariable; - std::map> m_children; + void endVisit(Identifier const& _node) override; + void endVisit(FunctionCall const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + void endVisit(ModifierInvocation const& _node) override; + void endVisit(PlaceholderStatement const& _node) override; + + std::set m_touchedVariables; + std::vector m_functionPath; }; } diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h index 082f3900f242..6787ddfddd97 100644 --- a/libsolidity/interface/ABI.h +++ b/libsolidity/interface/ABI.h @@ -32,7 +32,7 @@ namespace solidity // Forward declarations class ContractDefinition; class Type; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; class ABI { diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 81619acb76e2..7489a13f83cc 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -31,13 +31,13 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include @@ -46,9 +46,12 @@ #include #include +#include + #include #include +#include #include @@ -64,6 +67,26 @@ using namespace dev; using namespace langutil; using namespace dev::solidity; +static int g_compilerStackCounts = 0; + +CompilerStack::CompilerStack(ReadCallback::Callback const& _readFile): + m_readFile{_readFile}, + m_generateIR{false}, + m_errorList{}, + m_errorReporter{m_errorList} +{ + // Because TypeProvider is currently a singleton API, we must ensure that + // no more than one entity is actually using it at a time. + solAssert(g_compilerStackCounts == 0, "You shall not have another CompilerStack aside me."); + ++g_compilerStackCounts; +} + +CompilerStack::~CompilerStack() +{ + --g_compilerStackCounts; + TypeProvider::reset(); +} + boost::optional CompilerStack::parseRemapping(string const& _remapping) { auto eq = find(_remapping.begin(), _remapping.end(), '='); @@ -135,38 +158,38 @@ void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _respons m_smtlib2Responses[_hash] = _response; } -void CompilerStack::reset(bool _keepSources) +void CompilerStack::reset(bool _keepSettings) { - if (_keepSources) - { - m_stackState = SourcesSet; - for (auto sourcePair: m_sources) - sourcePair.second.reset(); - } - else - { - m_stackState = Empty; - m_sources.clear(); - } + m_stackState = Empty; + m_sources.clear(); m_smtlib2Responses.clear(); m_unhandledSMTLib2Queries.clear(); - m_libraries.clear(); - m_evmVersion = langutil::EVMVersion(); - m_optimiserSettings = OptimiserSettings::minimal(); + if (!_keepSettings) + { + m_remappings.clear(); + m_libraries.clear(); + m_evmVersion = langutil::EVMVersion(); + m_generateIR = false; + m_optimiserSettings = OptimiserSettings::minimal(); + m_metadataLiteralSources = false; + } m_globalContext.reset(); m_scopes.clear(); m_sourceOrder.clear(); m_contracts.clear(); m_errorReporter.clear(); + TypeProvider::reset(); } -bool CompilerStack::addSource(string const& _name, string const& _content) +void CompilerStack::setSources(StringMap _sources) { - bool existed = m_sources.count(_name) != 0; - reset(true); - m_sources[_name].scanner = make_shared(CharStream(_content, _name)); + if (m_stackState == SourcesSet) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Cannot change sources once set.")); + if (m_stackState != Empty) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set sources before parsing.")); + for (auto source: _sources) + m_sources[source.first].scanner = make_shared(CharStream(/*content*/std::move(source.second), /*name*/source.first)); m_stackState = SourcesSet; - return existed; } bool CompilerStack::parse() @@ -389,7 +412,11 @@ bool CompilerStack::compile() for (ASTPointer const& node: source->ast->nodes()) if (auto contract = dynamic_cast(node.get())) if (isRequestedContract(*contract)) + { compileContract(*contract, otherCompilers); + if (m_generateIR) + generateIR(*contract); + } m_stackState = CompilationSuccessful; this->link(); return true; @@ -498,6 +525,22 @@ std::string const CompilerStack::filesystemFriendlyName(string const& _contractN return matchContract.contract->name(); } +string const& CompilerStack::yulIR(string const& _contractName) const +{ + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + return contract(_contractName).yulIR; +} + +string const& CompilerStack::yulIROptimized(string const& _contractName) const +{ + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + return contract(_contractName).yulIROptimized; +} + eth::LinkerObject const& CompilerStack::object(string const& _contractName) const { if (m_stackState != CompilationSuccessful) @@ -528,7 +571,7 @@ string CompilerStack::assemblyString(string const& _contractName, StringMap _sou } /// TODO: cache the JSON -Json::Value CompilerStack::assemblyJSON(string const& _contractName, StringMap _sourceCodes) const +Json::Value CompilerStack::assemblyJSON(string const& _contractName, StringMap const& _sourceCodes) const { if (m_stackState != CompilationSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); @@ -904,6 +947,24 @@ void CompilerStack::compileContract( _otherCompilers[compiledContract.contract] = compiler; } +void CompilerStack::generateIR(ContractDefinition const& _contract) +{ + solAssert(m_stackState >= AnalysisSuccessful, ""); + + if (!_contract.canBeDeployed()) + return; + + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); + if (!compiledContract.yulIR.empty()) + return; + + for (auto const* dependency: _contract.annotation().contractDependencies) + generateIR(*dependency); + + IRGenerator generator(m_evmVersion, m_optimiserSettings); + tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract); +} + CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const { solAssert(m_stackState >= AnalysisSuccessful, ""); @@ -1006,6 +1067,8 @@ string CompilerStack::createMetadata(Contract const& _contract) const meta["settings"]["optimizer"]["details"] = std::move(details); } + if (m_metadataLiteralSources) + meta["settings"]["metadata"]["useLiteralContent"] = true; meta["settings"]["evmVersion"] = m_evmVersion.name(); meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] = _contract.contract->annotation().canonicalName; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index bbd2401e9a3d..173209d6e661 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -98,10 +98,9 @@ class CompilerStack: boost::noncopyable /// Creates a new compiler stack. /// @param _readFile callback to used to read files for import statements. Must return /// and must not emit exceptions. - explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): - m_readFile(_readFile), - m_errorList(), - m_errorReporter(m_errorList) {} + explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()); + + ~CompilerStack(); /// @returns the list of errors that occurred during parsing and type checking. langutil::ErrorList const& errors() const { return m_errorReporter.errors(); } @@ -109,10 +108,9 @@ class CompilerStack: boost::noncopyable /// @returns the current state. State state() const { return m_stackState; } - /// Resets the compiler to a state where the sources are not parsed or even removed. - /// Sets the state to SourcesSet if @a _keepSources is true, otherwise to Empty. - /// All settings, with the exception of remappings, are reset. - void reset(bool _keepSources = false); + /// Resets the compiler to an empty state. Unless @a _keepSettings is set to true, + /// all settings are reset as well. + void reset(bool _keepSettings = false); // Parses a remapping of the format "context:prefix=target". static boost::optional parseRemapping(std::string const& _remapping); @@ -144,13 +142,15 @@ class CompilerStack: boost::noncopyable m_requestedContractNames = _contractNames; } + /// Enable experimental generation of Yul IR code. + void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; } + /// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata. /// Must be set before parsing. void useMetadataLiteralSources(bool _metadataLiteralSources); - /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. - /// @returns true if a source object by the name already existed and was replaced. - bool addSource(std::string const& _name, std::string const& _content); + /// Sets the sources. Must be set before parsing. + void setSources(StringMap _sources); /// Adds a response to an SMTLib2 query (identified by the hash of the query input). /// Must be set before parsing. @@ -204,6 +204,12 @@ class CompilerStack: boost::noncopyable /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use std::string const filesystemFriendlyName(std::string const& _contractName) const; + /// @returns the IR representation of a contract. + std::string const& yulIR(std::string const& _contractName) const; + + /// @returns the optimized IR representation of a contract. + std::string const& yulIROptimized(std::string const& _contractName) const; + /// @returns the assembled object for a contract. eth::LinkerObject const& object(std::string const& _contractName) const; @@ -232,7 +238,7 @@ class CompilerStack: boost::noncopyable /// @returns a JSON representation of the assembly. /// @arg _sourceCodes is the map of input files to source code strings /// Prerequisite: Successful compilation. - Json::Value assemblyJSON(std::string const& _contractName, StringMap _sourceCodes = StringMap()) const; + Json::Value assemblyJSON(std::string const& _contractName, StringMap const& _sourceCodes = StringMap()) const; /// @returns a JSON representing the contract ABI. /// Prerequisite: Successful call to parse or compile. @@ -275,6 +281,8 @@ class CompilerStack: boost::noncopyable std::shared_ptr compiler; eth::LinkerObject object; ///< Deployment object (includes the runtime sub-object). eth::LinkerObject runtimeObject; ///< Runtime object. + std::string yulIR; ///< Experimental Yul IR code. + std::string yulIROptimized; ///< Optimized experimental Yul IR code. mutable std::unique_ptr metadata; ///< The metadata json that will be hashed into the chain. mutable std::unique_ptr abi; mutable std::unique_ptr userDocumentation; @@ -301,6 +309,10 @@ class CompilerStack: boost::noncopyable std::map>& _otherCompilers ); + /// Generate Yul IR for a single contract. + /// The IR is stored but otherwise unused. + void generateIR(ContractDefinition const& _contract); + /// Links all the known library addresses in the available objects. Any unknown /// library will still be kept as an unlinked placeholder in the objects. void link(); @@ -353,6 +365,7 @@ class CompilerStack: boost::noncopyable OptimiserSettings m_optimiserSettings; langutil::EVMVersion m_evmVersion; std::set m_requestedContractNames; + bool m_generateIR; std::map m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// "context:prefix=target" diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index c1f7067901d0..5b033dfce982 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -129,12 +129,18 @@ bool hashMatchesContent(string const& _hash, string const& _content) } } -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesIR) { for (auto const& artifact: _outputSelection) /// @TODO support sub-matching, e.g "evm" matches "evm.assembly" - if (artifact == "*" || artifact == _artifact) + if (artifact == _artifact) return true; + else if (artifact == "*") + { + // "ir" and "irOptimized" can only be matched by "*" if activated. + if ((_artifact != "ir" && _artifact != "irOptimized") || _wildcardMatchesIR) + return true; + } return false; } @@ -151,7 +157,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _art /// /// @TODO optimise this. Perhaps flatten the structure upfront. /// -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact, bool _wildcardMatchesIR) { if (!_outputSelection.isObject()) return false; @@ -168,7 +174,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil if ( _outputSelection[file].isMember(contract) && _outputSelection[file][contract].isArray() && - isArtifactRequested(_outputSelection[file][contract], _artifact) + isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesIR) ) return true; } @@ -176,10 +182,10 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil return false; } -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector const& _artifacts) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector const& _artifacts, bool _wildcardMatchesIR) { for (auto const& artifact: _artifacts) - if (isArtifactRequested(_outputSelection, _file, _contract, artifact)) + if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesIR)) return true; return false; } @@ -193,6 +199,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection) // This does not inculde "evm.methodIdentifiers" on purpose! static vector const outputsThatRequireBinaries{ "*", + "ir", "irOptimized", "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", @@ -203,11 +210,28 @@ bool isBinaryRequested(Json::Value const& _outputSelection) for (auto const& fileRequests: _outputSelection) for (auto const& requests: fileRequests) for (auto const& output: outputsThatRequireBinaries) - if (isArtifactRequested(requests, output)) + if (isArtifactRequested(requests, output, false)) + return true; + return false; +} + +/// @returns true if any Yul IR was requested. Note that as an exception, '*' does not +/// yet match "ir" or "irOptimized" +bool isIRRequested(Json::Value const& _outputSelection) +{ + if (!_outputSelection.isObject()) + return false; + + for (auto const& fileRequests: _outputSelection) + for (auto const& requests: fileRequests) + for (auto const& request: requests) + if (request == "ir" || request == "irOptimized") return true; + return false; } + Json::Value formatLinkReferences(std::map const& linkReferences) { Json::Value ret(Json::objectValue); @@ -239,7 +263,7 @@ Json::Value collectEVMObject(eth::LinkerObject const& _object, string const* _so { Json::Value output = Json::objectValue; output["object"] = _object.toHex(); - output["opcodes"] = solidity::disassemble(_object.bytecode); + output["opcodes"] = dev::eth::disassemble(_object.bytecode); output["sourceMap"] = _sourceMap ? *_sourceMap : ""; output["linkReferences"] = formatLinkReferences(_object.linkReferences); return output; @@ -647,8 +671,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting CompilerStack compilerStack(m_readFile); StringMap sourceList = std::move(_inputsAndSettings.sources); - for (auto const& source: sourceList) - compilerStack.addSource(source.first, source.second); + compilerStack.setSources(sourceList); for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses) compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); @@ -658,6 +681,10 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection)); + bool const irRequested = isIRRequested(_inputsAndSettings.outputSelection); + + compilerStack.enableIRGeneration(irRequested); + Json::Value errors = std::move(_inputsAndSettings.errors); bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection); @@ -768,15 +795,17 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting for (string const& query: compilerStack.unhandledSMTLib2Queries()) output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query; + bool const wildcardMatchesIR = false; + output["sources"] = Json::objectValue; unsigned sourceIndex = 0; for (string const& sourceName: analysisSuccess ? compilerStack.sourceNames() : vector()) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesIR)) sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST", wildcardMatchesIR)) sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); output["sources"][sourceName] = sourceResult; } @@ -791,32 +820,38 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesIR)) contractData["abi"] = compilerStack.contractABI(contractName); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesIR)) contractData["metadata"] = compilerStack.metadata(contractName); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesIR)) contractData["userdoc"] = compilerStack.natspecUser(contractName); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesIR)) contractData["devdoc"] = compilerStack.natspecDev(contractName); + // IR + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesIR)) + contractData["ir"] = compilerStack.yulIR(contractName); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesIR)) + contractData["irOptimized"] = compilerStack.yulIROptimized(contractName); + // EVM Json::Value evmData(Json::objectValue); - // @TODO: add ir - if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly")) + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesIR)) evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList); - if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly")) + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesIR)) evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesIR)) evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName); - if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates")) + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesIR)) evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); if (compilationSuccess && isArtifactRequested( _inputsAndSettings.outputSelection, file, name, - { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }, + wildcardMatchesIR )) evmData["bytecode"] = collectEVMObject( compilerStack.object(contractName), @@ -827,7 +862,8 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting _inputsAndSettings.outputSelection, file, name, - { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" } + { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }, + wildcardMatchesIR )) evmData["deployedBytecode"] = collectEVMObject( compilerStack.runtimeObject(contractName), @@ -864,7 +900,11 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) Json::Value output = Json::objectValue; - AssemblyStack stack(_inputsAndSettings.evmVersion, AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + _inputsAndSettings.evmVersion, + AssemblyStack::Language::StrictAssembly, + _inputsAndSettings.optimiserSettings + ); string const& sourceName = _inputsAndSettings.sources.begin()->first; string const& sourceContents = _inputsAndSettings.sources.begin()->second; @@ -897,28 +937,26 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) string contractName = stack.parserResult()->name.str(); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir")) + bool const wildcardMatchesIR = true; + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesIR)) output["contracts"][sourceName][contractName]["ir"] = stack.print(); - if (_inputsAndSettings.optimiserSettings.runYulOptimiser) - stack.optimize(); + stack.optimize(); - MachineAssemblyObject object = stack.assemble( - AssemblyStack::Machine::EVM, - _inputsAndSettings.optimiserSettings.optimizeStackAllocation - ); + MachineAssemblyObject object = stack.assemble(AssemblyStack::Machine::EVM); if (isArtifactRequested( _inputsAndSettings.outputSelection, sourceName, contractName, - { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }, + wildcardMatchesIR )) output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesIR)) output["contracts"][sourceName][contractName]["irOptimized"] = stack.print(); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesIR)) output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly; return output; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index ba02c048ccbc..cb4525b9298b 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -22,12 +22,12 @@ #include -#include #include #include #include #include #include +#include #include #include #include @@ -1036,7 +1036,8 @@ ASTPointer Parser::parseStatement() ASTPointer Parser::parseInlineAssembly(ASTPointer const& _docString) { RecursionGuard recursionGuard(*this); - ASTNodeFactory nodeFactory(*this); + SourceLocation location{position(), -1, source()}; + expectToken(Token::Assembly); if (m_scanner->currentToken() == Token::StringLiteral) { @@ -1050,8 +1051,9 @@ ASTPointer Parser::parseInlineAssembly(ASTPointer con shared_ptr block = asmParser.parse(m_scanner, true); if (block == nullptr) BOOST_THROW_EXCEPTION(FatalError()); - nodeFactory.markEndPosition(); - return nodeFactory.createNode(_docString, block); + + location.end = block->location.end; + return make_shared(location, _docString, block); } ASTPointer Parser::parseIfStatement(ASTPointer const& _docString) diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 33132756f5c8..f080234400a4 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -41,9 +41,9 @@ using namespace dev; using namespace langutil; using namespace yul; using namespace dev; -using namespace dev::solidity; -namespace { +namespace +{ set const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", "u128", "s128", "u256", "s256"}; @@ -51,12 +51,21 @@ set const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", " bool AsmAnalyzer::analyze(Block const& _block) { - if (!(ScopeFiller(m_info, m_errorReporter))(_block)) - return false; + bool success = false; + try + { + if (!(ScopeFiller(m_info, m_errorReporter))(_block)) + return false; - bool success = (*this)(_block); - if (!success) - solAssert(m_errorReporter.hasErrors(), "No success but no error."); + success = (*this)(_block); + if (!success) + solAssert(m_errorReporter.hasErrors(), "No success but no error."); + } + catch (FatalError const&) + { + // This FatalError con occur if the errorReporter has too many errors. + solAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); + } return success && !m_errorReporter.hasErrors(); } @@ -86,7 +95,7 @@ bool AsmAnalyzer::operator()(Label const& _label) "The use of labels is disallowed. Please use \"if\", \"switch\", \"for\" or function calls instead." ); m_info.stackHeightInfo[&_label] = m_stackHeight; - warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); + warnOnInstructions(dev::eth::Instruction::JUMPDEST, _label.location); return true; } @@ -442,15 +451,21 @@ bool AsmAnalyzer::operator()(Switch const& _switch) if (_case.value) { int const initialStackHeight = m_stackHeight; + bool isCaseValueValid = true; // We cannot use "expectExpression" here because *_case.value is not a // Statement and would be converted to a Statement otherwise. if (!(*this)(*_case.value)) + { + isCaseValueValid = false; success = false; + } expectDeposit(1, initialStackHeight, _case.value->location); m_stackHeight--; + // If the case value is not valid, we should not insert it into cases. + yulAssert(isCaseValueValid || m_errorReporter.hasErrors(), "Invalid case value."); /// Note: the parser ensures there is only one default case - if (!cases.insert(valueOfLiteral(*_case.value)).second) + if (isCaseValueValid && !cases.insert(valueOfLiteral(*_case.value)).second) { m_errorReporter.declarationError( _case.location, @@ -653,7 +668,7 @@ void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _loc ); } -void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location) +void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocation const& _location) { // We assume that returndatacopy, returndatasize and staticcall are either all available // or all not available. @@ -677,33 +692,37 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio }; if (( - _instr == solidity::Instruction::RETURNDATACOPY || - _instr == solidity::Instruction::RETURNDATASIZE + _instr == dev::eth::Instruction::RETURNDATACOPY || + _instr == dev::eth::Instruction::RETURNDATASIZE ) && !m_evmVersion.supportsReturndata()) { errorForVM("only available for Byzantium-compatible"); } - else if (_instr == solidity::Instruction::STATICCALL && !m_evmVersion.hasStaticCall()) + else if (_instr == dev::eth::Instruction::STATICCALL && !m_evmVersion.hasStaticCall()) { errorForVM("only available for Byzantium-compatible"); } else if (( - _instr == solidity::Instruction::SHL || - _instr == solidity::Instruction::SHR || - _instr == solidity::Instruction::SAR + _instr == dev::eth::Instruction::SHL || + _instr == dev::eth::Instruction::SHR || + _instr == dev::eth::Instruction::SAR ) && !m_evmVersion.hasBitwiseShifting()) { errorForVM("only available for Constantinople-compatible"); } - else if (_instr == solidity::Instruction::CREATE2 && !m_evmVersion.hasCreate2()) + else if (_instr == dev::eth::Instruction::CREATE2 && !m_evmVersion.hasCreate2()) { errorForVM("only available for Constantinople-compatible"); } - else if (_instr == solidity::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) + else if (_instr == dev::eth::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) { errorForVM("only available for Constantinople-compatible"); } - else if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) + else if ( + _instr == dev::eth::Instruction::JUMP || + _instr == dev::eth::Instruction::JUMPI || + _instr == dev::eth::Instruction::JUMPDEST + ) { if (m_dialect->flavour == AsmFlavour::Loose) m_errorReporter.error( diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index d25b3340a44e..ab0c718f579a 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -109,7 +109,7 @@ class AsmAnalyzer: public boost::static_visitor Scope& scope(Block const* _block); void expectValidType(std::string const& type, langutil::SourceLocation const& _location); - void warnOnInstructions(dev::solidity::Instruction _instr, langutil::SourceLocation const& _location); + void warnOnInstructions(dev::eth::Instruction _instr, langutil::SourceLocation const& _location); /// Depending on @a m_flavour and @a m_errorTypeForLoose, throws an internal compiler /// exception (if the flavour is not Loose), reports an error/warning diff --git a/libyul/AsmData.h b/libyul/AsmData.h index 3b1eaaf8bf21..cedff110689b 100644 --- a/libyul/AsmData.h +++ b/libyul/AsmData.h @@ -43,7 +43,7 @@ struct TypedName { langutil::SourceLocation location; YulString name; Type type; using TypedNameList = std::vector; /// Direct EVM instruction (except PUSHi and JUMPDEST) -struct Instruction { langutil::SourceLocation location; dev::solidity::Instruction instruction; }; +struct Instruction { langutil::SourceLocation location; dev::eth::Instruction instruction; }; /// Literal number or string (up to 32 bytes) enum class LiteralKind { Number, Boolean, String }; struct Literal { langutil::SourceLocation location; LiteralKind kind; YulString value; Type type; }; @@ -61,7 +61,7 @@ struct StackAssignment { langutil::SourceLocation location; Identifier variableN /// the same amount of items as the number of variables. struct Assignment { langutil::SourceLocation location; std::vector variableNames; std::unique_ptr value; }; /// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" -struct FunctionalInstruction { langutil::SourceLocation location; dev::solidity::Instruction instruction; std::vector arguments; }; +struct FunctionalInstruction { langutil::SourceLocation location; dev::eth::Instruction instruction; std::vector arguments; }; struct FunctionCall { langutil::SourceLocation location; Identifier functionName; std::vector arguments; }; /// Statement that contains only a single expression struct ExpressionStatement { langutil::SourceLocation location; Expression expression; }; diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 320da666bec3..13ff81c4fbd9 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -34,11 +34,14 @@ using namespace std; using namespace dev; using namespace langutil; using namespace yul; -using namespace dev::solidity; shared_ptr Parser::parse(std::shared_ptr const& _scanner, bool _reuseScanner) { m_recursionDepth = 0; + + _scanner->supportPeriodInIdentifier(true); + ScopeGuard resetScanner([&]{ _scanner->supportPeriodInIdentifier(false); }); + try { m_scanner = _scanner; @@ -51,9 +54,31 @@ shared_ptr Parser::parse(std::shared_ptr const& _scanner, bool _ { solAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); } + return nullptr; } +std::map const& Parser::instructions() +{ + // Allowed instructions, lowercase names. + static map s_instructions; + if (s_instructions.empty()) + { + for (auto const& instruction: dev::eth::c_instructions) + { + if ( + instruction.second == dev::eth::Instruction::JUMPDEST || + dev::eth::isPushInstruction(instruction.second) + ) + continue; + string name = instruction.first; + transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); + s_instructions[name] = instruction.second; + } + } + return s_instructions; +} + Block Parser::parseBlock() { RecursionGuard recursionGuard(*this); @@ -106,31 +131,19 @@ Statement Parser::parseStatement() case Token::For: return parseForLoop(); case Token::Break: - if (m_insideForLoopBody) - { - auto stmt = Statement{ createWithLocation() }; - m_scanner->next(); - return stmt; - } - else - { - m_errorReporter.syntaxError(location(), "Keyword break outside for-loop body is not allowed."); - m_scanner->next(); - return {}; - } + { + Statement stmt{createWithLocation()}; + checkBreakContinuePosition("break"); + m_scanner->next(); + return stmt; + } case Token::Continue: - if (m_insideForLoopBody) - { - auto stmt = Statement{ createWithLocation() }; - m_scanner->next(); - return stmt; - } - else - { - m_errorReporter.syntaxError(location(), "Keyword continue outside for-loop body is not allowed."); - m_scanner->next(); - return {}; - } + { + Statement stmt{createWithLocation()}; + checkBreakContinuePosition("continue"); + m_scanner->next(); + return stmt; + } case Token::Assign: { if (m_dialect->flavour != AsmFlavour::Loose) @@ -270,20 +283,24 @@ Case Parser::parseCase() ForLoop Parser::parseForLoop() { - bool outerForLoopBody = m_insideForLoopBody; - m_insideForLoopBody = false; - RecursionGuard recursionGuard(*this); + + ForLoopComponent outerForLoopComponent = m_currentForLoopComponent; + ForLoop forLoop = createWithLocation(); expectToken(Token::For); + m_currentForLoopComponent = ForLoopComponent::ForLoopPre; forLoop.pre = parseBlock(); + m_currentForLoopComponent = ForLoopComponent::None; forLoop.condition = make_unique(parseExpression()); + m_currentForLoopComponent = ForLoopComponent::ForLoopPost; forLoop.post = parseBlock(); - - m_insideForLoopBody = true; + m_currentForLoopComponent = ForLoopComponent::ForLoopBody; forLoop.body = parseBlock(); - m_insideForLoopBody = outerForLoopBody; forLoop.location.end = forLoop.body.location.end; + + m_currentForLoopComponent = outerForLoopComponent; + return forLoop; } @@ -341,37 +358,16 @@ Expression Parser::parseExpression() } } -std::map const& Parser::instructions() +std::map const& Parser::instructionNames() { - // Allowed instructions, lowercase names. - static map s_instructions; - if (s_instructions.empty()) - { - for (auto const& instruction: solidity::c_instructions) - { - if ( - instruction.second == solidity::Instruction::JUMPDEST || - solidity::isPushInstruction(instruction.second) - ) - continue; - string name = instruction.first; - transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); - s_instructions[name] = instruction.second; - } - } - return s_instructions; -} - -std::map const& Parser::instructionNames() -{ - static map s_instructionNames; + static map s_instructionNames; if (s_instructionNames.empty()) { for (auto const& instr: instructions()) s_instructionNames[instr.second] = instr.first; // set the ambiguous instructions to a clear default - s_instructionNames[solidity::Instruction::SELFDESTRUCT] = "selfdestruct"; - s_instructionNames[solidity::Instruction::KECCAK256] = "keccak256"; + s_instructionNames[dev::eth::Instruction::SELFDESTRUCT] = "selfdestruct"; + s_instructionNames[dev::eth::Instruction::KECCAK256] = "keccak256"; } return s_instructionNames; } @@ -401,7 +397,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() ret = Identifier{location(), literal}; else if (m_dialect->flavour != AsmFlavour::Yul && instructions().count(literal.str())) { - dev::solidity::Instruction const& instr = instructions().at(literal.str()); + dev::eth::Instruction const& instr = instructions().at(literal.str()); ret = Instruction{location(), instr}; } else @@ -488,9 +484,16 @@ VariableDeclaration Parser::parseVariableDeclaration() FunctionDefinition Parser::parseFunctionDefinition() { RecursionGuard recursionGuard(*this); - auto outerForLoopBody = m_insideForLoopBody; - m_insideForLoopBody = false; - ScopeGuard restoreInsideForLoopBody{[&]() { m_insideForLoopBody = outerForLoopBody; }}; + + if (m_currentForLoopComponent == ForLoopComponent::ForLoopPre) + m_errorReporter.syntaxError( + location(), + "Functions cannot be defined inside a for-loop init block." + ); + + ForLoopComponent outerForLoopComponent = m_currentForLoopComponent; + m_currentForLoopComponent = ForLoopComponent::None; + FunctionDefinition funDef = createWithLocation(); expectToken(Token::Function); funDef.name = expectAsmIdentifier(); @@ -517,6 +520,8 @@ FunctionDefinition Parser::parseFunctionDefinition() } funDef.body = parseBlock(); funDef.location.end = funDef.body.location.end; + + m_currentForLoopComponent = outerForLoopComponent; return funDef; } @@ -530,11 +535,11 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) FunctionalInstruction ret; ret.instruction = instruction.instruction; ret.location = std::move(instruction.location); - solidity::Instruction instr = ret.instruction; - InstructionInfo instrInfo = instructionInfo(instr); - if (solidity::isDupInstruction(instr)) + dev::eth::Instruction instr = ret.instruction; + dev::eth::InstructionInfo instrInfo = instructionInfo(instr); + if (dev::eth::isDupInstruction(instr)) fatalParserError("DUPi instructions not allowed for functional notation"); - if (solidity::isSwapInstruction(instr)) + if (dev::eth::isSwapInstruction(instr)) fatalParserError("SWAPi instructions not allowed for functional notation"); expectToken(Token::LParen); unsigned args = unsigned(instrInfo.args); @@ -643,6 +648,24 @@ YulString Parser::expectAsmIdentifier() return name; } +void Parser::checkBreakContinuePosition(string const& _which) +{ + switch (m_currentForLoopComponent) + { + case ForLoopComponent::None: + m_errorReporter.syntaxError(location(), "Keyword \"" + _which + "\" needs to be inside a for-loop body."); + break; + case ForLoopComponent::ForLoopPre: + m_errorReporter.syntaxError(location(), "Keyword \"" + _which + "\" in for-loop init block is not allowed."); + break; + case ForLoopComponent::ForLoopPost: + m_errorReporter.syntaxError(location(), "Keyword \"" + _which + "\" in for-loop post block is not allowed."); + break; + case ForLoopComponent::ForLoopBody: + break; + } +} + bool Parser::isValidNumberLiteral(string const& _literal) { try diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h index 098e237b91e5..8561648ff180 100644 --- a/libyul/AsmParser.h +++ b/libyul/AsmParser.h @@ -32,20 +32,29 @@ #include #include + namespace yul { class Parser: public langutil::ParserBase { public: + enum class ForLoopComponent + { + None, ForLoopPre, ForLoopPost, ForLoopBody + }; + explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr _dialect): - ParserBase(_errorReporter), m_dialect(std::move(_dialect)), m_insideForLoopBody{false} {} + ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @param _reuseScanner if true, do check for end of input after the `}`. /// @returns an empty shared pointer on error. std::shared_ptr parse(std::shared_ptr const& _scanner, bool _reuseScanner); + /// @returns a map of all EVM instructions available to assembly. + static std::map const& instructions(); + protected: using ElementaryOperation = boost::variant; @@ -71,8 +80,7 @@ class Parser: public langutil::ParserBase ForLoop parseForLoop(); /// Parses a functional expression that has to push exactly one stack element Expression parseExpression(); - static std::map const& instructions(); - static std::map const& instructionNames(); + static std::map const& instructionNames(); /// Parses an elementary operation, i.e. a literal, identifier or instruction. /// This will parse instructions even in strict mode as part of the full parser /// for FunctionalInstruction. @@ -83,11 +91,14 @@ class Parser: public langutil::ParserBase TypedName parseTypedName(); YulString expectAsmIdentifier(); + /// Reports an error if we are currently not inside the body part of a for loop. + void checkBreakContinuePosition(std::string const& _which); + static bool isValidNumberLiteral(std::string const& _literal); private: std::shared_ptr m_dialect; - bool m_insideForLoopBody; + ForLoopComponent m_currentForLoopComponent = ForLoopComponent::None; }; } diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index fd360bc32ecf..a7a819440462 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -36,7 +36,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; //@TODO source locations diff --git a/libyul/AsmScope.cpp b/libyul/AsmScope.cpp index b71f2367eb65..252361d27b11 100644 --- a/libyul/AsmScope.cpp +++ b/libyul/AsmScope.cpp @@ -42,11 +42,11 @@ bool Scope::registerVariable(YulString _name, YulType const& _type) return true; } -bool Scope::registerFunction(YulString _name, std::vector const& _arguments, std::vector const& _returns) +bool Scope::registerFunction(YulString _name, std::vector _arguments, std::vector _returns) { if (exists(_name)) return false; - identifiers[_name] = Function{_arguments, _returns}; + identifiers[_name] = Function{std::move(_arguments), std::move(_returns)}; return true; } diff --git a/libyul/AsmScope.h b/libyul/AsmScope.h index 2a8ef49e8c27..f6c7fe59ac41 100644 --- a/libyul/AsmScope.h +++ b/libyul/AsmScope.h @@ -56,8 +56,8 @@ struct Scope bool registerLabel(YulString _name); bool registerFunction( YulString _name, - std::vector const& _arguments, - std::vector const& _returns + std::vector _arguments, + std::vector _returns ); /// Looks up the identifier in this or super scopes and returns a valid pointer if found diff --git a/libyul/AsmScopeFiller.cpp b/libyul/AsmScopeFiller.cpp index e9461473b160..1706b147d960 100644 --- a/libyul/AsmScopeFiller.cpp +++ b/libyul/AsmScopeFiller.cpp @@ -38,7 +38,6 @@ using namespace std; using namespace dev; using namespace langutil; using namespace yul; -using namespace dev::solidity; ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter): m_info(_info), m_errorReporter(_errorReporter) @@ -75,28 +74,13 @@ bool ScopeFiller::operator()(VariableDeclaration const& _varDecl) bool ScopeFiller::operator()(FunctionDefinition const& _funDef) { - bool success = true; - vector arguments; - for (auto const& _argument: _funDef.parameters) - arguments.emplace_back(_argument.type.str()); - vector returns; - for (auto const& _return: _funDef.returnVariables) - returns.emplace_back(_return.type.str()); - if (!m_currentScope->registerFunction(_funDef.name, arguments, returns)) - { - //@TODO secondary location - m_errorReporter.declarationError( - _funDef.location, - "Function name " + _funDef.name.str() + " already taken in this scope." - ); - success = false; - } - auto virtualBlock = m_info.virtualBlocks[&_funDef] = make_shared(); Scope& varScope = scope(virtualBlock.get()); varScope.superScope = m_currentScope; m_currentScope = &varScope; varScope.functionScope = true; + + bool success = true; for (auto const& var: _funDef.parameters + _funDef.returnVariables) if (!registerVariable(var, _funDef.location, varScope)) success = false; @@ -154,12 +138,11 @@ bool ScopeFiller::operator()(Block const& _block) // an entry in the scope according to their visibility. for (auto const& s: _block.statements) if (s.type() == typeid(FunctionDefinition)) - if (!boost::apply_visitor(*this, s)) + if (!registerFunction(boost::get(s))) success = false; for (auto const& s: _block.statements) - if (s.type() != typeid(FunctionDefinition)) - if (!boost::apply_visitor(*this, s)) - success = false; + if (!boost::apply_visitor(*this, s)) + success = false; m_currentScope = m_currentScope->superScope; return success; @@ -179,6 +162,26 @@ bool ScopeFiller::registerVariable(TypedName const& _name, SourceLocation const& return true; } +bool ScopeFiller::registerFunction(FunctionDefinition const& _funDef) +{ + vector arguments; + for (auto const& _argument: _funDef.parameters) + arguments.emplace_back(_argument.type.str()); + vector returns; + for (auto const& _return: _funDef.returnVariables) + returns.emplace_back(_return.type.str()); + if (!m_currentScope->registerFunction(_funDef.name, std::move(arguments), std::move(returns))) + { + //@TODO secondary location + m_errorReporter.declarationError( + _funDef.location, + "Function name " + _funDef.name.str() + " already taken in this scope." + ); + return false; + } + return true; +} + Scope& ScopeFiller::scope(Block const* _block) { auto& scope = m_info.scopes[_block]; diff --git a/libyul/AsmScopeFiller.h b/libyul/AsmScopeFiller.h index 7c8834637bfa..220538bb203a 100644 --- a/libyul/AsmScopeFiller.h +++ b/libyul/AsmScopeFiller.h @@ -73,6 +73,7 @@ class ScopeFiller: public boost::static_visitor langutil::SourceLocation const& _location, Scope& _scope ); + bool registerFunction(FunctionDefinition const& _funDef); Scope& scope(Block const* _block); diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index a9d19927940b..db669d148675 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -34,6 +34,8 @@ #include #include +#include + #include #include @@ -83,9 +85,13 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string void AssemblyStack::optimize() { + if (!m_optimiserSettings.runYulOptimiser) + return; + if (m_language != Language::StrictAssembly) solUnimplemented("Optimizer for both loose assembly and Yul is not yet implemented"); solAssert(m_analysisSuccessful, "Analysis was not successful."); + m_analysisSuccessful = false; solAssert(m_parserResult, ""); optimize(*m_parserResult); @@ -135,13 +141,15 @@ void AssemblyStack::optimize(Object& _object) for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast(subNode.get())) optimize(*subObject); - // TODO: Store this as setting - it should be the same as the flag passed to - // ::assemble(...) - bool optimizeStackAllocation = false; - OptimiserSuite::run(languageToDialect(m_language, m_evmVersion), *_object.code, *_object.analysisInfo, optimizeStackAllocation); + OptimiserSuite::run( + languageToDialect(m_language, m_evmVersion), + *_object.code, + *_object.analysisInfo, + m_optimiserSettings.optimizeStackAllocation + ); } -MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) const +MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { solAssert(m_analysisSuccessful, ""); solAssert(m_parserResult, ""); @@ -155,7 +163,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) MachineAssemblyObject object; dev::eth::Assembly assembly; EthAssemblyAdapter adapter(assembly); - compileEVM(adapter, false, _optimize); + compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation); object.bytecode = make_shared(assembly.assemble()); object.assembly = assembly.assemblyString(); return object; @@ -164,7 +172,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) { MachineAssemblyObject object; EVMAssembly assembly(true); - compileEVM(assembly, true, _optimize); + compileEVM(assembly, true, m_optimiserSettings.optimizeStackAllocation); object.bytecode = make_shared(assembly.finalize()); /// TODO: fill out text representation return object; diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index 8a30ebfcbeb5..fb6686c39aec 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -27,6 +27,8 @@ #include #include +#include + #include #include @@ -58,8 +60,14 @@ class AssemblyStack enum class Language { Yul, Assembly, StrictAssembly }; enum class Machine { EVM, EVM15, eWasm }; - explicit AssemblyStack(langutil::EVMVersion _evmVersion = langutil::EVMVersion(), Language _language = Language::Assembly): - m_language(_language), m_evmVersion(_evmVersion), m_errorReporter(m_errors) + AssemblyStack(): + AssemblyStack(langutil::EVMVersion{}, Language::Assembly, dev::solidity::OptimiserSettings::none()) + {} + AssemblyStack(langutil::EVMVersion _evmVersion, Language _language, dev::solidity::OptimiserSettings _optimiserSettings): + m_language(_language), + m_evmVersion(_evmVersion), + m_optimiserSettings(std::move(_optimiserSettings)), + m_errorReporter(m_errors) {} /// @returns the scanner used during parsing @@ -70,11 +78,11 @@ class AssemblyStack bool parseAndAnalyze(std::string const& _sourceName, std::string const& _source); /// Run the optimizer suite. Can only be used with Yul or strict assembly. + /// If the settings (see constructor) disabled the optimizer, nothing is done here. void optimize(); /// Run the assembly step (should only be called after parseAndAnalyze). - /// @param _optimize does not run the optimizer but performs optimized code generation. - MachineAssemblyObject assemble(Machine _machine, bool _optimize) const; + MachineAssemblyObject assemble(Machine _machine) const; /// @returns the errors generated during parsing, analysis (and potentially assembly). langutil::ErrorList const& errors() const { return m_errors; } @@ -95,6 +103,7 @@ class AssemblyStack Language m_language = Language::Assembly; langutil::EVMVersion m_evmVersion; + dev::solidity::OptimiserSettings m_optimiserSettings; std::shared_ptr m_scanner; diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 1f669769ecd5..b70d813b4179 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -38,6 +38,8 @@ add_library(yul backends/evm/EVMObjectCompiler.h backends/evm/NoOutputAssembly.h backends/evm/NoOutputAssembly.cpp + backends/wasm/WasmDialect.cpp + backends/wasm/WasmDialect.h optimiser/ASTCopier.cpp optimiser/ASTCopier.h optimiser/ASTWalker.cpp @@ -48,6 +50,8 @@ add_library(yul optimiser/CommonSubexpressionEliminator.h optimiser/DataFlowAnalyzer.cpp optimiser/DataFlowAnalyzer.h + optimiser/DeadCodeEliminator.cpp + optimiser/DeadCodeEliminator.h optimiser/Disambiguator.cpp optimiser/Disambiguator.h optimiser/EquivalentFunctionDetector.cpp diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 3b00fc6a2cac..11fd608e5525 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -31,7 +31,6 @@ using namespace std; using namespace yul; using namespace dev; -using namespace dev::solidity; map CompilabilityChecker::run( shared_ptr _dialect, diff --git a/libyul/Dialect.h b/libyul/Dialect.h index e06a68263c5f..16f820460489 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -49,7 +49,7 @@ struct BuiltinFunction /// This means the function cannot depend on storage or memory, cannot have any side-effects, /// but it can depend on state that is constant across an EVM-call. bool movable = false; - /// If true, can only accept literals as arguments and they cannot be moved to voriables. + /// If true, can only accept literals as arguments and they cannot be moved to variables. bool literalArguments = false; }; diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 0cc41056ffc3..a531c0624f46 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -35,7 +35,7 @@ struct SourceLocation; namespace dev { -namespace solidity +namespace eth { enum class Instruction: uint8_t; } @@ -63,7 +63,7 @@ class AbstractAssembly /// at the beginning. virtual int stackHeight() const = 0; /// Append an EVM instruction. - virtual void appendInstruction(dev::solidity::Instruction _instruction) = 0; + virtual void appendInstruction(dev::eth::Instruction _instruction) = 0; /// Append a constant. virtual void appendConstant(dev::u256 const& _constant) = 0; /// Append a label. diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index 489bfdc19cf3..4e9776e17e83 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -57,7 +57,7 @@ int EthAssemblyAdapter::stackHeight() const return m_assembly.deposit(); } -void EthAssemblyAdapter::appendInstruction(solidity::Instruction _instruction) +void EthAssemblyAdapter::appendInstruction(dev::eth::Instruction _instruction) { m_assembly.append(_instruction); } @@ -94,7 +94,7 @@ void EthAssemblyAdapter::appendLinkerSymbol(std::string const& _linkerSymbol) void EthAssemblyAdapter::appendJump(int _stackDiffAfter) { - appendInstruction(solidity::Instruction::JUMP); + appendInstruction(dev::eth::Instruction::JUMP); m_assembly.adjustDeposit(_stackDiffAfter); } @@ -107,7 +107,7 @@ void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter) void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId) { appendLabelReference(_labelId); - appendInstruction(solidity::Instruction::JUMPI); + appendInstruction(dev::eth::Instruction::JUMPI); } void EthAssemblyAdapter::appendBeginsub(LabelID, int) diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index 9647014ce302..3d2e9e191363 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -44,7 +44,7 @@ class EthAssemblyAdapter: public AbstractAssembly explicit EthAssemblyAdapter(dev::eth::Assembly& _assembly); void setSourceLocation(langutil::SourceLocation const& _location) override; int stackHeight() const override; - void appendInstruction(dev::solidity::Instruction _instruction) override; + void appendInstruction(dev::eth::Instruction _instruction) override; void appendConstant(dev::u256 const& _constant) override; void appendLabel(LabelID _labelId) override; void appendLabelReference(LabelID _labelId) override; diff --git a/libyul/backends/evm/EVMAssembly.cpp b/libyul/backends/evm/EVMAssembly.cpp index 2cf9f0011800..65d22cbd8b35 100644 --- a/libyul/backends/evm/EVMAssembly.cpp +++ b/libyul/backends/evm/EVMAssembly.cpp @@ -26,6 +26,7 @@ using namespace std; using namespace dev; +using namespace dev::eth; using namespace langutil; using namespace yul; @@ -43,23 +44,23 @@ void EVMAssembly::setSourceLocation(SourceLocation const&) // Ignored for now; } -void EVMAssembly::appendInstruction(solidity::Instruction _instr) +void EVMAssembly::appendInstruction(dev::eth::Instruction _instr) { m_bytecode.push_back(uint8_t(_instr)); - m_stackHeight += solidity::instructionInfo(_instr).ret - solidity::instructionInfo(_instr).args; + m_stackHeight += instructionInfo(_instr).ret - instructionInfo(_instr).args; } void EVMAssembly::appendConstant(u256 const& _constant) { bytes data = toCompactBigEndian(_constant, 1); - appendInstruction(solidity::pushInstruction(data.size())); + appendInstruction(pushInstruction(data.size())); m_bytecode += data; } void EVMAssembly::appendLabel(LabelID _labelId) { setLabelToCurrentPosition(_labelId); - appendInstruction(solidity::Instruction::JUMPDEST); + appendInstruction(dev::eth::Instruction::JUMPDEST); } void EVMAssembly::appendLabelReference(LabelID _labelId) @@ -67,7 +68,7 @@ void EVMAssembly::appendLabelReference(LabelID _labelId) solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode."); // @TODO we now always use labelReferenceSize for all labels, it could be shortened // for some of them. - appendInstruction(solidity::pushInstruction(labelReferenceSize)); + appendInstruction(dev::eth::pushInstruction(labelReferenceSize)); m_labelReferences[m_bytecode.size()] = _labelId; m_bytecode += bytes(labelReferenceSize); } @@ -94,7 +95,7 @@ void EVMAssembly::appendLinkerSymbol(string const&) void EVMAssembly::appendJump(int _stackDiffAfter) { solAssert(!m_evm15, "Plain JUMP used for EVM 1.5"); - appendInstruction(solidity::Instruction::JUMP); + appendInstruction(dev::eth::Instruction::JUMP); m_stackHeight += _stackDiffAfter; } @@ -102,7 +103,7 @@ void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter) { if (m_evm15) { - m_bytecode.push_back(uint8_t(solidity::Instruction::JUMPTO)); + m_bytecode.push_back(uint8_t(dev::eth::Instruction::JUMPTO)); appendLabelReferenceInternal(_labelId); m_stackHeight += _stackDiffAfter; } @@ -117,14 +118,14 @@ void EVMAssembly::appendJumpToIf(LabelID _labelId) { if (m_evm15) { - m_bytecode.push_back(uint8_t(solidity::Instruction::JUMPIF)); + m_bytecode.push_back(uint8_t(dev::eth::Instruction::JUMPIF)); appendLabelReferenceInternal(_labelId); m_stackHeight--; } else { appendLabelReference(_labelId); - appendInstruction(solidity::Instruction::JUMPI); + appendInstruction(dev::eth::Instruction::JUMPI); } } @@ -133,7 +134,7 @@ void EVMAssembly::appendBeginsub(LabelID _labelId, int _arguments) solAssert(m_evm15, "BEGINSUB used for EVM 1.0"); solAssert(_arguments >= 0, ""); setLabelToCurrentPosition(_labelId); - m_bytecode.push_back(uint8_t(solidity::Instruction::BEGINSUB)); + m_bytecode.push_back(uint8_t(dev::eth::Instruction::BEGINSUB)); m_stackHeight += _arguments; } @@ -141,7 +142,7 @@ void EVMAssembly::appendJumpsub(LabelID _labelId, int _arguments, int _returns) { solAssert(m_evm15, "JUMPSUB used for EVM 1.0"); solAssert(_arguments >= 0 && _returns >= 0, ""); - m_bytecode.push_back(uint8_t(solidity::Instruction::JUMPSUB)); + m_bytecode.push_back(uint8_t(dev::eth::Instruction::JUMPSUB)); appendLabelReferenceInternal(_labelId); m_stackHeight += _returns - _arguments; } @@ -150,7 +151,7 @@ void EVMAssembly::appendReturnsub(int _returns, int _stackDiffAfter) { solAssert(m_evm15, "RETURNSUB used for EVM 1.0"); solAssert(_returns >= 0, ""); - m_bytecode.push_back(uint8_t(solidity::Instruction::RETURNSUB)); + m_bytecode.push_back(uint8_t(dev::eth::Instruction::RETURNSUB)); m_stackHeight += _stackDiffAfter - _returns; } @@ -189,7 +190,7 @@ void EVMAssembly::appendLabelReferenceInternal(LabelID _labelId) void EVMAssembly::appendAssemblySize() { - appendInstruction(solidity::pushInstruction(assemblySizeReferenceSize)); + appendInstruction(dev::eth::pushInstruction(assemblySizeReferenceSize)); m_assemblySizePositions.push_back(m_bytecode.size()); m_bytecode += bytes(assemblySizeReferenceSize); } diff --git a/libyul/backends/evm/EVMAssembly.h b/libyul/backends/evm/EVMAssembly.h index e62bc87e44c2..a2318aea7ddc 100644 --- a/libyul/backends/evm/EVMAssembly.h +++ b/libyul/backends/evm/EVMAssembly.h @@ -46,7 +46,7 @@ class EVMAssembly: public AbstractAssembly /// at the beginning. int stackHeight() const override { return m_stackHeight; } /// Append an EVM instruction. - void appendInstruction(dev::solidity::Instruction _instruction) override; + void appendInstruction(dev::eth::Instruction _instruction) override; /// Append a constant. void appendConstant(dev::u256 const& _constant) override; /// Append a label. diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 78c5e7d6314f..68797f11635d 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -32,7 +32,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void VariableReferenceCounter::operator()(Identifier const& _identifier) { @@ -155,7 +154,7 @@ void CodeTransform::freeUnusedVariables() while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1)) { solAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), ""); - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::Instruction::POP); --m_stackAdjustment; } } @@ -203,7 +202,7 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) { m_context->variableStackHeights.erase(&var); m_assembly.setSourceLocation(_varDecl.location); - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::Instruction::POP); --m_stackAdjustment; } else @@ -218,8 +217,8 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) m_context->variableStackHeights[&var] = slot; m_assembly.setSourceLocation(_varDecl.location); if (int heightDiff = variableHeightDiff(var, varName, true)) - m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1)); - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::swapInstruction(heightDiff - 1)); + m_assembly.appendInstruction(dev::eth::Instruction::POP); --m_stackAdjustment; } } @@ -228,10 +227,10 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) void CodeTransform::stackError(StackTooDeepError _error, int _targetStackHeight) { - m_assembly.appendInstruction(solidity::Instruction::INVALID); + m_assembly.appendInstruction(dev::eth::Instruction::INVALID); // Correct the stack. while (m_assembly.stackHeight() > _targetStackHeight) - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::Instruction::POP); while (m_assembly.stackHeight() < _targetStackHeight) m_assembly.appendConstant(u256(0)); // Store error. @@ -324,11 +323,11 @@ void CodeTransform::operator()(FunctionCall const& _call) void CodeTransform::operator()(FunctionalInstruction const& _instruction) { if (m_evm15 && ( - _instruction.instruction == solidity::Instruction::JUMP || - _instruction.instruction == solidity::Instruction::JUMPI + _instruction.instruction == dev::eth::Instruction::JUMP || + _instruction.instruction == dev::eth::Instruction::JUMPI )) { - bool const isJumpI = _instruction.instruction == solidity::Instruction::JUMPI; + bool const isJumpI = _instruction.instruction == dev::eth::Instruction::JUMPI; if (isJumpI) { solAssert(_instruction.arguments.size() == 2, ""); @@ -366,7 +365,7 @@ void CodeTransform::operator()(Identifier const& _identifier) // TODO: opportunity for optimization: Do not DUP if this is the last reference // to the top most element of the stack if (int heightDiff = variableHeightDiff(_var, _identifier.name, false)) - m_assembly.appendInstruction(solidity::dupInstruction(heightDiff)); + m_assembly.appendInstruction(dev::eth::dupInstruction(heightDiff)); else // Store something to balance the stack m_assembly.appendConstant(u256(0)); @@ -403,8 +402,8 @@ void CodeTransform::operator()(Literal const& _literal) void CodeTransform::operator()(yul::Instruction const& _instruction) { solAssert(!m_allowStackOpt, ""); - solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5"); - solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5"); + solAssert(!m_evm15 || _instruction.instruction != dev::eth::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5"); + solAssert(!m_evm15 || _instruction.instruction != dev::eth::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5"); m_assembly.setSourceLocation(_instruction.location); m_assembly.appendInstruction(_instruction.instruction); checkStackHeight(&_instruction); @@ -414,7 +413,7 @@ void CodeTransform::operator()(If const& _if) { visitExpression(*_if.condition); m_assembly.setSourceLocation(_if.location); - m_assembly.appendInstruction(solidity::Instruction::ISZERO); + m_assembly.appendInstruction(dev::eth::Instruction::ISZERO); AbstractAssembly::LabelID end = m_assembly.newLabelId(); m_assembly.appendJumpToIf(end); (*this)(_if.body); @@ -440,8 +439,8 @@ void CodeTransform::operator()(Switch const& _switch) AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId(); caseBodies[&c] = bodyLabel; solAssert(m_assembly.stackHeight() == expressionHeight + 1, ""); - m_assembly.appendInstruction(solidity::dupInstruction(2)); - m_assembly.appendInstruction(solidity::Instruction::EQ); + m_assembly.appendInstruction(dev::eth::dupInstruction(2)); + m_assembly.appendInstruction(dev::eth::Instruction::EQ); m_assembly.appendJumpToIf(bodyLabel); } else @@ -467,7 +466,7 @@ void CodeTransform::operator()(Switch const& _switch) m_assembly.setSourceLocation(_switch.location); m_assembly.appendLabel(end); - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::Instruction::POP); checkStackHeight(&_switch); } @@ -536,7 +535,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) StackTooDeepError error(_error); if (error.functionName.empty()) error.functionName = _function.name; - stackError(error, height); + stackError(std::move(error), height); } { @@ -566,19 +565,19 @@ void CodeTransform::operator()(FunctionDefinition const& _function) to_string(stackLayout.size() - 17) + " parameters or return variables too many to fit the stack size." ); - stackError(error, m_assembly.stackHeight() - _function.parameters.size()); + stackError(std::move(error), m_assembly.stackHeight() - _function.parameters.size()); } else { while (!stackLayout.empty() && stackLayout.back() != int(stackLayout.size() - 1)) if (stackLayout.back() < 0) { - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::Instruction::POP); stackLayout.pop_back(); } else { - m_assembly.appendInstruction(swapInstruction(stackLayout.size() - stackLayout.back() - 1)); + m_assembly.appendInstruction(dev::eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1)); swap(stackLayout[stackLayout.back()], stackLayout.back()); } for (int i = 0; size_t(i) < stackLayout.size(); ++i) @@ -612,7 +611,7 @@ void CodeTransform::operator()(ForLoop const& _forLoop) visitExpression(*_forLoop.condition); m_assembly.setSourceLocation(_forLoop.location); - m_assembly.appendInstruction(solidity::Instruction::ISZERO); + m_assembly.appendInstruction(dev::eth::Instruction::ISZERO); m_assembly.appendJumpToIf(loopEnd); int const stackHeightBody = m_assembly.stackHeight(); @@ -634,14 +633,34 @@ void CodeTransform::operator()(ForLoop const& _forLoop) checkStackHeight(&_forLoop); } -void CodeTransform::operator()(Break const&) +int CodeTransform::appendPopUntil(int _targetDepth) { - yulAssert(false, "Code generation for break statement in Yul is not implemented yet."); + int const stackDiffAfter = m_assembly.stackHeight() - _targetDepth; + for (int i = 0; i < stackDiffAfter; ++i) + m_assembly.appendInstruction(dev::eth::Instruction::POP); + return stackDiffAfter; } -void CodeTransform::operator()(Continue const&) +void CodeTransform::operator()(Break const& _break) { - yulAssert(false, "Code generation for continue statement in Yul is not implemented yet."); + yulAssert(!m_context->forLoopStack.empty(), "Invalid break-statement. Requires surrounding for-loop in code generation."); + m_assembly.setSourceLocation(_break.location); + + Context::JumpInfo const& jump = m_context->forLoopStack.top().done; + m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight)); + + checkStackHeight(&_break); +} + +void CodeTransform::operator()(Continue const& _continue) +{ + yulAssert(!m_context->forLoopStack.empty(), "Invalid continue-statement. Requires surrounding for-loop in code generation."); + m_assembly.setSourceLocation(_continue.location); + + Context::JumpInfo const& jump = m_context->forLoopStack.top().post; + m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight)); + + checkStackHeight(&_continue); } void CodeTransform::operator()(Block const& _block) @@ -732,7 +751,7 @@ void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight m_stackAdjustment++; } else - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::Instruction::POP); } int deposit = m_assembly.stackHeight() - blockStartStackHeight; @@ -754,8 +773,8 @@ void CodeTransform::generateAssignment(Identifier const& _variableName) { Scope::Variable const& _var = boost::get(*var); if (int heightDiff = variableHeightDiff(_var, _variableName.name, true)) - m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1)); - m_assembly.appendInstruction(solidity::Instruction::POP); + m_assembly.appendInstruction(dev::eth::swapInstruction(heightDiff - 1)); + m_assembly.appendInstruction(dev::eth::Instruction::POP); decreaseReference(_variableName.name, _var); } else diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index 141dd91effa7..001616c345d0 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -217,6 +217,10 @@ class CodeTransform: public boost::static_visitor<> /// and corrects the stack height to the target stack height. void stackError(StackTooDeepError _error, int _targetStackSize); + /// Ensures stack height is down to @p _targetDepth by appending POP instructions to the output assembly. + /// Returns the number of POP statements that have been appended. + int appendPopUntil(int _targetDepth); + AbstractAssembly& m_assembly; AsmAnalysisInfo& m_info; Scope* m_scope = nullptr; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 6337bf9e356c..69ae4034ca4f 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -34,8 +34,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; - EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVersion _evmVersion): Dialect{_flavour}, m_objectAccess(_objectAccess), m_evmVersion(_evmVersion) @@ -84,7 +82,7 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVer std::function _visitArguments ) { _visitArguments(); - _assembly.appendInstruction(solidity::Instruction::CODECOPY); + _assembly.appendInstruction(dev::eth::Instruction::CODECOPY); }); } diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 9d40a71c1996..887bf2f63ea8 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -30,25 +30,25 @@ using namespace langutil; using namespace yul; -void NoOutputAssembly::appendInstruction(solidity::Instruction _instr) +void NoOutputAssembly::appendInstruction(dev::eth::Instruction _instr) { - m_stackHeight += solidity::instructionInfo(_instr).ret - solidity::instructionInfo(_instr).args; + m_stackHeight += instructionInfo(_instr).ret - instructionInfo(_instr).args; } void NoOutputAssembly::appendConstant(u256 const&) { - appendInstruction(solidity::pushInstruction(1)); + appendInstruction(dev::eth::pushInstruction(1)); } void NoOutputAssembly::appendLabel(LabelID) { - appendInstruction(solidity::Instruction::JUMPDEST); + appendInstruction(dev::eth::Instruction::JUMPDEST); } void NoOutputAssembly::appendLabelReference(LabelID) { solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode."); - appendInstruction(solidity::pushInstruction(1)); + appendInstruction(dev::eth::pushInstruction(1)); } NoOutputAssembly::LabelID NoOutputAssembly::newLabelId() @@ -69,7 +69,7 @@ void NoOutputAssembly::appendLinkerSymbol(string const&) void NoOutputAssembly::appendJump(int _stackDiffAfter) { solAssert(!m_evm15, "Plain JUMP used for EVM 1.5"); - appendInstruction(solidity::Instruction::JUMP); + appendInstruction(dev::eth::Instruction::JUMP); m_stackHeight += _stackDiffAfter; } @@ -91,7 +91,7 @@ void NoOutputAssembly::appendJumpToIf(LabelID _labelId) else { appendLabelReference(_labelId); - appendInstruction(solidity::Instruction::JUMPI); + appendInstruction(dev::eth::Instruction::JUMPI); } } @@ -118,7 +118,7 @@ void NoOutputAssembly::appendReturnsub(int _returns, int _stackDiffAfter) void NoOutputAssembly::appendAssemblySize() { - appendInstruction(solidity::Instruction::PUSH1); + appendInstruction(dev::eth::Instruction::PUSH1); } pair, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly() @@ -129,12 +129,12 @@ pair, AbstractAssembly::SubID> NoOutputAssembly::cr void NoOutputAssembly::appendDataOffset(AbstractAssembly::SubID) { - appendInstruction(solidity::Instruction::PUSH1); + appendInstruction(dev::eth::Instruction::PUSH1); } void NoOutputAssembly::appendDataSize(AbstractAssembly::SubID) { - appendInstruction(solidity::Instruction::PUSH1); + appendInstruction(dev::eth::Instruction::PUSH1); } AbstractAssembly::SubID NoOutputAssembly::appendData(bytes const&) @@ -153,7 +153,7 @@ NoOutputEVMDialect::NoOutputEVMDialect(shared_ptr const& _copyFrom): { _visitArguments(); for (size_t i = 0; i < parameters; i++) - _assembly.appendInstruction(dev::solidity::Instruction::POP); + _assembly.appendInstruction(dev::eth::Instruction::POP); for (size_t i = 0; i < returns; i++) _assembly.appendConstant(u256(0)); diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 78d26b5e30c9..ee01726a79c6 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -49,7 +49,7 @@ class NoOutputAssembly: public AbstractAssembly void setSourceLocation(langutil::SourceLocation const&) override {} int stackHeight() const override { return m_stackHeight; } - void appendInstruction(dev::solidity::Instruction _instruction) override; + void appendInstruction(dev::eth::Instruction _instruction) override; void appendConstant(dev::u256 const& _constant) override; void appendLabel(LabelID _labelId) override; void appendLabelReference(LabelID _labelId) override; diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp new file mode 100644 index 000000000000..5e03b037f72a --- /dev/null +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -0,0 +1,75 @@ +/* + 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 . +*/ +/** + * Dialects for Wasm. + */ + +#include + +using namespace std; +using namespace yul; + +WasmDialect::WasmDialect(AsmFlavour _flavour): + Dialect{_flavour} +{ + for (auto const& name: { + "i64.add", + "i64.sub", + "i64.mul", + "i64.div_u", + "i64.rem_u", + "i64.and", + "i64.or", + "i64.xor", + "i64.shl", + "i64.shr_u", + "i64.eq", + "i64.ne", + "i64.lt_u", + "i64.gt_u", + "i64.le_u", + "i64.ge_u" + }) + addFunction(name, 2, 1); + + addFunction("i64.eqz", 1, 1); + addFunction("i64.store", 2, 0); + addFunction("i64.load", 1, 1); + + addFunction("drop", 1, 0); + addFunction("unreachable", 0, 0); +} + +BuiltinFunction const* WasmDialect::builtin(YulString _name) const +{ + auto it = m_functions.find(_name); + if (it != m_functions.end()) + return &it->second; + else + return nullptr; +} + +void WasmDialect::addFunction(string _name, size_t _params, size_t _returns) +{ + YulString name{std::move(_name)}; + BuiltinFunction& f = m_functions[name]; + f.name = name; + f.parameters.resize(_params); + f.returns.resize(_returns); + f.movable = false; + f.literalArguments = false; +} diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h new file mode 100644 index 000000000000..71509ca08743 --- /dev/null +++ b/libyul/backends/wasm/WasmDialect.h @@ -0,0 +1,55 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Dialects for Wasm. + */ + +#pragma once + +#include + +#include + +namespace yul +{ + +class YulString; +using Type = YulString; +struct FunctionCall; +struct Object; + +/** + * Yul dialect for Wasm as a backend. + * + * Builtin functions are a subset of the wasm instructions, always implicitly assuming + * unsigned 64 bit types. + * + * !This is subject to changes! + */ +struct WasmDialect: public Dialect +{ + WasmDialect(AsmFlavour _flavour); + + BuiltinFunction const* builtin(YulString _name) const override; + +protected: + void addFunction(std::string _name, size_t _params, size_t _returns); + + std::map m_functions; +}; + +} diff --git a/libyul/optimiser/ASTCopier.cpp b/libyul/optimiser/ASTCopier.cpp index f98cb5653df7..efe3ff8540f4 100644 --- a/libyul/optimiser/ASTCopier.cpp +++ b/libyul/optimiser/ASTCopier.cpp @@ -91,7 +91,7 @@ Expression ASTCopier::operator()(FunctionalInstruction const& _instruction) Expression ASTCopier::operator()(Identifier const& _identifier) { - return Identifier{_identifier.location, translateIdentifier(_identifier.name)}; + return translate(_identifier); } Expression ASTCopier::operator()(Literal const& _literal) diff --git a/libyul/optimiser/ASTCopier.h b/libyul/optimiser/ASTCopier.h index a408b43fbfb6..b2e0a383a650 100644 --- a/libyul/optimiser/ASTCopier.h +++ b/libyul/optimiser/ASTCopier.h @@ -104,7 +104,7 @@ class ASTCopier: public ExpressionCopier, public StatementCopier Block translate(Block const& _block); Case translate(Case const& _case); - Identifier translate(Identifier const& _identifier); + virtual Identifier translate(Identifier const& _identifier); Literal translate(Literal const& _literal); TypedName translate(TypedName const& _typedName); diff --git a/libyul/optimiser/ASTWalker.cpp b/libyul/optimiser/ASTWalker.cpp index fe08c4313a3c..85234c4c757b 100644 --- a/libyul/optimiser/ASTWalker.cpp +++ b/libyul/optimiser/ASTWalker.cpp @@ -27,8 +27,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; - void ASTWalker::operator()(FunctionalInstruction const& _instr) { diff --git a/libyul/optimiser/CommonSubexpressionEliminator.h b/libyul/optimiser/CommonSubexpressionEliminator.h index 9f416d9fd517..6499e6db3feb 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.h +++ b/libyul/optimiser/CommonSubexpressionEliminator.h @@ -32,7 +32,7 @@ struct Dialect; * Optimisation stage that replaces expressions known to be the current value of a variable * in scope by a reference to that variable. * - * Prerequisite: Disambiguator + * Prerequisite: Disambiguator, ForLoopInitRewriter. */ class CommonSubexpressionEliminator: public DataFlowAnalyzer { diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 5a812151d833..86011ffd108b 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -112,10 +112,9 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) void DataFlowAnalyzer::operator()(ForLoop& _for) { - // Special scope handling of the pre block. - pushScope(false); - for (auto& statement: _for.pre.statements) - visit(statement); + // If the pre block was not empty, + // we would have to deal with more complicated scoping rules. + assertThrow(_for.pre.statements.empty(), OptimizerException, ""); AssignmentsSinceContinue assignmentsSinceCont; assignmentsSinceCont(_for.body); @@ -130,8 +129,6 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) clearValues(assignmentsSinceCont.names()); (*this)(_for.post); clearValues(assignments.names()); - - popScope(); } void DataFlowAnalyzer::operator()(Block& _block) diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 5fb5db958d2f..100a38665c02 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -39,7 +39,7 @@ struct Dialect; * * A special zero constant expression is used for the default value of variables. * - * Prerequisite: Disambiguator + * Prerequisite: Disambiguator, ForLoopInitRewriter. */ class DataFlowAnalyzer: public ASTModifier { diff --git a/libyul/optimiser/DeadCodeEliminator.cpp b/libyul/optimiser/DeadCodeEliminator.cpp new file mode 100644 index 000000000000..c405f2d76003 --- /dev/null +++ b/libyul/optimiser/DeadCodeEliminator.cpp @@ -0,0 +1,100 @@ +/* + 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 . +*/ +/** + * Optimisation stage that removes unreachable code. + */ + +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; + +namespace +{ +bool isTerminating(yul::ExpressionStatement const& _exprStmnt) +{ + if (_exprStmnt.expression.type() != typeid(FunctionalInstruction)) + return false; + + auto const& funcInstr = boost::get(_exprStmnt.expression); + + return eth::SemanticInformation::terminatesControlFlow(funcInstr.instruction); +} + +/// Returns an iterator to the first terminating statement or +/// `_block.statements.end()()` when none was found +auto findFirstTerminatingStatement(Block& _block) +{ + return find_if( + _block.statements.begin(), + _block.statements.end(), + [](Statement const& _stmnt) + { + if ( + _stmnt.type() == typeid(ExpressionStatement) && + isTerminating(boost::get(_stmnt)) + ) + return true; + else if (_stmnt.type() == typeid(Break)) + return true; + else if (_stmnt.type() == typeid(Continue)) + return true; + + return false; + } + ); +} +} + +void DeadCodeEliminator::operator()(ForLoop& _for) +{ + yulAssert(_for.pre.statements.empty(), "DeadCodeEliminator needs ForLoopInitRewriter as a prerequisite."); + ASTModifier::operator()(_for); +} + +void DeadCodeEliminator::operator()(Block& _block) +{ + auto& statements = _block.statements; + + auto firstTerminatingStatment = findFirstTerminatingStatement(_block); + + if ( + firstTerminatingStatment != statements.end() && + firstTerminatingStatment + 1 != statements.end() + ) + statements.erase( + std::remove_if( + firstTerminatingStatment + 1, + statements.end(), + [] (Statement const& _s) + { + return _s.type() != typeid(yul::FunctionDefinition); + } + ), + statements.end() + ); + + ASTModifier::operator()(_block); +} + diff --git a/libyul/optimiser/DeadCodeEliminator.h b/libyul/optimiser/DeadCodeEliminator.h new file mode 100644 index 000000000000..c4d60cf4c4f7 --- /dev/null +++ b/libyul/optimiser/DeadCodeEliminator.h @@ -0,0 +1,54 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Optimisation stage that removes unused variables and functions. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace yul +{ + +/** + * Optimisation stage that removes unreachable code + * + * Unreachable code is any code within a block which is preceded by a + * return, invalid, break, continue, selfdestruct or revert. + * + * Function definitions are retained as they might be called by earlier + * code and thus are considered reachable. + * + * Because variables declared in a for loop's init block have their scope extended to the loop body, + * we require ForLoopInitRewriter to run before this step. + * + * Prerequisite: ForLoopInitRewriter + */ +class DeadCodeEliminator: public ASTModifier +{ +public: + using ASTModifier::operator(); + void operator()(ForLoop& _for) override; + void operator()(Block& _block) override; +}; + +} diff --git a/libyul/optimiser/Disambiguator.cpp b/libyul/optimiser/Disambiguator.cpp index cb56ee998bcd..d1459bce80d3 100644 --- a/libyul/optimiser/Disambiguator.cpp +++ b/libyul/optimiser/Disambiguator.cpp @@ -28,7 +28,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; YulString Disambiguator::translateIdentifier(YulString _originalName) { diff --git a/libyul/optimiser/EquivalentFunctionCombiner.cpp b/libyul/optimiser/EquivalentFunctionCombiner.cpp index 939e63d23537..c9b34436be6e 100644 --- a/libyul/optimiser/EquivalentFunctionCombiner.cpp +++ b/libyul/optimiser/EquivalentFunctionCombiner.cpp @@ -25,7 +25,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void EquivalentFunctionCombiner::run(Block& _ast) { diff --git a/libyul/optimiser/EquivalentFunctionDetector.cpp b/libyul/optimiser/EquivalentFunctionDetector.cpp index d3a697bdcd5c..e4fd7f5c8a48 100644 --- a/libyul/optimiser/EquivalentFunctionDetector.cpp +++ b/libyul/optimiser/EquivalentFunctionDetector.cpp @@ -27,7 +27,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace solidity; void EquivalentFunctionDetector::operator()(FunctionDefinition const& _fun) { diff --git a/libyul/optimiser/ExpressionInliner.cpp b/libyul/optimiser/ExpressionInliner.cpp index 858c87337656..43a6a3342624 100644 --- a/libyul/optimiser/ExpressionInliner.cpp +++ b/libyul/optimiser/ExpressionInliner.cpp @@ -30,7 +30,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void ExpressionInliner::run() { diff --git a/libyul/optimiser/ExpressionJoiner.cpp b/libyul/optimiser/ExpressionJoiner.cpp index 02ac4e45e71f..654e9b5a86f5 100644 --- a/libyul/optimiser/ExpressionJoiner.cpp +++ b/libyul/optimiser/ExpressionJoiner.cpp @@ -33,7 +33,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void ExpressionJoiner::operator()(FunctionalInstruction& _instruction) { diff --git a/libyul/optimiser/ExpressionSimplifier.cpp b/libyul/optimiser/ExpressionSimplifier.cpp index 213cac977c75..c1764c792ec5 100644 --- a/libyul/optimiser/ExpressionSimplifier.cpp +++ b/libyul/optimiser/ExpressionSimplifier.cpp @@ -30,8 +30,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; - void ExpressionSimplifier::visit(Expression& _expression) { diff --git a/libyul/optimiser/ExpressionSimplifier.h b/libyul/optimiser/ExpressionSimplifier.h index bf62fb3070d0..1f371a8fc200 100644 --- a/libyul/optimiser/ExpressionSimplifier.h +++ b/libyul/optimiser/ExpressionSimplifier.h @@ -36,7 +36,7 @@ struct Dialect; * It tracks the current values of variables using the DataFlowAnalyzer * and takes them into account for replacements. * - * Prerequisite: Disambiguator. + * Prerequisite: Disambiguator, ForLoopInitRewriter. */ class ExpressionSimplifier: public DataFlowAnalyzer { diff --git a/libyul/optimiser/ExpressionSplitter.cpp b/libyul/optimiser/ExpressionSplitter.cpp index 2f80fc32c213..7eb099b8e41f 100644 --- a/libyul/optimiser/ExpressionSplitter.cpp +++ b/libyul/optimiser/ExpressionSplitter.cpp @@ -34,7 +34,6 @@ using namespace std; using namespace dev; using namespace langutil; using namespace yul; -using namespace dev::solidity; void ExpressionSplitter::operator()(FunctionalInstruction& _instruction) { diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index 3a60a33d4674..9dd09734f86f 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -37,7 +37,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser): m_ast(_ast), m_nameDispenser(_dispenser) diff --git a/libyul/optimiser/FunctionGrouper.cpp b/libyul/optimiser/FunctionGrouper.cpp index b9852fcd9007..a8d7982b4d59 100644 --- a/libyul/optimiser/FunctionGrouper.cpp +++ b/libyul/optimiser/FunctionGrouper.cpp @@ -28,7 +28,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void FunctionGrouper::operator()(Block& _block) diff --git a/libyul/optimiser/FunctionHoister.cpp b/libyul/optimiser/FunctionHoister.cpp index 4863b94d9de6..6ed930fcc87d 100644 --- a/libyul/optimiser/FunctionHoister.cpp +++ b/libyul/optimiser/FunctionHoister.cpp @@ -29,7 +29,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void FunctionHoister::operator()(Block& _block) { diff --git a/libyul/optimiser/MainFunction.cpp b/libyul/optimiser/MainFunction.cpp index fabbf66fda7c..7ece2c330d4c 100644 --- a/libyul/optimiser/MainFunction.cpp +++ b/libyul/optimiser/MainFunction.cpp @@ -31,7 +31,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void MainFunction::operator()(Block& _block) { diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index a171f3610214..02b5a7179789 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -63,6 +63,16 @@ void CodeSize::visit(Statement const& _statement) { if (_statement.type() == typeid(FunctionDefinition) && m_ignoreFunctions) return; + else if ( + _statement.type() == typeid(If) || + _statement.type() == typeid(Break) || + _statement.type() == typeid(Continue) + ) + m_size += 2; + else if (_statement.type() == typeid(ForLoop)) + m_size += 3; + else if (_statement.type() == typeid(Switch)) + m_size += 1 + 2 * boost::get(_statement).cases.size(); else if (!( _statement.type() == typeid(Block) || _statement.type() == typeid(ExpressionStatement) || @@ -99,12 +109,11 @@ void CodeCost::operator()(FunctionCall const& _funCall) void CodeCost::operator()(FunctionalInstruction const& _instr) { - using namespace dev::solidity; yulAssert(m_cost >= 1, "Should assign cost one in visit(Expression)."); - Tier gasPriceTier = instructionInfo(_instr.instruction).gasPriceTier; - if (gasPriceTier < Tier::VeryLow) + dev::eth::Tier gasPriceTier = dev::eth::instructionInfo(_instr.instruction).gasPriceTier; + if (gasPriceTier < dev::eth::Tier::VeryLow) m_cost -= 1; - else if (gasPriceTier < Tier::High) + else if (gasPriceTier < dev::eth::Tier::High) m_cost += 1; else m_cost += 49; diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index c0f6a9cd0638..1620d4d33848 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -37,6 +37,11 @@ namespace yul * - variable references * - variable declarations (only the right hand side has a cost) * - assignments (only the value has a cost) + * + * As another exception, each statement incurs and additional cost of one + * per jump/branch. This means if, break and continue statements have a cost of 2, + * switch statements have a cost of 1 plus the number of cases times two, + * and for loops cost 3. */ class CodeSize: public ASTWalker { diff --git a/libyul/optimiser/NameDispenser.cpp b/libyul/optimiser/NameDispenser.cpp index 52a1158740c5..2a0c1e44f979 100644 --- a/libyul/optimiser/NameDispenser.cpp +++ b/libyul/optimiser/NameDispenser.cpp @@ -23,6 +23,10 @@ #include #include #include +#include +#include + +#include using namespace std; using namespace dev; @@ -42,7 +46,7 @@ NameDispenser::NameDispenser(Dialect const& _dialect, set _usedNames) YulString NameDispenser::newName(YulString _nameHint) { YulString name = _nameHint; - while (name.empty() || m_usedNames.count(name) || m_dialect.builtin(name)) + while (illegalName(name)) { m_counter++; name = YulString(_nameHint.str() + "_" + to_string(m_counter)); @@ -50,3 +54,12 @@ YulString NameDispenser::newName(YulString _nameHint) m_usedNames.emplace(name); return name; } + +bool NameDispenser::illegalName(YulString _name) +{ + if (_name.empty() || m_usedNames.count(_name) || m_dialect.builtin(_name)) + return true; + if (dynamic_cast(&m_dialect)) + return Parser::instructions().count(_name.str()); + return false; +} diff --git a/libyul/optimiser/NameDispenser.h b/libyul/optimiser/NameDispenser.h index 6b61596fefba..9591dd135b69 100644 --- a/libyul/optimiser/NameDispenser.h +++ b/libyul/optimiser/NameDispenser.h @@ -47,6 +47,7 @@ class NameDispenser YulString newName(YulString _nameHint); private: + bool illegalName(YulString _name); Dialect const& m_dialect; std::set m_usedNames; diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index 48f0e7fba310..60e2dab4d521 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -31,7 +31,6 @@ using namespace std; using namespace dev; using namespace yul; -using namespace dev::solidity; void RedundantAssignEliminator::operator()(Identifier const& _identifier) { @@ -97,38 +96,34 @@ void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDe { std::set outerDeclaredVariables; TrackedAssignments outerAssignments; + ForLoopInfo forLoopInfo; swap(m_declaredVariables, outerDeclaredVariables); swap(m_assignments, outerAssignments); + swap(m_forLoopInfo, forLoopInfo); (*this)(_functionDefinition.body); for (auto const& param: _functionDefinition.parameters) - { - changeUndecidedTo(param.name, State::Unused); - finalize(param.name); - } + finalize(param.name, State::Unused); for (auto const& retParam: _functionDefinition.returnVariables) - { - changeUndecidedTo(retParam.name, State::Used); - finalize(retParam.name); - } + finalize(retParam.name, State::Used); swap(m_declaredVariables, outerDeclaredVariables); swap(m_assignments, outerAssignments); + swap(m_forLoopInfo, forLoopInfo); } void RedundantAssignEliminator::operator()(ForLoop const& _forLoop) { - // This will set all variables that are declared in this - // block to "unused" when it is destroyed. - BlockScope scope(*this); + ForLoopInfo outerForLoopInfo; + swap(outerForLoopInfo, m_forLoopInfo); + ++m_forLoopNestingDepth; - // We need to visit the statements directly because of the - // scoping rules. - walkVector(_forLoop.pre.statements); + // If the pre block was not empty, + // we would have to deal with more complicated scoping rules. + assertThrow(_forLoop.pre.statements.empty(), OptimizerException, ""); - // We just run the loop twice to account for the - // back edge. + // We just run the loop twice to account for the back edge. // There need not be more runs because we only have three different states. visit(*_forLoop.condition); @@ -136,39 +131,77 @@ void RedundantAssignEliminator::operator()(ForLoop const& _forLoop) TrackedAssignments zeroRuns{m_assignments}; (*this)(_forLoop.body); + merge(m_assignments, move(m_forLoopInfo.pendingContinueStmts)); + m_forLoopInfo.pendingContinueStmts = {}; (*this)(_forLoop.post); visit(*_forLoop.condition); - TrackedAssignments oneRun{m_assignments}; + if (m_forLoopNestingDepth < 6) + { + // Do the second run only for small nesting depths to avoid horrible runtime. + TrackedAssignments oneRun{m_assignments}; - (*this)(_forLoop.body); - (*this)(_forLoop.post); + (*this)(_forLoop.body); - visit(*_forLoop.condition); + merge(m_assignments, move(m_forLoopInfo.pendingContinueStmts)); + m_forLoopInfo.pendingContinueStmts.clear(); + (*this)(_forLoop.post); + + visit(*_forLoop.condition); + // Order of merging does not matter because "max" is commutative and associative. + merge(m_assignments, move(oneRun)); + } + else + { + // Shortcut to avoid horrible runtime: + // Change all assignments that were newly introduced in the for loop to "used". + // We do not have to do that with the "break" or "continue" paths, because + // they will be joined later anyway. + // TODO parallel traversal might be more efficient here. + for (auto& var: m_assignments) + for (auto& assignment: var.second) + { + auto zeroIt = zeroRuns.find(var.first); + if (zeroIt != zeroRuns.end() && zeroIt->second.count(assignment.first)) + continue; + assignment.second = State::Value::Used; + } + } - // Order does not matter because "max" is commutative and associative. - merge(m_assignments, move(oneRun)); + // Order of merging does not matter because "max" is commutative and associative. merge(m_assignments, move(zeroRuns)); + merge(m_assignments, move(m_forLoopInfo.pendingBreakStmts)); + m_forLoopInfo.pendingBreakStmts.clear(); + + // Restore potential outer for-loop states. + swap(m_forLoopInfo, outerForLoopInfo); + --m_forLoopNestingDepth; } void RedundantAssignEliminator::operator()(Break const&) { - yulAssert(false, "Not implemented yet."); + m_forLoopInfo.pendingBreakStmts.emplace_back(move(m_assignments)); + m_assignments.clear(); } void RedundantAssignEliminator::operator()(Continue const&) { - yulAssert(false, "Not implemented yet."); + m_forLoopInfo.pendingContinueStmts.emplace_back(move(m_assignments)); + m_assignments.clear(); } void RedundantAssignEliminator::operator()(Block const& _block) { - // This will set all variables that are declared in this - // block to "unused" when it is destroyed. - BlockScope scope(*this); + set outerDeclaredVariables; + swap(m_declaredVariables, outerDeclaredVariables); ASTWalker::operator()(_block); + + for (auto const& var: m_declaredVariables) + finalize(var, State::Unused); + + swap(m_declaredVariables, outerDeclaredVariables); } void RedundantAssignEliminator::run(Dialect const& _dialect, Block& _ast) @@ -218,24 +251,45 @@ void RedundantAssignEliminator::merge(TrackedAssignments& _target, TrackedAssign }); } +void RedundantAssignEliminator::merge(TrackedAssignments& _target, vector&& _source) +{ + for (TrackedAssignments& ts: _source) + merge(_target, move(ts)); + _source.clear(); +} + void RedundantAssignEliminator::changeUndecidedTo(YulString _variable, RedundantAssignEliminator::State _newState) { for (auto& assignment: m_assignments[_variable]) - if (assignment.second == State{State::Undecided}) + if (assignment.second == State::Undecided) assignment.second = _newState; } -void RedundantAssignEliminator::finalize(YulString _variable) +void RedundantAssignEliminator::finalize(YulString _variable, RedundantAssignEliminator::State _finalState) { - for (auto& assignment: m_assignments[_variable]) + finalize(m_assignments, _variable, _finalState); + for (auto& assignments: m_forLoopInfo.pendingBreakStmts) + finalize(assignments, _variable, _finalState); + for (auto& assignments: m_forLoopInfo.pendingContinueStmts) + finalize(assignments, _variable, _finalState); +} + +void RedundantAssignEliminator::finalize( + TrackedAssignments& _assignments, + YulString _variable, + RedundantAssignEliminator::State _finalState +) +{ + for (auto const& assignment: _assignments[_variable]) { - assertThrow(assignment.second != State::Undecided, OptimizerException, ""); - if (assignment.second == State{State::Unused} && MovableChecker{*m_dialect, *assignment.first->value}.movable()) + State const state = assignment.second == State::Undecided ? _finalState : assignment.second; + + if (state == State::Unused && MovableChecker{*m_dialect, *assignment.first->value}.movable()) // TODO the only point where we actually need this // to be a set is for the for loop m_pendingRemovals.insert(assignment.first); } - m_assignments.erase(_variable); + _assignments.erase(_variable); } void AssignmentRemover::operator()(Block& _block) diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h index 8ccd37faea90..ecea0fefadd9 100644 --- a/libyul/optimiser/RedundantAssignEliminator.h +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -25,6 +25,7 @@ #include #include +#include namespace yul { @@ -81,6 +82,11 @@ struct Dialect; * one run and two runs and then combine them at the end. * Running at most twice is enough because there are only three different states. * + * Since this algorithm has exponential runtime in the nesting depth of for loops, + * a shortcut is taken at a certain nesting level: We only use the zero- and + * once-run of the for loop and change any assignment that was newly introduced + * in the for loop from to "used". + * * For switch statements that have a "default"-case, there is no control-flow * part that skips the switch. * @@ -94,7 +100,7 @@ struct Dialect; * This step is usually run right after the SSA transform to complete * the generation of the pseudo-SSA. * - * Prerequisite: Disambiguator. + * Prerequisite: Disambiguator, ForLoopInitRewriter. */ class RedundantAssignEliminator: public ASTWalker { @@ -136,33 +142,6 @@ class RedundantAssignEliminator: public ASTWalker Value m_value = Undecided; }; - /** - * Takes care about storing the list of declared variables and - * sets them to "unused" when it is destroyed. - */ - class BlockScope - { - public: - explicit BlockScope(RedundantAssignEliminator& _rae): m_rae(_rae) - { - swap(m_rae.m_declaredVariables, m_outerDeclaredVariables); - } - ~BlockScope() - { - // This should actually store all declared variables - // into a different mapping - for (auto const& var: m_rae.m_declaredVariables) - m_rae.changeUndecidedTo(var, State::Unused); - for (auto const& var: m_rae.m_declaredVariables) - m_rae.finalize(var); - swap(m_rae.m_declaredVariables, m_outerDeclaredVariables); - } - - private: - RedundantAssignEliminator& m_rae; - std::set m_outerDeclaredVariables; - }; - // TODO check that this does not cause nondeterminism! // This could also be a pseudo-map from state to assignment. using TrackedAssignments = std::map>; @@ -171,13 +150,30 @@ class RedundantAssignEliminator: public ASTWalker /// above. /// Will destroy @a _source. static void merge(TrackedAssignments& _target, TrackedAssignments&& _source); + static void merge(TrackedAssignments& _target, std::vector&& _source); void changeUndecidedTo(YulString _variable, State _newState); - void finalize(YulString _variable); + /// Called when a variable goes out of scope. Sets the state of all still undecided + /// assignments to the final state. In this case, this also applies to pending + /// break and continue TrackedAssignments. + void finalize(YulString _variable, State _finalState); + /// Helper function for the above. + void finalize(TrackedAssignments& _assignments, YulString _variable, State _finalState); Dialect const* m_dialect; std::set m_declaredVariables; std::set m_pendingRemovals; TrackedAssignments m_assignments; + + /// Working data for traversing for-loops. + struct ForLoopInfo + { + /// Tracked assignment states for each break statement. + std::vector pendingBreakStmts; + /// Tracked assignment states for each continue statement. + std::vector pendingContinueStmts; + }; + ForLoopInfo m_forLoopInfo; + size_t m_forLoopNestingDepth = 0; }; class AssignmentRemover: public ASTModifier diff --git a/libyul/optimiser/Rematerialiser.h b/libyul/optimiser/Rematerialiser.h index b568b5dbd156..6871cdaa9d43 100644 --- a/libyul/optimiser/Rematerialiser.h +++ b/libyul/optimiser/Rematerialiser.h @@ -33,7 +33,7 @@ namespace yul * - the variable is referenced at most 5 times and the value is rather cheap * ("cost" of at most 1 like a constant up to 0xff) * - * Prerequisite: Disambiguator + * Prerequisite: Disambiguator, ForLoopInitRewriter. */ class Rematerialiser: public DataFlowAnalyzer { diff --git a/libyul/optimiser/SSATransform.cpp b/libyul/optimiser/SSATransform.cpp index dd10336c617c..625d82bdc01b 100644 --- a/libyul/optimiser/SSATransform.cpp +++ b/libyul/optimiser/SSATransform.cpp @@ -27,12 +27,10 @@ #include - using namespace std; using namespace dev; using namespace langutil; using namespace yul; -using namespace dev::solidity; void SSATransform::operator()(Identifier& _identifier) { @@ -64,17 +62,13 @@ void SSATransform::operator()(Block& _block) { set variablesToClearAtEnd; - // Creates a new variable (and returns its declaration) with value _value - // and replaces _value by a reference to that new variable. - - auto replaceByNew = [&](SourceLocation _loc, YulString _varName, YulString _type, unique_ptr& _value) -> VariableDeclaration + // Creates a new variable and stores it in the current variable value map. + auto newVariable = [&](YulString _varName) -> YulString { YulString newName = m_nameDispenser.newName(_varName); m_currentVariableValues[_varName] = newName; variablesToClearAtEnd.emplace(_varName); - unique_ptr v = make_unique(Identifier{_loc, newName}); - _value.swap(v); - return VariableDeclaration{_loc, {TypedName{_loc, std::move(newName), std::move(_type)}}, std::move(v)}; + return newName; }; iterateReplacing( @@ -86,36 +80,60 @@ void SSATransform::operator()(Block& _block) VariableDeclaration& varDecl = boost::get(_s); if (varDecl.value) visit(*varDecl.value); - if (varDecl.variables.size() != 1 || !m_variablesToReplace.count(varDecl.variables.front().name)) + + bool needToReplaceSome = false; + for (auto const& var: varDecl.variables) + if (m_variablesToReplace.count(var.name)) + needToReplaceSome = true; + if (!needToReplaceSome) return {}; - vector v; + // Replace "let a := v" by "let a_1 := v let a := a_1" - v.emplace_back(replaceByNew( - varDecl.location, - varDecl.variables.front().name, - varDecl.variables.front().type, - varDecl.value - )); - v.emplace_back(move(varDecl)); - return std::move(v); + // Replace "let a, b := v" by "let a_1, b_1 := v let a := a_1 let b := b_2" + auto loc = varDecl.location; + vector statements; + statements.emplace_back(VariableDeclaration{loc, {}, std::move(varDecl.value)}); + TypedNameList newVariables; + for (auto const& var: varDecl.variables) + { + YulString newName = newVariable(var.name); + YulString oldName = var.name; + newVariables.emplace_back(TypedName{loc, newName, {}}); + statements.emplace_back(VariableDeclaration{ + loc, + {TypedName{loc, oldName, {}}}, + make_unique(Identifier{loc, newName}) + }); + } + boost::get(statements.front()).variables = std::move(newVariables); + return std::move(statements); } else if (_s.type() == typeid(Assignment)) { Assignment& assignment = boost::get(_s); visit(*assignment.value); - if (assignment.variableNames.size() != 1) - return {}; - assertThrow(m_variablesToReplace.count(assignment.variableNames.front().name), OptimizerException, ""); - vector v; + for (auto const& var: assignment.variableNames) + assertThrow(m_variablesToReplace.count(var.name), OptimizerException, ""); + // Replace "a := v" by "let a_1 := v a := v" - v.emplace_back(replaceByNew( - assignment.location, - assignment.variableNames.front().name, - {}, // TODO determine type - assignment.value - )); - v.emplace_back(move(assignment)); - return std::move(v); + // Replace "a, b := v" by "let a_1, b_1 := v a := a_1 b := b_2" + auto loc = assignment.location; + vector statements; + statements.emplace_back(VariableDeclaration{loc, {}, std::move(assignment.value)}); + TypedNameList newVariables; + for (auto const& var: assignment.variableNames) + { + YulString newName = newVariable(var.name); + YulString oldName = var.name; + newVariables.emplace_back(TypedName{loc, newName, {}}); + statements.emplace_back(Assignment{ + loc, + {Identifier{loc, oldName}}, + make_unique(Identifier{loc, newName}) + }); + } + boost::get(statements.front()).variables = std::move(newVariables); + return std::move(statements); } else visit(_s); diff --git a/libyul/optimiser/SSATransform.h b/libyul/optimiser/SSATransform.h index 4cb62f238f5f..1a367afc7769 100644 --- a/libyul/optimiser/SSATransform.h +++ b/libyul/optimiser/SSATransform.h @@ -87,6 +87,8 @@ class SSATransform: public ASTModifier { } NameDispenser& m_nameDispenser; + /// This is a set of all variables that are assigned to anywhere in the code. + /// Variables that are only declared but never re-assigned are not touched. std::set const& m_variablesToReplace; std::map m_currentVariableValues; }; diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index c70ed2061b42..205b42c076e0 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -30,11 +30,12 @@ using namespace std; using namespace dev; +using namespace dev::eth; using namespace langutil; using namespace yul; -SimplificationRule const* SimplificationRules::findFirstMatch( +SimplificationRule const* SimplificationRules::findFirstMatch( Expression const& _expr, Dialect const& _dialect, map const& _ssaValues @@ -59,7 +60,7 @@ SimplificationRule const* SimplificationRules::findFirstMatch( bool SimplificationRules::isInitialized() const { - return !m_rules[uint8_t(solidity::Instruction::ADD)].empty(); + return !m_rules[uint8_t(dev::eth::Instruction::ADD)].empty(); } void SimplificationRules::addRules(vector> const& _rules) @@ -93,7 +94,7 @@ SimplificationRules::SimplificationRules() assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized."); } -Pattern::Pattern(solidity::Instruction _instruction, vector const& _arguments): +yul::Pattern::Pattern(dev::eth::Instruction _instruction, vector const& _arguments): m_kind(PatternKind::Operation), m_instruction(_instruction), m_arguments(_arguments) @@ -187,7 +188,7 @@ bool Pattern::matches( return true; } -solidity::Instruction Pattern::instruction() const +dev::eth::Instruction Pattern::instruction() const { assertThrow(m_kind == PatternKind::Operation, OptimizerException, ""); return m_instruction; diff --git a/libyul/optimiser/SimplificationRules.h b/libyul/optimiser/SimplificationRules.h index 8213a185b079..40ae43260d41 100644 --- a/libyul/optimiser/SimplificationRules.h +++ b/libyul/optimiser/SimplificationRules.h @@ -20,7 +20,6 @@ #pragma once -#include #include #include @@ -47,7 +46,7 @@ class SimplificationRules: public boost::noncopyable /// @returns a pointer to the first matching pattern and sets the match /// groups accordingly. /// @param _ssaValues values of variables that are assigned exactly once. - static SimplificationRule const* findFirstMatch( + static dev::eth::SimplificationRule const* findFirstMatch( Expression const& _expr, Dialect const& _dialect, std::map const& _ssaValues @@ -57,13 +56,13 @@ class SimplificationRules: public boost::noncopyable /// by the constructor, but we had some issues with static initialization. bool isInitialized() const; private: - void addRules(std::vector> const& _rules); - void addRule(SimplificationRule const& _rule); + void addRules(std::vector> const& _rules); + void addRule(dev::eth::SimplificationRule const& _rule); void resetMatchGroups() { m_matchGroups.clear(); } std::map m_matchGroups; - std::vector> m_rules[256]; + std::vector> m_rules[256]; }; enum class PatternKind @@ -88,7 +87,7 @@ class Pattern // Matches a specific constant value. Pattern(dev::u256 const& _value): m_kind(PatternKind::Constant), m_data(std::make_shared(_value)) {} // Matches a given instruction with given arguments - Pattern(dev::solidity::Instruction _instruction, std::vector const& _arguments = {}); + Pattern(dev::eth::Instruction _instruction, std::vector const& _arguments = {}); /// Sets this pattern to be part of the match group with the identifier @a _group. /// Inside one rule, all patterns in the same match group have to match expressions from the /// same expression equivalence class. @@ -105,7 +104,7 @@ class Pattern /// @returns the data of the matched expression if this pattern is part of a match group. dev::u256 d() const; - dev::solidity::Instruction instruction() const; + dev::eth::Instruction instruction() const; /// Turns this pattern into an actual expression. Should only be called /// for patterns resulting from an action, i.e. with match groups assigned. @@ -115,7 +114,7 @@ class Pattern Expression const& matchGroupValue() const; PatternKind m_kind = PatternKind::Any; - dev::solidity::Instruction m_instruction; ///< Only valid if m_kind is Operation + dev::eth::Instruction m_instruction; ///< Only valid if m_kind is Operation std::shared_ptr m_data; ///< Only valid if m_kind is Constant std::vector m_arguments; unsigned m_matchGroup = 0; diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index dd94a4ad3397..391e4153ce4b 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -36,7 +36,7 @@ ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _ { return {_location, FunctionalInstruction{ _location, - solidity::Instruction::POP, + dev::eth::Instruction::POP, {std::move(_expression)} }}; } @@ -105,7 +105,7 @@ OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt) std::move(_switchStmt.location), make_unique(FunctionalInstruction{ std::move(loc), - solidity::Instruction::EQ, + dev::eth::Instruction::EQ, {std::move(*switchCase.value), std::move(*_switchStmt.expression)} }), std::move(switchCase.body) diff --git a/libyul/optimiser/StructuralSimplifier.h b/libyul/optimiser/StructuralSimplifier.h index 222324700593..a4c90a7d3de4 100644 --- a/libyul/optimiser/StructuralSimplifier.h +++ b/libyul/optimiser/StructuralSimplifier.h @@ -36,7 +36,7 @@ namespace yul * - replace switch with const expr with matching case body * - replace for with false condition by its initialization part * - * Prerequisites: Disambiguator + * Prerequisite: Disambiguator, ForLoopInitRewriter. * * Important: Can only be used on EVM code. */ diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 91ac3f3a769c..e0ff847c65f8 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -70,10 +71,11 @@ void OptimiserSuite::run( VarDeclInitializer{}(ast); FunctionHoister{}(ast); BlockFlattener{}(ast); + ForLoopInitRewriter{}(ast); + DeadCodeEliminator{}(ast); FunctionGrouper{}(ast); EquivalentFunctionCombiner::run(ast); UnusedPruner::runUntilStabilised(*_dialect, ast, reservedIdentifiers); - ForLoopInitRewriter{}(ast); BlockFlattener{}(ast); StructuralSimplifier{*_dialect}(ast); BlockFlattener{}(ast); @@ -107,6 +109,7 @@ void OptimiserSuite::run( // still in SSA, perform structural simplification StructuralSimplifier{*_dialect}(ast); BlockFlattener{}(ast); + DeadCodeEliminator{}(ast); UnusedPruner::runUntilStabilised(*_dialect, ast, reservedIdentifiers); } { @@ -158,6 +161,7 @@ void OptimiserSuite::run( ExpressionSimplifier::run(*_dialect, ast); StructuralSimplifier{*_dialect}(ast); BlockFlattener{}(ast); + DeadCodeEliminator{}(ast); CommonSubexpressionEliminator{*_dialect}(ast); SSATransform::run(ast, dispenser); RedundantAssignEliminator::run(*_dialect, ast); @@ -192,7 +196,9 @@ void OptimiserSuite::run( // message once we perform code generation. StackCompressor::run(_dialect, ast, _optimizeStackAllocation, stackCompressorMaxIterations); BlockFlattener{}(ast); + DeadCodeEliminator{}(ast); + FunctionGrouper{}(ast); VarNameCleaner{ast, *_dialect, reservedIdentifiers}(ast); yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, ast); diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index f9878e4dadc5..4b1295a43500 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -85,7 +85,7 @@ void UnusedPruner::operator()(Block& _block) // instead of `pop`. statement = ExpressionStatement{varDecl.location, FunctionalInstruction{ varDecl.location, - solidity::Instruction::POP, + dev::eth::Instruction::POP, {*std::move(varDecl.value)} }}; } diff --git a/libyul/optimiser/VarNameCleaner.cpp b/libyul/optimiser/VarNameCleaner.cpp index 81532c136966..4c39462953b1 100644 --- a/libyul/optimiser/VarNameCleaner.cpp +++ b/libyul/optimiser/VarNameCleaner.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include #include @@ -93,7 +95,7 @@ void VarNameCleaner::operator()(Identifier& _identifier) YulString VarNameCleaner::findCleanName(YulString const& _name) const { auto newName = stripSuffix(_name); - if (newName != YulString{} && !isUsedName(newName)) + if (!isUsedName(newName)) return newName; // create new name with suffix (by finding a free identifier) @@ -108,7 +110,11 @@ YulString VarNameCleaner::findCleanName(YulString const& _name) const bool VarNameCleaner::isUsedName(YulString const& _name) const { - return m_dialect.builtin(_name) || m_usedNames.count(_name); + if (_name.empty() || m_dialect.builtin(_name) || m_usedNames.count(_name)) + return true; + if (dynamic_cast(&m_dialect)) + return Parser::instructions().count(_name.str()); + return false; } YulString VarNameCleaner::stripSuffix(YulString const& _name) const diff --git a/libyul/optimiser/VarNameCleaner.h b/libyul/optimiser/VarNameCleaner.h index 250cd84177df..7f2e9098957b 100644 --- a/libyul/optimiser/VarNameCleaner.h +++ b/libyul/optimiser/VarNameCleaner.h @@ -38,7 +38,7 @@ struct Dialect; * renumbered by their base name. * Function names are not modified. * - * Prerequisites: Disambiguator, FunctionHoister + * Prerequisites: Disambiguator, FunctionHoister, FunctionGrouper */ class VarNameCleaner: public ASTModifier { diff --git a/lllc/main.cpp b/lllc/main.cpp index f7f45e741016..10b3cdff0a2c 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -31,7 +31,6 @@ using namespace std; using namespace dev; -using namespace dev::solidity; using namespace dev::lll; static string const VersionString = @@ -130,11 +129,11 @@ int main(int argc, char** argv) } else if (mode == Disassemble) { - cout << disassemble(fromHex(src)) << endl; + cout << dev::eth::disassemble(fromHex(src)) << endl; } else if (mode == Binary || mode == Hex) { - auto bs = compileLLL(src, langutil::EVMVersion{}, optimise ? true : false, &errors, readFileAsString); + auto bs = compileLLL(std::move(src), langutil::EVMVersion{}, optimise ? true : false, &errors, readFileAsString); if (mode == Hex) cout << toHex(bs) << endl; else if (mode == Binary) @@ -142,11 +141,11 @@ int main(int argc, char** argv) } else if (mode == ParseTree) { - cout << parseLLL(src) << endl; + cout << parseLLL(std::move(src)) << endl; } else if (mode == Assembly) { - cout << compileLLLToAsm(src, langutil::EVMVersion{}, optimise ? true : false, &errors, readFileAsString) << endl; + cout << compileLLLToAsm(std::move(src), langutil::EVMVersion{}, optimise ? true : false, &errors, readFileAsString) << endl; } for (auto const& i: errors) diff --git a/scripts/aleth_with_log.sh b/scripts/aleth_with_log.sh new file mode 100755 index 000000000000..3fd8ba656c75 --- /dev/null +++ b/scripts/aleth_with_log.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +ALETH_PATH=$1 +ALETH_TMP_OUT=$2 +shift +shift + + +$ALETH_PATH $@ &> >(tail -n 100000 &> "$ALETH_TMP_OUT") & + +PID=$! + +function cleanup() +{ + kill $PID +} + +trap cleanup INT TERM + +wait $PID + diff --git a/scripts/download_ossfuzz_corpus.sh b/scripts/download_ossfuzz_corpus.sh new file mode 100755 index 000000000000..92478caf6ff8 --- /dev/null +++ b/scripts/download_ossfuzz_corpus.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +set -e +cd /tmp +git clone --depth 1 https://github.com/ethereum/solidity-fuzzing-corpus.git diff --git a/scripts/install_libfuzzer.sh b/scripts/install_libfuzzer.sh new file mode 100755 index 000000000000..f231403ef811 --- /dev/null +++ b/scripts/install_libfuzzer.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env sh +set -e +TEMPDIR=$(mktemp -d) +( + cd $TEMPDIR + svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer libfuzzer + mkdir -p build-libfuzzer + cd build-libfuzzer + CXXFLAGS="-O1 -stdlib=libstdc++" + $CXX $CXXFLAGS -std=c++11 -O2 -fPIC -c ../libfuzzer/*.cpp -I../libfuzzer + ar r /usr/lib/libFuzzingEngine.a *.o +) +rm -rf $TEMPDIR diff --git a/scripts/install_lpm.sh b/scripts/install_lpm.sh new file mode 100755 index 000000000000..0bc4f83d2bdc --- /dev/null +++ b/scripts/install_lpm.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env sh +set -e + +TEMPDIR="src" +cd / +mkdir -p $TEMPDIR +( + cd $TEMPDIR + git clone --depth 1 https://github.com/google/libprotobuf-mutator.git + mkdir -p LPM + cd LPM + cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release && ninja install +) diff --git a/scripts/regressions.py b/scripts/regressions.py new file mode 100755 index 000000000000..8ccd03b8f349 --- /dev/null +++ b/scripts/regressions.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +from argparse import ArgumentParser +import sys +import os +import subprocess +import re +import glob +import threading +import time + +DESCRIPTION = """Regressor is a tool to run regression tests in a CI env.""" + +class PrintDotsThread(object): + """Prints a dot every "interval" (default is 300) seconds""" + + def __init__(self, interval=300): + self.interval = interval + + thread = threading.Thread(target=self.run, args=()) + thread.daemon = True + thread.start() + + def run(self): + """ Runs until the main Python thread exits. """ + ## Print a newline at the very beginning. + print("") + while True: + # Print dot + print(".") + time.sleep(self.interval) + +class regressor(): + _re_sanitizer_log = re.compile(r"""ERROR: (?P\w+).*""") + _error_blacklist = ["AddressSanitizer", "libFuzzer"] + + def __init__(self, description, args): + self._description = description + self._args = self.parseCmdLine(description, args) + self._repo_root = os.path.dirname(sys.path[0]) + self._fuzzer_path = os.path.join(self._repo_root, + "build/test/tools/ossfuzz") + self._logpath = os.path.join(self._repo_root, "test_results") + + def parseCmdLine(self, description, args): + argParser = ArgumentParser(description) + argParser.add_argument('-o', '--out-dir', required=True, type=str, + help="""Directory where test results will be written""") + return argParser.parse_args(args) + + @staticmethod + def run_cmd(command, logfile=None, env=None): + if not logfile: + logfile = os.devnull + + if not env: + env = os.environ.copy() + + logfh = open(logfile, 'w') + proc = subprocess.Popen(command, shell=True, executable='/bin/bash', + env=env, stdout=logfh, + stderr=subprocess.STDOUT) + ret = proc.wait() + logfh.close() + + if ret != 0: + return False + return True + + def process_log(self, logfile): + ## Log may contain non ASCII characters, so we simply stringify them + ## since they don't matter for regular expression matching + rawtext = str(open(logfile, 'rb').read()) + list = re.findall(self._re_sanitizer_log, rawtext) + numSuppressedLeaks = list.count("LeakSanitizer") + rv = any(word in list for word in self._error_blacklist) + return not rv, numSuppressedLeaks + + def run(self): + for fuzzer in glob.iglob("{}/*_ossfuzz".format(self._fuzzer_path)): + basename = os.path.basename(fuzzer) + logfile = os.path.join(self._logpath, "{}.log".format(basename)) + corpus_dir = "/tmp/solidity-fuzzing-corpus/{0}_seed_corpus" \ + .format(basename) + cmd = "find {0} -type f | xargs -P2 {1}".format(corpus_dir, fuzzer) + if not self.run_cmd(cmd, logfile=logfile): + ret, numLeaks = self.process_log(logfile) + if not ret: + print( + "\t[-] AddressSanitizer reported failure for {0}. " + "Failure logged to test_results".format( + basename)) + return False + else: + print("\t[+] {0} passed regression tests but leaked " + "memory.".format(basename)) + print("\t\t[+] Suppressed {0} memory leak reports".format( + numLeaks)) + else: + print("\t[+] {0} passed regression tests.".format(basename)) + return True + + +if __name__ == '__main__': + dotprinter = PrintDotsThread() + tool = regressor(DESCRIPTION, sys.argv[1:]) + tool.run() diff --git a/scripts/soltest.sh b/scripts/soltest.sh index 00f484a14372..d1c41404ff47 100755 --- a/scripts/soltest.sh +++ b/scripts/soltest.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -e REPO_ROOT="$(dirname "$0")"/.. @@ -7,6 +6,27 @@ USE_DEBUGGER=0 DEBUGGER="gdb --args" BOOST_OPTIONS= SOLTEST_OPTIONS= +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} + +usage() { + echo 2>&1 " +Usage: $0 [options] [soltest-options] +Runs BOOST C++ unit test program, soltest. + +Options: + --debug soltest invocation prefaced with: \"$DEBUGGER\" + --debugger *dbg-cmd* soltest prefaced with your own debugger command. + --run_test | -t *name* filters test unit(s) to include or exclude from test. + This option can be given several times. + --boost-options *x* Set BOOST option *x*. + --show-progress | -p Set BOOST option --show-progress. + +Important environment variables: + +SOLIDITY_BUILD_DIR: Sets directory under the repository root of where test/soltest should be found. + The default is \"${SOLIDITY_BUILD_DIR}\". +" +} while [ $# -gt 0 ] do @@ -23,7 +43,11 @@ do shift BOOST_OPTIONS="${BOOST_OPTIONS} $1" ;; - -t) + --help) + usage + exit 0 + ;; + --run_test | -t ) shift BOOST_OPTIONS="${BOOST_OPTIONS} -t $1" ;; @@ -40,4 +64,4 @@ if [ "$USE_DEBUGGER" -ne "0" ]; then DEBUG_PREFIX=${DEBUGGER} fi -exec ${DEBUG_PREFIX} ${REPO_ROOT}/build/test/soltest ${BOOST_OPTIONS} -- --testpath ${REPO_ROOT}/test ${SOLTEST_OPTIONS} +exec ${DEBUG_PREFIX} ${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/test/soltest ${BOOST_OPTIONS} -- --testpath ${REPO_ROOT}/test ${SOLTEST_OPTIONS} diff --git a/scripts/tests.sh b/scripts/tests.sh index b0c627caa120..1a60ee262fe1 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -31,6 +31,8 @@ set -e REPO_ROOT="$(dirname "$0")"/.. WORKDIR=`mktemp -d` +# Will be printed in case of a test failure +ALETH_TMP_OUT=`mktemp` IPC_ENABLED=true ALETH_PID= CMDLINE_PID= @@ -71,8 +73,8 @@ safe_kill() { } cleanup() { - # ensure failing commands don't cause termination during cleanup (especially within safe_kill) - set +e + # ensure failing commands don't cause termination during cleanup (especially within safe_kill) + set +e if [[ "$IPC_ENABLED" = true ]] && [[ -n "${ALETH_PID}" ]] then @@ -85,6 +87,7 @@ cleanup() { echo "Cleaning up working directory ${WORKDIR} ..." rm -rf "$WORKDIR" || true + rm $ALETH_TMP_OUT } trap cleanup INT TERM @@ -132,8 +135,8 @@ function download_aleth() else mkdir -p /tmp/test # Any time the hash is updated here, the "Running compiler tests" section should also be updated. - ALETH_HASH="a6a9884bf3e5d8b3e01b55d4f6e9fe6dce5b5db7" - ALETH_VERSION=1.5.2 + ALETH_HASH="8979a9179d5222c89bf9daf7ca73cc115fa2dac2" + ALETH_VERSION=1.6.0-rc.1 wget -q -O /tmp/test/aleth.tar.gz https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz test "$(shasum /tmp/test/aleth.tar.gz)" = "$ALETH_HASH /tmp/test/aleth.tar.gz" tar -xf /tmp/test/aleth.tar.gz -C /tmp/test @@ -149,7 +152,9 @@ function download_aleth() # echos the PID function run_aleth() { - $ALETH_PATH --db memorydb --test -d "${WORKDIR}" >/dev/null 2>&1 & + # Use this to have aleth log output + #$REPO_ROOT/scripts/aleth_with_log.sh $ALETH_PATH $ALETH_TMP_OUT --log-verbosity 3 --db memorydb --test -d "${WORKDIR}" &> /dev/null & + $ALETH_PATH --db memorydb --test -d "${WORKDIR}" &> /dev/null & echo $! # Wait until the IPC endpoint is available. while [ ! -S "${WORKDIR}/geth.ipc" ] ; do sleep 1; done @@ -200,7 +205,7 @@ do force_abiv2_flag="" if [[ "$abiv2" == "yes" ]] then - force_abiv2_flag="--abiencoderv2" + force_abiv2_flag="--abiencoderv2 --optimize-yul" fi printTask "--> Running tests using "$optimize" --evm-version "$vm" $force_abiv2_flag..." @@ -209,13 +214,27 @@ do then if [ -n "$optimize" ] then - log=--logger=JUNIT,test_suite,$log_directory/opt_$vm.xml $testargs + log=--logger=JUNIT,error,$log_directory/opt_$vm.xml $testargs else - log=--logger=JUNIT,test_suite,$log_directory/noopt_$vm.xml $testargs_no_opt + log=--logger=JUNIT,error,$log_directory/noopt_$vm.xml $testargs_no_opt fi fi + set +e "$REPO_ROOT"/build/test/soltest $progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $IPC_FLAGS $force_abiv2_flag --ipcpath "${WORKDIR}/geth.ipc" + + if test "0" -ne "$?"; then + if [ -n "$log_directory" ] + then + # Need to kill aleth first so the log is written + safe_kill $ALETH_PID $ALETH_PATH + cp $ALETH_TMP_OUT $log_directory/aleth.log + printError "Some test failed, wrote aleth.log" + fi + exit 1 + fi + set -e + done done done diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 20612dacd549..d2b9e4ac4e1d 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -121,6 +121,7 @@ static string const g_strHelp = "help"; static string const g_strInputFile = "input-file"; static string const g_strInterface = "interface"; static string const g_strYul = "yul"; +static string const g_strIR = "ir"; static string const g_strLicense = "license"; static string const g_strLibraries = "libraries"; static string const g_strLink = "link"; @@ -166,6 +167,7 @@ static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; static string const g_argInputFile = g_strInputFile; static string const g_argYul = g_strYul; +static string const g_argIR = g_strIR; static string const g_argLibraries = g_strLibraries; static string const g_argLink = g_strLink; static string const g_argMachine = g_strMachine; @@ -284,15 +286,29 @@ void CommandLineInterface::handleBinary(string const& _contract) void CommandLineInterface::handleOpcode(string const& _contract) { if (m_args.count(g_argOutputDir)) - createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", solidity::disassemble(m_compiler->object(_contract).bytecode)); + createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", dev::eth::disassemble(m_compiler->object(_contract).bytecode)); else { sout() << "Opcodes: " << endl; - sout() << solidity::disassemble(m_compiler->object(_contract).bytecode); + sout() << dev::eth::disassemble(m_compiler->object(_contract).bytecode); sout() << endl; } } +void CommandLineInterface::handleIR(string const& _contractName) +{ + if (m_args.count(g_argIR)) + { + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName)); + else + { + sout() << "IR: " << endl; + sout() << m_compiler->yulIR(_contractName) << endl; + } + } +} + void CommandLineInterface::handleBytecode(string const& _contract) { if (m_args.count(g_argOpcodes)) @@ -685,6 +701,7 @@ Allowed options)", (g_argBinary.c_str(), "Binary of the contracts in hex.") (g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.") (g_argAbi.c_str(), "ABI specification of the contracts.") + (g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).") (g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.") (g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.") (g_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.") @@ -817,7 +834,7 @@ bool CommandLineInterface::processInput() { string input = dev::readStandardInput(); StandardCompiler compiler(fileReader); - sout() << compiler.compile(input) << endl; + sout() << compiler.compile(std::move(input)) << endl; return true; } @@ -901,13 +918,14 @@ bool CommandLineInterface::processInput() m_compiler->useMetadataLiteralSources(true); if (m_args.count(g_argInputFile)) m_compiler->setRemappings(m_remappings); - for (auto const& sourceCode: m_sourceCodes) - m_compiler->addSource(sourceCode.first, sourceCode.second); + m_compiler->setSources(m_sourceCodes); if (m_args.count(g_argLibraries)) m_compiler->setLibraries(m_libraries); m_compiler->setEVMVersion(m_evmVersion); // TODO: Perhaps we should not compile unless requested + m_compiler->enableIRGeneration(m_args.count(g_argIR)); + OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal(); settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as(); settings.runYulOptimiser = m_args.count(g_strOptimizeYul); @@ -919,10 +937,7 @@ bool CommandLineInterface::processInput() for (auto const& error: m_compiler->errors()) { g_hasOutput = true; - formatter->printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + formatter->printErrorInformation(*error); } if (!successful) @@ -1002,7 +1017,7 @@ void CommandLineInterface::handleCombinedJSON() if (requests.count(g_strBinaryRuntime)) contractData[g_strBinaryRuntime] = m_compiler->runtimeObject(contractName).toHex(); if (requests.count(g_strOpcodes)) - contractData[g_strOpcodes] = solidity::disassemble(m_compiler->object(contractName).bytecode); + contractData[g_strOpcodes] = dev::eth::disassemble(m_compiler->object(contractName).bytecode); if (requests.count(g_strAsm)) contractData[g_strAsm] = m_compiler->assemblyJSON(contractName, m_sourceCodes); if (requests.count(g_strSrcMap)) @@ -1240,12 +1255,16 @@ bool CommandLineInterface::assemble( map assemblyStacks; for (auto const& src: m_sourceCodes) { - auto& stack = assemblyStacks[src.first] = yul::AssemblyStack(m_evmVersion, _language); + auto& stack = assemblyStacks[src.first] = yul::AssemblyStack( + m_evmVersion, + _language, + _optimize ? OptimiserSettings::full() : OptimiserSettings::minimal() + ); try { if (!stack.parseAndAnalyze(src.first, src.second)) successful = false; - else if (_optimize) + else stack.optimize(); } catch (Exception const& _exception) @@ -1272,10 +1291,7 @@ bool CommandLineInterface::assemble( for (auto const& error: stack.errors()) { g_hasOutput = true; - formatter->printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + formatter->printErrorInformation(*error); } if (!Error::containsOnlyWarnings(stack.errors())) successful = false; @@ -1299,7 +1315,7 @@ bool CommandLineInterface::assemble( yul::MachineAssemblyObject object; try { - object = stack.assemble(_targetMachine, _optimize); + object = stack.assemble(_targetMachine); } catch (Exception const& _exception) { @@ -1366,6 +1382,7 @@ void CommandLineInterface::outputCompilationResults() handleGasEstimation(contract); handleBytecode(contract); + handleIR(contract); handleSignatureHashes(contract); handleMetadata(contract); handleABI(contract); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 0d3c656f599e..e9c293d1706f 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -65,6 +65,7 @@ class CommandLineInterface void handleAst(std::string const& _argStr); void handleBinary(std::string const& _contract); void handleOpcode(std::string const& _contract); + void handleIR(std::string const& _contract); void handleBytecode(std::string const& _contract); void handleSignatureHashes(std::string const& _contract); void handleMetadata(std::string const& _contract); diff --git a/solc/main.cpp b/solc/main.cpp index 6d5595422997..f4a4c5d24bbf 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -20,10 +20,10 @@ * Solidity commandline compiler. */ -#include "CommandLineInterface.h" +#include +#include #include #include -#include using namespace std; diff --git a/test/Common.h b/test/Common.h index f16646daaf45..12f2dacefa84 100644 --- a/test/Common.h +++ b/test/Common.h @@ -37,6 +37,7 @@ struct CommonOptions: boost::noncopyable boost::filesystem::path ipcPath; boost::filesystem::path testPath; bool optimize = false; + bool optimizeYul = false; bool disableIPC = false; bool disableSMT = false; diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index eeac68be28aa..23a715133d36 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -29,6 +29,9 @@ #include +#include +#include + using namespace std; using namespace dev; using namespace dev::test; @@ -57,10 +60,14 @@ ExecutionFramework::ExecutionFramework(): ExecutionFramework::ExecutionFramework(string const& _ipcPath, langutil::EVMVersion _evmVersion): m_rpc(RPCSession::instance(_ipcPath)), m_evmVersion(_evmVersion), - m_optimiserSettings(dev::test::Options::get().optimize ? solidity::OptimiserSettings::standard() : solidity::OptimiserSettings::minimal()), + m_optimiserSettings(solidity::OptimiserSettings::minimal()), m_showMessages(dev::test::Options::get().showMessages), m_sender(m_rpc.account(0)) { + if (dev::test::Options::get().optimizeYul) + m_optimiserSettings = solidity::OptimiserSettings::full(); + else if (dev::test::Options::get().optimize) + m_optimiserSettings = solidity::OptimiserSettings::standard(); m_rpc.test_rewindToBlock(0); } @@ -133,6 +140,7 @@ void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256 } string txHash = m_rpc.eth_sendTransaction(d); + waitForTransaction(txHash); m_rpc.test_mineBlocks(1); RPCSession::TransactionReceipt receipt(m_rpc.eth_getTransactionReceipt(txHash)); @@ -170,6 +178,27 @@ void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256 m_transactionSuccessful = (m_gas != m_gasUsed); } +void ExecutionFramework::waitForTransaction(std::string const& _txHash) const +{ + for (int polls = 0; polls < 3000; polls++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + auto pendingBlock = m_rpc.eth_getBlockByNumber("pending", false); + + if (!pendingBlock["transactions"].empty()) + { + BOOST_REQUIRE_EQUAL(pendingBlock["transactions"][0].asString(), _txHash); + return; + } + + if (polls == 200) + { + cerr << "Note: Already used 200 iterations while waiting for transaction confirmation. Issuing an eth_flush request." << endl; + m_rpc.rpcCall("eth_flush"); + } + } +} + void ExecutionFramework::sendEther(Address const& _to, u256 const& _value) { RPCSession::TransactionData d; diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 4a42382d0845..11e364c59248 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -247,6 +247,7 @@ class ExecutionFramework protected: void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0); void sendEther(Address const& _to, u256 const& _value); + void waitForTransaction(std::string const& _txHash) const; size_t currentTimestamp(); size_t blockTimestamp(u256 _number); diff --git a/test/Options.cpp b/test/Options.cpp index 26a83d16f301..3294dad1ed87 100644 --- a/test/Options.cpp +++ b/test/Options.cpp @@ -52,6 +52,7 @@ Options::Options() options.add_options() ("optimize", po::bool_switch(&optimize), "enables optimization") + ("optimize-yul", po::bool_switch(&optimizeYul), "enables Yul optimization") ("abiencoderv2", po::bool_switch(&useABIEncoderV2), "enables abi encoder v2") ("show-messages", po::bool_switch(&showMessages), "enables message output"); diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index deb64dd80d14..60de0ffb73e2 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -351,7 +351,12 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector const& } if (!result.isMember("result") || result["result"].isNull()) - BOOST_FAIL("Missing result for JSON-RPC call: " + result.toStyledString()); + BOOST_FAIL( + "Missing result for JSON-RPC call: " + + result.toStyledString() + + "\nRequest was " + + request + ); return result["result"]; } diff --git a/test/TestCase.cpp b/test/TestCase.cpp index 6f3c92a53f8b..b4260e436c7b 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -17,76 +17,80 @@ #include +#include + #include #include #include +#include +#include #include +#include + using namespace dev; using namespace solidity; using namespace dev::solidity::test; using namespace std; +void TestCase::printUpdatedSettings(ostream& _stream, const string& _linePrefix, const bool) +{ + if (m_validatedSettings.empty()) + return; + + _stream << _linePrefix << "// ====" << endl; + for (auto const& setting: m_validatedSettings) + _stream << _linePrefix << "// " << setting.first << ": " << setting.second << endl; +} + bool TestCase::isTestFilename(boost::filesystem::path const& _filename) { string extension = _filename.extension().string(); return (extension == ".sol" || extension == ".yul") && !boost::starts_with(_filename.string(), "~") && - !boost::starts_with(_filename.string(), "."); + !boost::starts_with(_filename.string(), "."); } -bool TestCase::supportedForEVMVersion(langutil::EVMVersion _evmVersion) const +bool TestCase::validateSettings(langutil::EVMVersion) { - return boost::algorithm::none_of(m_evmVersionRules, [&](auto const& rule) { return !rule(_evmVersion); }); + if (!m_settings.empty()) + throw runtime_error( + "Unknown setting(s): " + + joinHumanReadable(m_settings | boost::adaptors::map_keys) + ); + return true; } -string TestCase::parseSource(istream& _stream) +string TestCase::parseSourceAndSettings(istream& _stream) { string source; string line; + static string const settingsDelimiter("// ===="); static string const delimiter("// ----"); - static string const evmVersion("// EVMVersion: "); - bool isTop = true; + bool sourcePart = true; while (getline(_stream, line)) + { if (boost::algorithm::starts_with(line, delimiter)) break; - else - { - if (isTop && boost::algorithm::starts_with(line, evmVersion)) - { - string versionString = line.substr(evmVersion.size() + 1); - auto version = langutil::EVMVersion::fromString(versionString); - if (!version) - throw runtime_error("Invalid EVM version: \"" + versionString + "\""); - switch (line.at(evmVersion.size())) - { - case '>': - m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) { - return version < _version; - }); - break; - case '<': - m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) { - return _version < version; - }); - break; - case '=': - m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) { - return _version == version; - }); - break; - case '!': - m_evmVersionRules.emplace_back([version](langutil::EVMVersion _version) { - return !(_version == version); - }); - break; - } - } - else - isTop = false; + else if (boost::algorithm::starts_with(line, settingsDelimiter)) + sourcePart = false; + else if (sourcePart) source += line + "\n"; + else if (boost::algorithm::starts_with(line, "// ")) + { + size_t colon = line.find(':'); + if (colon == string::npos) + throw runtime_error(string("Expected \":\" inside setting.")); + string key = line.substr(3, colon - 3); + string value = line.substr(colon + 1); + boost::algorithm::trim(key); + boost::algorithm::trim(value); + m_settings[key] = value; } + else + throw runtime_error(string("Expected \"//\" or \"// ---\" to terminate settings and source.")); + } return source; } @@ -96,3 +100,51 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu throw runtime_error(string("Invalid test expectation. Expected: \"") + _c + "\"."); ++_it; } + +bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVersion) +{ + if (!m_settings.count("EVMVersion")) + return true; + + string versionString = m_settings["EVMVersion"]; + m_validatedSettings["EVMVersion"] = versionString; + m_settings.erase("EVMVersion"); + + if (!TestCase::validateSettings(_evmVersion)) + return false; + + if (versionString.empty()) + return true; + + string comparator; + size_t versionBegin = 0; + for (auto character: versionString) + if (!isalpha(character)) + { + comparator += character; + versionBegin++; + } + else + break; + + versionString = versionString.substr(versionBegin); + boost::optional version = langutil::EVMVersion::fromString(versionString); + if (!version) + throw runtime_error("Invalid EVM version: \"" + versionString + "\""); + + if (comparator == ">") + return _evmVersion > version; + else if (comparator == ">=") + return _evmVersion >= version; + else if (comparator == "<") + return _evmVersion < version; + else if (comparator == "<=") + return _evmVersion <= version; + else if (comparator == "=") + return _evmVersion == version; + else if (comparator == "!") + return !(_evmVersion == version); + else + throw runtime_error("Invalid EVM comparator: \"" + comparator + "\""); + return false; // not reached +} diff --git a/test/TestCase.h b/test/TestCase.h index 86a109682ff2..8f40725df567 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace dev { @@ -42,7 +43,9 @@ namespace test } \ while (false) -/** Common superclass of SyntaxTest and SemanticsTest. */ +/** + * Common superclass of anything that can be run via isoltest. + */ class TestCase { public: @@ -68,17 +71,23 @@ class TestCase /// If @arg _formatted is true, color-coding may be used to indicate /// error locations in the contract, if applicable. virtual void printSource(std::ostream &_stream, std::string const &_linePrefix = "", bool const _formatted = false) const = 0; + /// Outputs the updated settings. + virtual void printUpdatedSettings(std::ostream &_stream, std::string const &_linePrefix = "", bool const _formatted = false); /// Outputs test expectations to @arg _stream that match the actual results of the test. /// Each line of output is prefixed with @arg _linePrefix. virtual void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const = 0; static bool isTestFilename(boost::filesystem::path const& _filename); - /// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise. - bool supportedForEVMVersion(langutil::EVMVersion _evmVersion) const; + /// Validates the settings, i.e. moves them from m_settings to m_validatedSettings. + /// Throws a runtime exception if any setting is left at this class (i.e. unknown setting). + /// Returns true, if the test case is supported in the current environment and false + /// otherwise which causes this test to be skipped. + /// This might check e.g. for restrictions on the EVM version. + virtual bool validateSettings(langutil::EVMVersion /*_evmVersion*/); protected: - std::string parseSource(std::istream& _file); + std::string parseSourceAndSettings(std::istream& _file); static void expect(std::string::iterator& _it, std::string::iterator _end, std::string::value_type _c); template @@ -94,10 +103,19 @@ class TestCase while (_it != _end && *_it == '/') ++_it; } -private: - std::vector> m_evmVersionRules; + + /// Parsed settings. + std::map m_settings; + /// Updated settings after validation. + std::map m_validatedSettings; }; +class EVMVersionRestrictedTestCase: public TestCase +{ +public: + /// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise. + bool validateSettings(langutil::EVMVersion _evmVersion) override; +}; } } } diff --git a/test/boostTest.cpp b/test/boostTest.cpp index d9bf9c3fc48a..609274e9fe49 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -105,7 +105,7 @@ int registerTests( { stringstream errorStream; auto testCase = _testCaseCreator(config); - if (testCase->supportedForEVMVersion(dev::test::Options::get().evmVersion())) + if (testCase->validateSettings(dev::test::Options::get().evmVersion())) if (!testCase->run(errorStream)) BOOST_ERROR("Test expectation mismatch.\n" + errorStream.str()); } diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 37c319e7d07c..a7a9f8d955a0 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -93,6 +93,27 @@ function compileFull() fi } +function ask_expectation_update() +{ + local newExpectation="${1}" + local expectationFile="${2}" + while true; + do + set +e + read -t10 -p "(u)pdate expectation/(q)uit? " + if [ $? -gt 128 ]; + then + echo -e "\nUser input timed out." + exit 1 + fi + set -e + case $REPLY in + u* ) echo "$newExpectation" > $expectationFile ; break;; + q* ) exit 1;; + esac + done +} + # General helper function for testing SOLC behaviour, based on file name, compile opts, exit code, stdout and stderr. # An failure is expected. function test_solc_behaviour() @@ -103,17 +124,22 @@ function test_solc_behaviour() local stdout_expected="${4}" local exit_code_expected="${5}" local stderr_expected="${6}" + local stdout_expectation_file="${7}" # the file to write to when user chooses to update stdout expectation + local stderr_expectation_file="${8}" # the file to write to when user chooses to update stderr expectation local stdout_path=`mktemp` local stderr_path=`mktemp` + + trap "rm -f $stdout_path $stderr_path" EXIT + if [[ "$exit_code_expected" = "" ]]; then exit_code_expected="0"; fi + local solc_command="$SOLC ${filename} ${solc_args}" + if [[ -n "$solc_stdin" ]]; then solc_command+=" <$solc_stdin" ; fi + if [[ -n "$stdout_path" ]]; then solc_command+=" 1>$stdout_path"; fi + if [[ -n "$stderr_path" ]]; then solc_command+=" 2>$stderr_path"; fi + set +e - if [[ "$solc_stdin" = "" ]] - then - "$SOLC" "${filename}" ${solc_args} 1>$stdout_path 2>$stderr_path - else - "$SOLC" "${filename}" ${solc_args} <$solc_stdin 1>$stdout_path 2>$stderr_path - fi + eval "$solc_command" exitCode=$? set -e @@ -133,37 +159,44 @@ function test_solc_behaviour() if [[ $exitCode -ne "$exit_code_expected" ]] then printError "Incorrect exit code. Expected $exit_code_expected but got $exitCode." - rm -f $stdout_path $stderr_path exit 1 fi if [[ "$(cat $stdout_path)" != "${stdout_expected}" ]] then printError "Incorrect output on stdout received. Expected:" - echo -e "${stdout_expected}" + echo "${stdout_expected}" printError "But got:" cat $stdout_path - printError "When running $SOLC ${filename} ${solc_args} <$solc_stdin" - rm -f $stdout_path $stderr_path - exit 1 + printError "When running $solc_command" + + if [ -n "$stdout_expectation_file" ] + then + ask_expectation_update "$(cat $stdout_path)" "$stdout_expectation_file" + else + exit 1 + fi fi if [[ "$(cat $stderr_path)" != "${stderr_expected}" ]] then printError "Incorrect output on stderr received. Expected:" - echo -e "${stderr_expected}" + echo "${stderr_expected}" printError "But got:" cat $stderr_path - printError "When running $SOLC ${filename} ${solc_args} <$solc_stdin" - rm -f $stdout_path $stderr_path - exit 1 - fi + printError "When running $solc_command" - rm -f $stdout_path $stderr_path + if [ -n "$stderr_expectation_file" ] + then + ask_expectation_update "$(cat $stderr_path)" "$stderr_expectation_file" + else + exit 1 + fi + fi } @@ -210,14 +243,14 @@ printTask "Testing unknown options..." printTask "Testing passing files that are not found..." -test_solc_behaviour "file_not_found.sol" "" "" "" 1 "\"file_not_found.sol\" is not found." +test_solc_behaviour "file_not_found.sol" "" "" "" 1 "\"file_not_found.sol\" is not found." "" "" printTask "Testing passing files that are not files..." -test_solc_behaviour "." "" "" "" 1 "\".\" is not a valid file." +test_solc_behaviour "." "" "" "" 1 "\".\" is not a valid file." "" "" printTask "Testing passing empty remappings..." -test_solc_behaviour "${0}" "=/some/remapping/target" "" "" 1 "Invalid remapping: \"=/some/remapping/target\"." -test_solc_behaviour "${0}" "ctx:=/some/remapping/target" "" "" 1 "Invalid remapping: \"ctx:=/some/remapping/target\"." +test_solc_behaviour "${0}" "=/some/remapping/target" "" "" 1 "Invalid remapping: \"=/some/remapping/target\"." "" "" +test_solc_behaviour "${0}" "ctx:=/some/remapping/target" "" "" 1 "Invalid remapping: \"ctx:=/some/remapping/target\"." "" "" printTask "Running general commandline tests..." ( @@ -228,18 +261,28 @@ printTask "Running general commandline tests..." then inputFile="" stdin="${tdir}/input.json" - stdout=$(cat ${tdir}/output.json 2>/dev/null || true) + stdout="$(cat ${tdir}/output.json 2>/dev/null || true)" + stdoutExpectationFile="$(pwd)/${tdir}/output.json" args="--standard-json "$(cat ${tdir}/args 2>/dev/null || true) else inputFile="${tdir}input.sol" stdin="" - stdout=$(cat ${tdir}/output 2>/dev/null || true) + stdout="$(cat ${tdir}/output 2>/dev/null || true)" + stdoutExpectationFile="$(pwd)/${tdir}/output" args=$(cat ${tdir}/args 2>/dev/null || true) fi exitCode=$(cat ${tdir}/exit 2>/dev/null || true) - err=$(cat ${tdir}/err 2>/dev/null || true) + err="$(cat ${tdir}/err 2>/dev/null || true)" + stderrExpectationFile="${tdir}/err" printTask " - ${tdir}" - test_solc_behaviour "$inputFile" "$args" "$stdin" "$stdout" "$exitCode" "$err" + test_solc_behaviour "$inputFile" \ + "$args" \ + "$stdin" \ + "$stdout" \ + "$exitCode" \ + "$err" \ + "$stdoutExpectationFile" \ + "$stderrExpectationFile" done ) @@ -343,7 +386,7 @@ printTask "Testing assemble, yul, strict-assembly and optimize..." # while it results in empty binary representation with optimizations turned on. test_solc_assembly_output "{ let x:u256 := 0:u256 }" "{ let x:u256 := 0:u256 }" "--yul" test_solc_assembly_output "{ let x := 0 }" "{ let x := 0 }" "--strict-assembly" - test_solc_assembly_output "{ let x := 0 }" "{ }" "--strict-assembly --optimize" + test_solc_assembly_output "{ let x := 0 }" "{ { } }" "--strict-assembly --optimize" ) @@ -392,26 +435,9 @@ SOLTMPDIR=$(mktemp -d) cd "$SOLTMPDIR" "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/ "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/ docs - for f in *.sol - do - set +e - "$REPO_ROOT"/build/test/tools/solfuzzer --quiet < "$f" - if [ $? -ne 0 ] - then - printError "Fuzzer failed on:" - cat "$f" - exit 1 - fi - "$REPO_ROOT"/build/test/tools/solfuzzer --without-optimizer --quiet < "$f" - if [ $? -ne 0 ] - then - printError "Fuzzer (without optimizer) failed on:" - cat "$f" - exit 1 - fi - set -e - done + echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/build/test/tools/solfuzzer --quiet --input-files + echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/build/test/tools/solfuzzer --without-optimizer --quiet --input-files ) rm -rf "$SOLTMPDIR" diff --git a/test/cmdlineTests/gas_test_abiv2/err b/test/cmdlineTests/gas_test_abiv2/err index 012ed14fccd5..b654d45edf4b 100644 --- a/test/cmdlineTests/gas_test_abiv2/err +++ b/test/cmdlineTests/gas_test_abiv2/err @@ -1,3 +1,3 @@ gas_test_abiv2/input.sol:2:1: Warning: Experimental features are turned on. Do not use experimental features on live deployments. pragma experimental ABIEncoderV2; -^-------------------------------^ \ No newline at end of file +^-------------------------------^ diff --git a/test/cmdlineTests/gas_test_abiv2/output b/test/cmdlineTests/gas_test_abiv2/output index f555b7ffdb90..609d6669f113 100644 --- a/test/cmdlineTests/gas_test_abiv2/output +++ b/test/cmdlineTests/gas_test_abiv2/output @@ -2,11 +2,11 @@ ======= gas_test_abiv2/input.sol:C ======= Gas estimation: construction: - 1140 + 1096600 = 1097740 + 1160 + 1119000 = 1120160 external: a(): 530 - b(uint256): 1118 - f1(uint256): 586 + b(uint256): infinite + f1(uint256): infinite f2(uint256[],string[],uint16,address): infinite f3(uint16[],string[],uint16,address): infinite f4(uint32[],string[12],bytes[2][],address): infinite diff --git a/test/cmdlineTests/gas_test_abiv2_optimize_yul/output b/test/cmdlineTests/gas_test_abiv2_optimize_yul/output index 8a77186cb309..9c8ddfb3d486 100644 --- a/test/cmdlineTests/gas_test_abiv2_optimize_yul/output +++ b/test/cmdlineTests/gas_test_abiv2_optimize_yul/output @@ -2,7 +2,7 @@ ======= gas_test_abiv2_optimize_yul/input.sol:C ======= Gas estimation: construction: - 651 + 617200 = 617851 + 645 + 608800 = 609445 external: a(): 429 b(uint256): 884 diff --git a/test/cmdlineTests/object_compiler/output b/test/cmdlineTests/object_compiler/output index 51830a0c0fcf..f257721b963b 100644 --- a/test/cmdlineTests/object_compiler/output +++ b/test/cmdlineTests/object_compiler/output @@ -4,15 +4,19 @@ Pretty printed source: object "MyContract" { code { - sstore(0, caller()) - let _1 := datasize("Runtime") - datacopy(0, dataoffset("Runtime"), _1) - return(0, _1) + { + sstore(0, caller()) + let _1 := datasize("Runtime") + datacopy(0, dataoffset("Runtime"), _1) + return(0, _1) + } } object "Runtime" { code { - mstore(0, sload(0)) - return(0, 0x20) + { + mstore(0, sload(0)) + return(0, 0x20) + } } } } @@ -61,4 +65,3 @@ sub_0: assembly { /* "object_compiler/input.sol":407:422 */ return } - diff --git a/test/cmdlineTests/standard_irOptimized_requested/input.json b/test/cmdlineTests/standard_irOptimized_requested/input.json new file mode 100644 index 000000000000..96ea078bdcf7 --- /dev/null +++ b/test/cmdlineTests/standard_irOptimized_requested/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["irOptimized"] } + } + } +} diff --git a/test/cmdlineTests/standard_irOptimized_requested/output.json b/test/cmdlineTests/standard_irOptimized_requested/output.json new file mode 100644 index 000000000000..5fb828a66b4f --- /dev/null +++ b/test/cmdlineTests/standard_irOptimized_requested/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue()\n {\n revert(0, 0)\n }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default {\n }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0)\n {\n revert(0, 0)\n }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n {\n tail := add(headStart, 0)\n }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))\n {\n revert(0, 0)\n }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n }\n function shift_right_224_unsigned(value) -> newValue\n {\n newValue := shr(224, value)\n }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_ir_requested/input.json b/test/cmdlineTests/standard_ir_requested/input.json new file mode 100644 index 000000000000..37404ddba9ef --- /dev/null +++ b/test/cmdlineTests/standard_ir_requested/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json new file mode 100644 index 000000000000..ddca38e621d1 --- /dev/null +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\nfunction fun_f_5() {\n\n}\n\n\t\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\t\tnewValue := shr(224, value)\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_missing_key_useLiteralContent/output.json b/test/cmdlineTests/standard_missing_key_useLiteralContent/output.json index 14c325725838..59b90c8cc98e 100644 --- a/test/cmdlineTests/standard_missing_key_useLiteralContent/output.json +++ b/test/cmdlineTests/standard_missing_key_useLiteralContent/output.json @@ -1 +1 @@ -{"sources":{"A":{"id":0}}} \ No newline at end of file +{"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_optimizer_invalid_details/output.json b/test/cmdlineTests/standard_optimizer_invalid_details/output.json index 8be28f5b362d..20e62bc820b0 100644 --- a/test/cmdlineTests/standard_optimizer_invalid_details/output.json +++ b/test/cmdlineTests/standard_optimizer_invalid_details/output.json @@ -1 +1 @@ -{"errors":[{"component":"general","formattedMessage":"Unknown key \"notThere\"","message":"Unknown key \"notThere\"","severity":"error","type":"JSONError"}]} \ No newline at end of file +{"errors":[{"component":"general","formattedMessage":"Unknown key \"notThere\"","message":"Unknown key \"notThere\"","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input/output.json b/test/cmdlineTests/standard_wrong_type_auxiliary_input/output.json index 046cb6d99cc8..fe33dcd06c47 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input/output.json +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input/output.json @@ -1,2 +1 @@ {"errors":[{"component":"general","formattedMessage":"\"auxiliaryInput\" must be an object","message":"\"auxiliaryInput\" must be an object","severity":"error","type":"JSONError"}]} - diff --git a/test/cmdlineTests/standard_yul_multiple_files/output.json b/test/cmdlineTests/standard_yul_multiple_files/output.json index b748cd50f8de..2f2da9310d00 100644 --- a/test/cmdlineTests/standard_yul_multiple_files/output.json +++ b/test/cmdlineTests/standard_yul_multiple_files/output.json @@ -1 +1 @@ -{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]} \ No newline at end of file +{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_yul_multiple_files_selected/output.json b/test/cmdlineTests/standard_yul_multiple_files_selected/output.json index b748cd50f8de..2f2da9310d00 100644 --- a/test/cmdlineTests/standard_yul_multiple_files_selected/output.json +++ b/test/cmdlineTests/standard_yul_multiple_files_selected/output.json @@ -1 +1 @@ -{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]} \ No newline at end of file +{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_yul_optimized/output.json b/test/cmdlineTests/standard_yul_optimized/output.json index 80b764ece5bb..fe4992a9fe92 100644 --- a/test/cmdlineTests/standard_yul_optimized/output.json +++ b/test/cmdlineTests/standard_yul_optimized/output.json @@ -1 +1 @@ -{"contracts":{"A":{"object":{"evm":{"assembly":" /* \"A\":17:18 */\n 0x00\n 0x00\n /* \"A\":11:19 */\n mload\n /* \"A\":20:40 */\n sstore\n","bytecode":{"linkReferences":{},"object":"600060005155","opcodes":"PUSH1 0x0 PUSH1 0x0 MLOAD SSTORE ","sourceMap":""}},"ir":"object \"object\" {\n code {\n let x := mload(0)\n sstore(add(x, 0), 0)\n }\n}\n","irOptimized":"object \"object\" {\n code {\n sstore(mload(0), 0)\n }\n}\n"}}},"errors":[{"component":"general","formattedMessage":"Yul is still experimental. Please use the output with care.","message":"Yul is still experimental. Please use the output with care.","severity":"warning","type":"Warning"}]} +{"contracts":{"A":{"object":{"evm":{"assembly":" /* \"A\":17:18 */\n 0x00\n 0x00\n /* \"A\":11:19 */\n mload\n /* \"A\":20:40 */\n sstore\n","bytecode":{"linkReferences":{},"object":"600060005155","opcodes":"PUSH1 0x0 PUSH1 0x0 MLOAD SSTORE ","sourceMap":""}},"ir":"object \"object\" {\n code {\n let x := mload(0)\n sstore(add(x, 0), 0)\n }\n}\n","irOptimized":"object \"object\" {\n code {\n {\n sstore(mload(0), 0)\n }\n }\n}\n"}}},"errors":[{"component":"general","formattedMessage":"Yul is still experimental. Please use the output with care.","message":"Yul is still experimental. Please use the output with care.","severity":"warning","type":"Warning"}]} diff --git a/test/cmdlineTests/too_long_line_multiline.sol.err b/test/cmdlineTests/too_long_line_multiline/err similarity index 53% rename from test/cmdlineTests/too_long_line_multiline.sol.err rename to test/cmdlineTests/too_long_line_multiline/err index d7412ffeba6c..c00e43fd7303 100644 --- a/test/cmdlineTests/too_long_line_multiline.sol.err +++ b/test/cmdlineTests/too_long_line_multiline/err @@ -1,6 +1,6 @@ -too_long_line_multiline.sol:2:5: Error: No visibility specified. Did you intend to add "public"? +too_long_line_multiline/input.sol:2:5: Error: No visibility specified. Did you intend to add "public"? function f() returns (byte _b, byte ... _b7, bytes22 _b22, bytes32 _b32) { ^ (Relevant source part starts here and spans across multiple lines). -too_long_line_multiline.sol:1:1: Warning: Source file does not specify required compiler version! +too_long_line_multiline/input.sol:1:1: Warning: Source file does not specify required compiler version! contract C { ^ (Relevant source part starts here and spans across multiple lines). diff --git a/test/cmdlineTests/too_long_line_multiline.sol.exit b/test/cmdlineTests/too_long_line_multiline/exit similarity index 100% rename from test/cmdlineTests/too_long_line_multiline.sol.exit rename to test/cmdlineTests/too_long_line_multiline/exit diff --git a/test/cmdlineTests/too_long_line_multiline.sol b/test/cmdlineTests/too_long_line_multiline/input.sol similarity index 100% rename from test/cmdlineTests/too_long_line_multiline.sol rename to test/cmdlineTests/too_long_line_multiline/input.sol diff --git a/test/cmdlineTests/yul_stack_opt/output b/test/cmdlineTests/yul_stack_opt/output index c8e10fe86cfc..0f987c7e6c8d 100644 --- a/test/cmdlineTests/yul_stack_opt/output +++ b/test/cmdlineTests/yul_stack_opt/output @@ -4,9 +4,11 @@ Pretty printed source: object "object" { code { - let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() - let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() - sstore(a1, a2) + { + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) + } function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 { let a := 1 @@ -199,4 +201,3 @@ tag_2: swap16 jump tag_4: - diff --git a/test/cmdlineTests/yul_stack_opt_disabled/err b/test/cmdlineTests/yul_stack_opt_disabled/err index 392200019439..beb36e749eed 100644 --- a/test/cmdlineTests/yul_stack_opt_disabled/err +++ b/test/cmdlineTests/yul_stack_opt_disabled/err @@ -3,4 +3,3 @@ Exception while assembling: Dynamic exception type: std::exception::what: Variable a1 is 17 slot(s) too deep inside the stack. [dev::tag_comment*] = Variable a1 is 17 slot(s) too deep inside the stack. - diff --git a/test/cmdlineTests/yul_stack_opt_disabled/output b/test/cmdlineTests/yul_stack_opt_disabled/output index c9e3d5078f78..f6fa373dff56 100644 --- a/test/cmdlineTests/yul_stack_opt_disabled/output +++ b/test/cmdlineTests/yul_stack_opt_disabled/output @@ -28,4 +28,3 @@ object "object" { sstore(a1, a2) } } - diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index 9fe02e463918..d0cbb9956b6f 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -464,7 +464,8 @@ BOOST_AUTO_TEST_CASE(creation) { deployWallet(200); BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(m_sender, h256::AlignRight)) == encodeArgs(true)); - BOOST_REQUIRE(callContractFunction("isOwner(address)", ~h256(m_sender, h256::AlignRight)) == encodeArgs(false)); + bool v2 = dev::test::Options::get().useABIEncoderV2; + BOOST_REQUIRE(callContractFunction("isOwner(address)", ~h256(m_sender, h256::AlignRight)) == (v2 ? encodeArgs() : encodeArgs(false))); } BOOST_AUTO_TEST_CASE(add_owners) diff --git a/test/externalTests.sh b/test/externalTests.sh index 0ee492d9cee6..482819458f00 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -35,81 +35,14 @@ then fi SOLJSON="$1" +REPO_ROOT="$(dirname "$0")" -function test_truffle -{ - name="$1" - repo="$2" - branch="$3" - echo "Running $name tests..." - DIR=$(mktemp -d) - ( - cd "$DIR" - git clone --depth 1 -b v0.5.0 https://github.com/ethereum/solc-js.git solc - SOLCVERSION="UNDEFINED" +source test/externalTests/common.sh - cd solc - npm install - cp "$SOLJSON" soljson.js - SOLCVERSION=$(./solcjs --version) - cd .. - echo "Using solcjs version $SOLCVERSION" +printTask "Running external tests..." - if [ -n "$branch" ] - then - echo "Cloning $branch of $repo..." - git clone --depth 1 "$repo" -b "$branch" "$DIR/ext" - else - echo "Cloning $repo..." - git clone --depth 1 "$repo" "$DIR/ext" - fi - cd ext - echo "Current commit hash: `git rev-parse HEAD`" - npm ci - # Replace solc package by v0.5.0 - for d in node_modules node_modules/truffle/node_modules - do - ( - if [ -d "$d" ] - then - cd $d - rm -rf solc - git clone --depth 1 -b v0.5.0 https://github.com/ethereum/solc-js.git solc - cp "$SOLJSON" solc/soljson.js - fi - ) - done - if [ "$name" == "Zeppelin" -o "$name" == "Gnosis" ]; then - echo "Replaced fixed-version pragmas..." - # Replace fixed-version pragmas in Gnosis (part of Consensys best practice) - find contracts test -name '*.sol' -type f -print0 | xargs -0 sed -i -e 's/pragma solidity [\^0-9\.]*/pragma solidity >=0.0/' - fi - # Change "compileStandard" to "compile" (needed for pre-5.x Truffle) - sed -i s/solc.compileStandard/solc.compile/ "node_modules/truffle/build/cli.bundled.js" - # Force usage of correct solidity binary (only works with Truffle 5.x) - cat >> truffle*.js <> truffle*.js - npx truffle compile - echo "Verify that the correct version ($SOLCVERSION) of the compiler was used to compile the contracts..." - grep -e "$SOLCVERSION" -r build/contracts > /dev/null - npm run test - done - ) - rm -rf "$DIR" -} - -# Since Zeppelin 2.1.1 it supports Solidity 0.5.0. -test_truffle Zeppelin https://github.com/OpenZeppelin/openzeppelin-solidity.git master +$REPO_ROOT/externalTests/zeppelin.sh "$SOLJSON" +$REPO_ROOT/externalTests/gnosis.sh "$SOLJSON" # Disabled temporarily as it needs to be updated to latest Truffle first. #test_truffle Gnosis https://github.com/axic/pm-contracts.git solidity-050 - -# Disabled temporarily because it is incompatible with petersburg EVM and -# there is no easy way to set the EVM version in truffle pre 5.0. -#test_truffle GnosisSafe https://github.com/gnosis/safe-contracts.git development diff --git a/test/externalTests/common.sh b/test/externalTests/common.sh new file mode 100644 index 000000000000..0eade0fad1b3 --- /dev/null +++ b/test/externalTests/common.sh @@ -0,0 +1,249 @@ +#!/usr/bin/env bash + +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2019 solidity contributors. +#------------------------------------------------------------------------------ +set -e + +if [ "$CIRCLECI" ] +then + function printTask() { echo ""; echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } + function printError() { echo ""; echo "$(tput setaf 1)$1$(tput setaf 7)"; } + function printLog() { echo "$(tput setaf 3)$1$(tput setaf 7)"; } +else + function printTask() { echo ""; echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } + function printError() { echo ""; echo "$(tput setaf 1)$1$(tput sgr0)"; } + function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; } +fi + +function verify_input +{ + if [ ! -f "$1" ]; then + printError "Usage: $0 " + exit 1 + fi +} + +function setup_solcjs +{ + local dir="$1" + local soljson="$2" + + cd "$dir" + printLog "Setting up solc-js..." + git clone --depth 1 -b v0.5.0 https://github.com/ethereum/solc-js.git solc + + cd solc + npm install + cp "$soljson" soljson.js + SOLCVERSION=$(./solcjs --version) + printLog "Using solcjs version $SOLCVERSION" + cd .. +} + +function download_project +{ + local repo="$1" + local branch="$2" + local dir="$3" + + printLog "Cloning $branch of $repo..." + git clone --depth 1 "$repo" -b "$branch" "$dir/ext" + cd ext + echo "Current commit hash: `git rev-parse HEAD`" +} + +function setup +{ + local repo="$1" + local branch="$2" + + setup_solcjs "$DIR" "$SOLJSON" + download_project "$repo" "$branch" "$DIR" + + replace_version_pragmas +} + +function replace_version_pragmas +{ + # Replace fixed-version pragmas in Gnosis (part of Consensys best practice) + printLog "Replacing fixed-version pragmas..." + find contracts test -name '*.sol' -type f -print0 | xargs -0 sed -i -e 's/pragma solidity [\^0-9\.]*/pragma solidity >=0.0/' +} + +function replace_libsolc_call +{ + # Change "compileStandard" to "compile" (needed for pre-5.x Truffle) + printLog "Replacing libsolc compile call in Truffle..." + sed -i s/solc.compileStandard/solc.compile/ "node_modules/truffle/build/cli.bundled.js" +} + +function find_truffle_config +{ + local config_file="truffle.js" + local alt_config_file="truffle-config.js" + + if [ ! -f "$config_file" ] && [ ! -f "$alt_config_file" ]; then + printError "No matching Truffle config found." + fi + if [ ! -f "$config_file" ]; then + config_file=alt_config_file + fi + echo "$config_file" +} + +function force_solc_truffle_modules +{ + # Replace solc package by v0.5.0 and then overwrite with current version. + printLog "Forcing solc version for all Truffle modules..." + for d in node_modules node_modules/truffle/node_modules + do + ( + if [ -d "$d" ]; then + cd $d + rm -rf solc + git clone --depth 1 -b v0.5.0 https://github.com/ethereum/solc-js.git solc + cp "$1" solc/soljson.js + fi + ) + done +} + +function force_solc +{ + local config_file="$1" + local dir="$2" + local soljson="$3" + + force_solc_truffle_modules "$soljson" + + printLog "Forcing solc version..." + cat >> "$config_file" <> "$config_file" + echo "module.exports['compilers']['solc']['settings'] = { optimizer: $settings, evmVersion: \"$evmVersion\" };" >> "$config_file" +} + +function force_abi_v2 +{ + # Add "pragma experimental ABIEncoderV2" to all files. + printLog "Forcibly enabling ABIEncodreV2..." + find contracts test -name '*.sol' -type f -print0 | \ + while IFS= read -r -d '' file + do + # Only add the pragma if it is not already there. + if grep -q -v 'pragma experimental ABIEncoderV2' "$file"; then + sed -i -e '1 i pragma experimental ABIEncoderV2;' "$file" + fi + done +} + +function verify_compiler_version +{ + local solc_version="$1" + + printLog "Verify that the correct version ($solc_version) of the compiler was used to compile the contracts..." + grep -e "$solc_version" -r build/contracts > /dev/null +} + +function clean +{ + rm -rf build || true +} + +function run_install +{ + local init_fn="$1" + printLog "Running install function..." + $init_fn +} + +function run_test +{ + local compile_fn="$1" + local test_fn="$2" + + force_solc "$CONFIG" "$DIR" "$SOLJSON" + + printLog "Checking optimizer level..." + if [ -z "$OPTIMIZER_LEVEL" ]; then + printError "Optimizer level not found. Please define OPTIMIZER_LEVEL=[1, 2, 3]" + exit 1 + fi + if [[ "$OPTIMIZER_LEVEL" == 1 ]]; then + declare -a optimizer_settings=("{ enabled: false }" "{ enabled: true }" "{ enabled: true, details: { yul: true } }") + fi + if [[ "$OPTIMIZER_LEVEL" == 2 ]]; then + declare -a optimizer_settings=("{ enabled: true }" "{ enabled: true, details: { yul: true } }") + fi + if [[ "$OPTIMIZER_LEVEL" == 3 ]]; then + declare -a optimizer_settings=("{ enabled: true, details: { yul: true } }") + fi + + for optimize in "${optimizer_settings[@]}" + do + clean + force_solc_settings "$CONFIG" "$optimize" "petersburg" + # Force ABIEncoderV2 in the last step. Has to be the last because code is modified. + [[ "$optimize" =~ yul ]] && force_abi_v2 + + printLog "Running compile function..." + $compile_fn + verify_compiler_version "$SOLCVERSION" + printLog "Running test function..." + $test_fn + done +} + +function external_test +{ + local name="$1" + local main_fn="$2" + + printTask "Testing $name..." + echo "===========================" + DIR=$(mktemp -d) + ( + if [ -z "$main_fn" ]; then + printError "Test main function not defined." + exit 1 + fi + $main_fn + ) + rm -rf "$DIR" + echo "Done." +} + diff --git a/test/externalTests/gnosis.sh b/test/externalTests/gnosis.sh new file mode 100755 index 000000000000..095207acc0e9 --- /dev/null +++ b/test/externalTests/gnosis.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2019 solidity contributors. +#------------------------------------------------------------------------------ +source test/externalTests/common.sh + +verify_input "$1" +SOLJSON="$1" + +function install_fn { npm install; } +function compile_fn { npx truffle compile; } +function test_fn { npm test; } + +function gnosis_safe_test +{ + OPTIMIZER_LEVEL=1 + setup https://github.com/gnosis/safe-contracts.git development + run_install install_fn + + CONFIG=$(find_truffle_config) + replace_libsolc_call + + run_test compile_fn test_fn +} + +external_test Gnosis-Safe gnosis_safe_test + diff --git a/test/externalTests/zeppelin.sh b/test/externalTests/zeppelin.sh new file mode 100755 index 000000000000..b85309921437 --- /dev/null +++ b/test/externalTests/zeppelin.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2019 solidity contributors. +#------------------------------------------------------------------------------ +source test/externalTests/common.sh + +verify_input "$1" +SOLJSON="$1" + +function install_fn { npm install; } +function compile_fn { npx truffle compile; } +function test_fn { npm run test; } + +function zeppelin_test +{ + OPTIMIZER_LEVEL=1 + setup https://github.com/OpenZeppelin/openzeppelin-solidity.git master + run_install install_fn + + CONFIG="truffle-config.js" + replace_libsolc_call + + run_test compile_fn test_fn +} + +external_test Zeppelin zeppelin_test diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 97632e348eaa..869e91c83bbb 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1180,6 +1180,84 @@ BOOST_AUTO_TEST_CASE(cse_sub_zero) }); } +BOOST_AUTO_TEST_CASE(cse_remove_redundant_shift_masking) +{ + if (!dev::test::Options::get().evmVersion().hasBitwiseShifting()) + return; + + for (int i = 1; i < 256; i++) + { + checkCSE({ + u256(boost::multiprecision::pow(u256(2), i)-1), + Instruction::CALLVALUE, + u256(256-i), + Instruction::SHR, + Instruction::AND + }, { + Instruction::CALLVALUE, + u256(256-i), + Instruction::SHR, + }); + + checkCSE({ + Instruction::CALLVALUE, + u256(256-i), + Instruction::SHR, + u256(boost::multiprecision::pow(u256(2), i)-1), + Instruction::AND + }, { + Instruction::CALLVALUE, + u256(256-i), + Instruction::SHR, + }); + } + + // Check that opt. does NOT trigger + for (int i = 1; i < 255; i++) + { + checkCSE({ + u256(boost::multiprecision::pow(u256(2), i)-1), + Instruction::CALLVALUE, + u256(255-i), + Instruction::SHR, + Instruction::AND + }, { // Opt. did some reordering + Instruction::CALLVALUE, + u256(255-i), + Instruction::SHR, + u256(boost::multiprecision::pow(u256(2), i)-1), + Instruction::AND + }); + + checkCSE({ + Instruction::CALLVALUE, + u256(255-i), + Instruction::SHR, + u256(boost::multiprecision::pow(u256(2), i)-1), + Instruction::AND + }, { // Opt. did some reordering + u256(boost::multiprecision::pow(u256(2), i)-1), + Instruction::CALLVALUE, + u256(255-i), + Instruction::SHR, + Instruction::AND + }); + } + + //(x >> (31*8)) & 0xffffffff + checkCSE({ + Instruction::CALLVALUE, + u256(31*8), + Instruction::SHR, + u256(0xffffffff), + Instruction::AND + }, { + Instruction::CALLVALUE, + u256(31*8), + Instruction::SHR + }); +} + BOOST_AUTO_TEST_CASE(cse_remove_unwanted_masking_of_address) { vector ops{ @@ -1250,6 +1328,48 @@ BOOST_AUTO_TEST_CASE(cse_remove_unwanted_masking_of_address) }); } +BOOST_AUTO_TEST_CASE(cse_replace_too_large_shift) +{ + if (!dev::test::Options::get().evmVersion().hasBitwiseShifting()) + return; + + checkCSE({ + Instruction::CALLVALUE, + u256(299), + Instruction::SHL + }, { + u256(0) + }); + + checkCSE({ + Instruction::CALLVALUE, + u256(299), + Instruction::SHR + }, { + u256(0) + }); + + checkCSE({ + Instruction::CALLVALUE, + u256(255), + Instruction::SHL + }, { + Instruction::CALLVALUE, + u256(255), + Instruction::SHL + }); + + checkCSE({ + Instruction::CALLVALUE, + u256(255), + Instruction::SHR + }, { + Instruction::CALLVALUE, + u256(255), + Instruction::SHR + }); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp index 52153eb8699b..8c31915466bd 100644 --- a/test/liblll/EndToEndTest.cpp +++ b/test/liblll/EndToEndTest.cpp @@ -29,6 +29,7 @@ #include using namespace std; +using namespace dev::test; namespace dev { diff --git a/test/liblll/ExecutionFramework.h b/test/liblll/ExecutionFramework.h index 2f54afa8a854..cbdd19887bf9 100644 --- a/test/liblll/ExecutionFramework.h +++ b/test/liblll/ExecutionFramework.h @@ -22,13 +22,12 @@ #pragma once -#include -#include "../ExecutionFramework.h" +#include #include -using namespace dev::test; +#include namespace dev { @@ -38,9 +37,8 @@ namespace lll namespace test { -class LLLExecutionFramework: public ExecutionFramework +class LLLExecutionFramework: public dev::test::ExecutionFramework { - public: LLLExecutionFramework(); @@ -49,7 +47,7 @@ class LLLExecutionFramework: public ExecutionFramework u256 const& _value = 0, std::string const& _contractName = "", bytes const& _arguments = bytes(), - std::map const& _libraryAddresses = std::map() + std::map const& _libraryAddresses = {} ) override { BOOST_REQUIRE(_contractName.empty()); diff --git a/test/liblll/LLL_ENS.cpp b/test/liblll/LLL_ENS.cpp index cfd6970ccac2..b79b78020e7c 100644 --- a/test/liblll/LLL_ENS.cpp +++ b/test/liblll/LLL_ENS.cpp @@ -29,6 +29,7 @@ using namespace std; using namespace dev::lll; +using namespace dev::test; namespace dev { diff --git a/test/liblll/LLL_ERC20.cpp b/test/liblll/LLL_ERC20.cpp index 6c6762dd0fd8..3777f4826c15 100644 --- a/test/liblll/LLL_ERC20.cpp +++ b/test/liblll/LLL_ERC20.cpp @@ -34,6 +34,7 @@ using namespace std; using namespace dev::lll; +using namespace dev::test; namespace dev { diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp index 4cb41c87b036..3d621c886019 100644 --- a/test/libsolidity/ABIDecoderTests.cpp +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -108,6 +108,7 @@ BOOST_AUTO_TEST_CASE(cleanup) } } )"; + bool newDecoder = dev::test::Options::get().useABIEncoderV2; BOTH_ENCODERS( compileAndRun(sourceCode); ABI_CHECK( @@ -117,10 +118,46 @@ BOOST_AUTO_TEST_CASE(cleanup) ABI_CHECK( callContractFunction( "f(uint16,int16,address,bytes3,bool)", - u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(4) + u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(1) + ), + newDecoder ? bytes{} : encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true) + ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0xffffff), u256(0), u256(0), string("bcd"), u256(1) + ), + newDecoder ? bytes{} : encodeArgs(u256(0xffff), u256(0), 0, string("bcd"), true) + ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0), u256(0x1ffff), u256(0), string("ab"), u256(1) + ), + newDecoder ? bytes{} : encodeArgs(u256(0), u256(-1), 0, string("ab"), true) + ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0), u256(0), u256(-1), string("ad"), u256(1) + ), + newDecoder ? bytes{} : encodeArgs(u256(0), u256(0), (u256(1) << 160) - 1, string("ad"), true) + ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0), u256(0), u256(0), string("abcd"), u256(1) ), - encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true) + newDecoder ? bytes{} : encodeArgs(u256(0), u256(0), 0, string("abc"), true) ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0), u256(0), u256(0), string("abc"), u256(2) + ), + newDecoder ? bytes{} : encodeArgs(u256(0), u256(0), 0, string("abc"), true) + ); + newDecoder = true; ) } @@ -506,7 +543,7 @@ BOOST_AUTO_TEST_CASE(short_input_bytes) ) } -BOOST_AUTO_TEST_CASE(cleanup_int_inside_arrays) +BOOST_AUTO_TEST_CASE(validation_int_inside_arrays) { string sourceCode = R"( contract C { @@ -521,15 +558,69 @@ BOOST_AUTO_TEST_CASE(cleanup_int_inside_arrays) ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, 7), encodeArgs(7)); ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, 7), encodeArgs(7)); ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256("0xffff"))); - ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256(-1))); - ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0x1ffff")), encodeArgs(u256("0xffff"))); - ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0x10fff")), encodeArgs(u256("0x0fff"))); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0xffff")), encodeArgs()); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0x1ffff")), encodeArgs()); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0x10fff")), encodeArgs()); ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 0), encodeArgs(u256(0))); ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 1), encodeArgs(u256(1))); ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 2), encodeArgs()); ) } +BOOST_AUTO_TEST_CASE(validation_function_type) +{ + string sourceCode = R"( + contract C { + function f(function () external) public pure returns (uint r) { r = 1; } + function g(function () external[] memory) public pure returns (uint r) { r = 2; } + function h(function () external[] calldata) external pure returns (uint r) { r = 3; } + function i(function () external[] calldata a) external pure returns (uint r) { a[0]; r = 4; } + } + )"; + bool newDecoder = dev::test::Options::get().useABIEncoderV2; + string validFun{"01234567890123456789abcd"}; + string invalidFun{"01234567890123456789abcdX"}; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(function)", validFun), encodeArgs(1)); + ABI_CHECK(callContractFunction("f(function)", invalidFun), newDecoder ? bytes{} : encodeArgs(1)); + ABI_CHECK(callContractFunction("g(function[])", 0x20, 1, validFun), encodeArgs(2)); + ABI_CHECK(callContractFunction("g(function[])", 0x20, 1, invalidFun), newDecoder ? bytes{} : encodeArgs(2)); + ABI_CHECK(callContractFunction("h(function[])", 0x20, 1, validFun), encodeArgs(3)); + // No failure because the data is not accessed. + ABI_CHECK(callContractFunction("h(function[])", 0x20, 1, invalidFun), encodeArgs(3)); + ABI_CHECK(callContractFunction("i(function[])", 0x20, 1, validFun), encodeArgs(4)); + ABI_CHECK(callContractFunction("i(function[])", 0x20, 1, invalidFun), newDecoder ? bytes{} : encodeArgs(4)); + newDecoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(validation_function_type_inside_struct) +{ + string sourceCode = R"( + contract C { + struct S { function () external x; } + function f(S memory) public pure returns (uint r) { r = 1; } + function g(S calldata) external pure returns (uint r) { r = 2; } + function h(S calldata s) external pure returns (uint r) { s.x; r = 3; } + } + )"; + string validFun{"01234567890123456789abcd"}; + string invalidFun{"01234567890123456789abcdX"}; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f((function))", validFun), encodeArgs(1)); + // Error because we copy to memory + ABI_CHECK(callContractFunction("f((function))", invalidFun), encodeArgs()); + ABI_CHECK(callContractFunction("g((function))", validFun), encodeArgs(2)); + // No error because x is not accessed. + ABI_CHECK(callContractFunction("g((function))", invalidFun), encodeArgs(2)); + ABI_CHECK(callContractFunction("h((function))", validFun), encodeArgs(3)); + // Error on access. + ABI_CHECK(callContractFunction("h((function))", invalidFun), encodeArgs()); + ) +} + BOOST_AUTO_TEST_CASE(storage_ptr) { string sourceCode = R"( @@ -583,7 +674,7 @@ BOOST_AUTO_TEST_CASE(struct_simple) ) } -BOOST_AUTO_TEST_CASE(struct_cleanup) +BOOST_AUTO_TEST_CASE(struct_validation) { string sourceCode = R"( contract C { @@ -597,11 +688,24 @@ BOOST_AUTO_TEST_CASE(struct_cleanup) } } )"; + u256 largeNeg("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01"); NEW_ENCODER( compileAndRun(sourceCode, 0, "C"); ABI_CHECK( - callContractFunction("f((int16,uint8,bytes2))", 0xff010, 0xff0002, "abcd"), - encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010"), 2, "ab") + callContractFunction("f((int16,uint8,bytes2))", largeNeg, 0xff, "ab"), + encodeArgs(largeNeg, 0xff, "ab") + ); + ABI_CHECK( + callContractFunction("f((int16,uint8,bytes2))", 0xff010, 0xff, "ab"), + encodeArgs() + ); + ABI_CHECK( + callContractFunction("f((int16,uint8,bytes2))", largeNeg, 0xff0002, "ab"), + encodeArgs() + ); + ABI_CHECK( + callContractFunction("f((int16,uint8,bytes2))", largeNeg, 0xff, "abcd"), + encodeArgs() ); ) } @@ -759,7 +863,6 @@ BOOST_AUTO_TEST_CASE(complex_struct) ) } - BOOST_AUTO_TEST_CASE(return_dynamic_types_cross_call_simple) { if (m_evmVersion == langutil::EVMVersion::homestead()) @@ -844,6 +947,24 @@ BOOST_AUTO_TEST_CASE(return_dynamic_types_cross_call_out_of_range) ) } +BOOST_AUTO_TEST_CASE(out_of_bounds_bool_value) +{ + string sourceCode = R"( + contract C { + function f(bool b) public pure returns (bool) { return b; } + } + )"; + bool newDecoder = dev::test::Options::get().useABIEncoderV2; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(bool)", true), encodeArgs(true)); + ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(false)); + ABI_CHECK(callContractFunctionNoEncoding("f(bool)", bytes(32, 0)), encodeArgs(0)); + ABI_CHECK(callContractFunctionNoEncoding("f(bool)", bytes(32, 0xff)), newDecoder ? encodeArgs() : encodeArgs(1)); + newDecoder = true; + ) +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index 605fbfc0ce23..bed667bc4615 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -92,13 +92,14 @@ bool ASTJSONTest::run(ostream& _stream, string const& _linePrefix, bool const _f { CompilerStack c; + StringMap sources; map sourceIndices; for (size_t i = 0; i < m_sources.size(); i++) { - c.addSource(m_sources[i].first, m_sources[i].second); + sources[m_sources[i].first] = m_sources[i].second; sourceIndices[m_sources[i].first] = i + 1; } - + c.setSources(sources); c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index abeecd327f93..faebfc3f5f37 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -47,21 +47,21 @@ AnalysisFramework::parseAnalyseAndReturnError( bool _allowMultipleErrors ) { - m_compiler.reset(); - m_compiler.addSource("", _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source); - m_compiler.setEVMVersion(dev::test::Options::get().evmVersion()); - if (!m_compiler.parse()) + compiler().reset(); + compiler().setSources({{"", _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source}}); + compiler().setEVMVersion(dev::test::Options::get().evmVersion()); + if (!compiler().parse()) { BOOST_FAIL("Parsing contract failed in analysis test suite:" + formatErrors()); } - m_compiler.analyze(); + compiler().analyze(); - ErrorList errors = filterErrors(m_compiler.errors(), _reportWarnings); + ErrorList errors = filterErrors(compiler().errors(), _reportWarnings); if (errors.size() > 1 && !_allowMultipleErrors) BOOST_FAIL("Multiple errors found: " + formatErrors()); - return make_pair(&m_compiler.ast(""), std::move(errors)); + return make_pair(&compiler().ast(""), std::move(errors)); } ErrorList AnalysisFramework::filterErrors(ErrorList const& _errorList, bool _includeWarnings) const @@ -118,17 +118,14 @@ ErrorList AnalysisFramework::expectError(std::string const& _source, bool _warni string AnalysisFramework::formatErrors() const { string message; - for (auto const& error: m_compiler.errors()) + for (auto const& error: compiler().errors()) message += formatError(*error); return message; } string AnalysisFramework::formatError(Error const& _error) const { - return SourceReferenceFormatter::formatExceptionInformation( - _error, - (_error.type() == Error::Type::Warning) ? "Warning" : "Error" - ); + return SourceReferenceFormatter::formatErrorInformation(_error); } ContractDefinition const* AnalysisFramework::retrieveContractByName(SourceUnit const& _source, string const& _name) diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h index 391a21dad062..cb590674fe2b 100644 --- a/test/libsolidity/AnalysisFramework.h +++ b/test/libsolidity/AnalysisFramework.h @@ -35,8 +35,8 @@ namespace solidity class Type; class FunctionType; -using TypePointer = std::shared_ptr; -using FunctionTypePointer = std::shared_ptr; +using TypePointer = Type const*; +using FunctionTypePointer = FunctionType const*; namespace test { @@ -71,7 +71,25 @@ class AnalysisFramework langutil::ErrorList filterErrors(langutil::ErrorList const& _errorList, bool _includeWarnings) const; std::vector m_warningsToFilter = {"This is a pre-release compiler version"}; - dev::solidity::CompilerStack m_compiler; + + /// @returns reference to lazy-instanciated CompilerStack. + dev::solidity::CompilerStack& compiler() + { + if (!m_compiler) + m_compiler = std::make_unique(); + return *m_compiler; + } + + /// @returns reference to lazy-instanciated CompilerStack. + dev::solidity::CompilerStack const& compiler() const + { + if (!m_compiler) + m_compiler = std::make_unique(); + return *m_compiler; + } + +private: + mutable std::unique_ptr m_compiler; }; // Asserts that the compilation down to typechecking diff --git a/test/libsolidity/GasCosts.cpp b/test/libsolidity/GasCosts.cpp index 9970d0454f0b..1e7858a51395 100644 --- a/test/libsolidity/GasCosts.cpp +++ b/test/libsolidity/GasCosts.cpp @@ -72,7 +72,12 @@ BOOST_AUTO_TEST_CASE(string_storage) CHECK_GAS(133899, 130591, 100); // This is only correct on >=Constantinople. else if (Options::get().useABIEncoderV2) - CHECK_GAS(151283, 136003, 100); + { + if (Options::get().optimizeYul) + CHECK_GAS(151283, 128285, 100); + else + CHECK_GAS(151283, 136003, 100); + } else CHECK_GAS(126689, 120159, 100); if (Options::get().evmVersion() >= EVMVersion::byzantium()) @@ -82,7 +87,12 @@ BOOST_AUTO_TEST_CASE(string_storage) CHECK_GAS(21551, 21526, 20); // This is only correct on >=Constantinople. else if (Options::get().useABIEncoderV2) - CHECK_GAS(21713, 21635, 20); + { + if (Options::get().optimizeYul) + CHECK_GAS(21713, 21567, 20); + else + CHECK_GAS(21713, 21635, 20); + } else CHECK_GAS(21546, 21526, 20); } diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 54d3c247c09c..7ef791c54a36 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -46,8 +46,8 @@ class GasMeterTestFramework: public SolidityExecutionFramework public: void compile(string const& _sourceCode) { - m_compiler.reset(false); - m_compiler.addSource("", "pragma solidity >=0.0;\n" + _sourceCode); + m_compiler.reset(); + m_compiler.setSources({{"", "pragma solidity >=0.0;\n" + _sourceCode}}); m_compiler.setOptimiserSettings(dev::test::Options::get().optimize); m_compiler.setEVMVersion(m_evmVersion); BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 140401f10eed..95a538aae997 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_SUITE(SolidityImports) BOOST_AUTO_TEST_CASE(smoke_test) { CompilerStack c; - c.addSource("a", "contract C {} pragma solidity >=0.0;"); + c.setSources({{"a", "contract C {} pragma solidity >=0.0;"}}); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -52,8 +52,10 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_AUTO_TEST_CASE(regular_import) { CompilerStack c; - c.addSource("a", "contract C {} pragma solidity >=0.0;"); - c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract C {} pragma solidity >=0.0;"}, + {"b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -61,8 +63,10 @@ BOOST_AUTO_TEST_CASE(regular_import) BOOST_AUTO_TEST_CASE(import_does_not_clutter_importee) { CompilerStack c; - c.addSource("a", "contract C { D d; } pragma solidity >=0.0;"); - c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract C { D d; } pragma solidity >=0.0;"}, + {"b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -70,9 +74,11 @@ BOOST_AUTO_TEST_CASE(import_does_not_clutter_importee) BOOST_AUTO_TEST_CASE(import_is_transitive) { CompilerStack c; - c.addSource("a", "contract C { } pragma solidity >=0.0;"); - c.addSource("b", "import \"a\"; pragma solidity >=0.0;"); - c.addSource("c", "import \"b\"; contract D is C {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract C { } pragma solidity >=0.0;"}, + {"b", "import \"a\"; pragma solidity >=0.0;"}, + {"c", "import \"b\"; contract D is C {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -80,8 +86,10 @@ BOOST_AUTO_TEST_CASE(import_is_transitive) BOOST_AUTO_TEST_CASE(circular_import) { CompilerStack c; - c.addSource("a", "import \"b\"; contract C { D d; } pragma solidity >=0.0;"); - c.addSource("b", "import \"a\"; contract D { C c; } pragma solidity >=0.0;"); + c.setSources({ + {"a", "import \"b\"; contract C { D d; } pragma solidity >=0.0;"}, + {"b", "import \"a\"; contract D { C c; } pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -89,9 +97,11 @@ BOOST_AUTO_TEST_CASE(circular_import) BOOST_AUTO_TEST_CASE(relative_import) { CompilerStack c; - c.addSource("a", "import \"./dir/b\"; contract A is B {} pragma solidity >=0.0;"); - c.addSource("dir/b", "contract B {} pragma solidity >=0.0;"); - c.addSource("dir/c", "import \"../a\"; contract C is A {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "import \"./dir/b\"; contract A is B {} pragma solidity >=0.0;"}, + {"dir/b", "contract B {} pragma solidity >=0.0;"}, + {"dir/c", "import \"../a\"; contract C is A {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -99,8 +109,10 @@ BOOST_AUTO_TEST_CASE(relative_import) BOOST_AUTO_TEST_CASE(relative_import_multiplex) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("dir/a/b/c", "import \"../../.././a\"; contract B is A {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"dir/a/b/c", "import \"../../.././a\"; contract B is A {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -108,8 +120,10 @@ BOOST_AUTO_TEST_CASE(relative_import_multiplex) BOOST_AUTO_TEST_CASE(simple_alias) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("dir/a/b/c", "import \"../../.././a\" as x; contract B is x.A { function() external { x.A r = x.A(20); } } pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"dir/a/b/c", "import \"../../.././a\" as x; contract B is x.A { function() external { x.A r = x.A(20); } } pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -117,9 +131,11 @@ BOOST_AUTO_TEST_CASE(simple_alias) BOOST_AUTO_TEST_CASE(library_name_clash) { CompilerStack c; - c.addSource("a", "library A {} pragma solidity >=0.0;"); - c.addSource("b", "library A {} pragma solidity >=0.0;"); - c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";"); + c.setSources({ + {"a", "library A {} pragma solidity >=0.0;"}, + {"b", "library A {} pragma solidity >=0.0;"}, + {"c", "import {A} from \"./a\"; import {A} from \"./b\";"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -127,8 +143,10 @@ BOOST_AUTO_TEST_CASE(library_name_clash) BOOST_AUTO_TEST_CASE(library_name_clash_with_contract) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("b", "library A {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"b", "library A {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -136,9 +154,11 @@ BOOST_AUTO_TEST_CASE(library_name_clash_with_contract) BOOST_AUTO_TEST_CASE(complex_import) { CompilerStack c; - c.addSource("a", "contract A {} contract B {} contract C { struct S { uint a; } } pragma solidity >=0.0;"); - c.addSource("b", "import \"a\" as x; import {B as b, C as c, C} from \"a\"; " - "contract D is b { function f(c.S memory var1, x.C.S memory var2, C.S memory var3) internal {} } pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} contract B {} contract C { struct S { uint a; } } pragma solidity >=0.0;"}, + {"b", "import \"a\" as x; import {B as b, C as c, C} from \"a\"; " + "contract D is b { function f(c.S memory var1, x.C.S memory var2, C.S memory var3) internal {} } pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -146,8 +166,10 @@ BOOST_AUTO_TEST_CASE(complex_import) BOOST_AUTO_TEST_CASE(name_clash_in_import_1) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("b", "import \"a\"; contract A {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"b", "import \"a\"; contract A {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -155,8 +177,10 @@ BOOST_AUTO_TEST_CASE(name_clash_in_import_1) BOOST_AUTO_TEST_CASE(name_clash_in_import_2) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("b", "import \"a\" as A; contract A {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"b", "import \"a\" as A; contract A {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -164,8 +188,10 @@ BOOST_AUTO_TEST_CASE(name_clash_in_import_2) BOOST_AUTO_TEST_CASE(name_clash_in_import_3) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("b", "import {A as b} from \"a\"; contract b {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"b", "import {A as b} from \"a\"; contract b {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -173,8 +199,10 @@ BOOST_AUTO_TEST_CASE(name_clash_in_import_3) BOOST_AUTO_TEST_CASE(name_clash_in_import_4) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("b", "import {A} from \"a\"; contract A {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"b", "import {A} from \"a\"; contract A {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -182,8 +210,10 @@ BOOST_AUTO_TEST_CASE(name_clash_in_import_4) BOOST_AUTO_TEST_CASE(name_clash_in_import_5) { CompilerStack c; - c.addSource("a", "contract A {} pragma solidity >=0.0;"); - c.addSource("b", "import {A} from \"a\"; contract B {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "contract A {} pragma solidity >=0.0;"}, + {"b", "import {A} from \"a\"; contract B {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -192,10 +222,12 @@ BOOST_AUTO_TEST_CASE(remappings) { CompilerStack c; c.setRemappings(vector{{"", "s", "s_1.4.6"},{"", "t", "Tee"}}); - c.addSource("a", "import \"s/s.sol\"; contract A is S {} pragma solidity >=0.0;"); - c.addSource("b", "import \"t/tee.sol\"; contract A is Tee {} pragma solidity >=0.0;"); - c.addSource("s_1.4.6/s.sol", "contract S {} pragma solidity >=0.0;"); - c.addSource("Tee/tee.sol", "contract Tee {} pragma solidity >=0.0;"); + c.setSources({ + {"a", "import \"s/s.sol\"; contract A is S {} pragma solidity >=0.0;"}, + {"b", "import \"t/tee.sol\"; contract A is Tee {} pragma solidity >=0.0;"}, + {"s_1.4.6/s.sol", "contract S {} pragma solidity >=0.0;"}, + {"Tee/tee.sol", "contract Tee {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -204,10 +236,12 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings) { CompilerStack c; c.setRemappings(vector{{"a", "s", "s_1.4.6"}, {"b", "s", "s_1.4.7"}}); - c.addSource("a/a.sol", "import \"s/s.sol\"; contract A is SSix {} pragma solidity >=0.0;"); - c.addSource("b/b.sol", "import \"s/s.sol\"; contract B is SSeven {} pragma solidity >=0.0;"); - c.addSource("s_1.4.6/s.sol", "contract SSix {} pragma solidity >=0.0;"); - c.addSource("s_1.4.7/s.sol", "contract SSeven {} pragma solidity >=0.0;"); + c.setSources({ + {"a/a.sol", "import \"s/s.sol\"; contract A is SSix {} pragma solidity >=0.0;"}, + {"b/b.sol", "import \"s/s.sol\"; contract B is SSeven {} pragma solidity >=0.0;"}, + {"s_1.4.6/s.sol", "contract SSix {} pragma solidity >=0.0;"}, + {"s_1.4.7/s.sol", "contract SSeven {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -215,8 +249,10 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings) BOOST_AUTO_TEST_CASE(filename_with_period) { CompilerStack c; - c.addSource("a/a.sol", "import \".b.sol\"; contract A is B {} pragma solidity >=0.0;"); - c.addSource("a/.b.sol", "contract B {} pragma solidity >=0.0;"); + c.setSources({ + {"a/a.sol", "import \".b.sol\"; contract A is B {} pragma solidity >=0.0;"}, + {"a/.b.sol", "contract B {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -229,10 +265,12 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_pres {"vendor/bar", "foo", "vendor/foo_1.0.0"}, {"", "bar", "vendor/bar"} }); - c.addSource("main.sol", "import \"foo/foo.sol\"; import {Bar} from \"bar/bar.sol\"; contract Main is Foo2, Bar {} pragma solidity >=0.0;"); - c.addSource("vendor/bar/bar.sol", "import \"foo/foo.sol\"; contract Bar {Foo1 foo;} pragma solidity >=0.0;"); - c.addSource("vendor/foo_1.0.0/foo.sol", "contract Foo1 {} pragma solidity >=0.0;"); - c.addSource("vendor/foo_2.0.0/foo.sol", "contract Foo2 {} pragma solidity >=0.0;"); + c.setSources({ + {"main.sol", "import \"foo/foo.sol\"; import {Bar} from \"bar/bar.sol\"; contract Main is Foo2, Bar {} pragma solidity >=0.0;"}, + {"vendor/bar/bar.sol", "import \"foo/foo.sol\"; contract Bar {Foo1 foo;} pragma solidity >=0.0;"}, + {"vendor/foo_1.0.0/foo.sol", "contract Foo1 {} pragma solidity >=0.0;"}, + {"vendor/foo_2.0.0/foo.sol", "contract Foo2 {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -241,10 +279,12 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent_1) { CompilerStack c; c.setRemappings(vector{{"a", "x/y/z", "d"}, {"a/b", "x", "e"}}); - c.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); - c.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); - c.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); - c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); + c.setSources({ + {"a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"}, + {"a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"}, + {"d/z.sol", "contract D {} pragma solidity >=0.0;"}, + {"e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -253,10 +293,12 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent_2) { CompilerStack c; c.setRemappings(vector{{"a/b", "x", "e"}, {"a", "x/y/z", "d"}}); - c.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"); - c.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); - c.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); - c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); + c.setSources({ + {"a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;"}, + {"a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"}, + {"d/z.sol", "contract D {} pragma solidity >=0.0;"}, + {"e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -264,9 +306,11 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent_2) BOOST_AUTO_TEST_CASE(shadowing_via_import) { CompilerStack c; - c.addSource("a", "library A {} pragma solidity >=0.0;"); - c.addSource("b", "library A {} pragma solidity >=0.0;"); - c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";"); + c.setSources({ + {"a", "library A {} pragma solidity >=0.0;"}, + {"b", "library A {} pragma solidity >=0.0;"}, + {"c", "import {A} from \"./a\"; import {A} from \"./b\";"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -274,13 +318,14 @@ BOOST_AUTO_TEST_CASE(shadowing_via_import) BOOST_AUTO_TEST_CASE(shadowing_builtins_with_imports) { CompilerStack c; - c.addSource("B.sol", "contract X {} pragma solidity >=0.0;"); - c.addSource("b", R"( + c.setSources({ + {"B.sol", "contract X {} pragma solidity >=0.0;"}, + {"b", R"( pragma solidity >=0.0; import * as msg from "B.sol"; contract C { - } - )"); + })"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); size_t errorCount = 0; @@ -301,13 +346,14 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_imports) BOOST_AUTO_TEST_CASE(shadowing_builtins_with_multiple_imports) { CompilerStack c; - c.addSource("B.sol", "contract msg {} contract block{} pragma solidity >=0.0;"); - c.addSource("b", R"( + c.setSources({ + {"B.sol", "contract msg {} contract block{} pragma solidity >=0.0;"}, + {"b", R"( pragma solidity >=0.0; import {msg, block} from "B.sol"; contract C { - } - )"); + })"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); auto numErrors = c.errors().size(); @@ -327,11 +373,12 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_multiple_imports) BOOST_AUTO_TEST_CASE(shadowing_builtins_with_alias) { CompilerStack c; - c.addSource("B.sol", "contract C {} pragma solidity >=0.0;"); - c.addSource("b", R"( + c.setSources({ + {"B.sol", "contract C {} pragma solidity >=0.0;"}, + {"b", R"( pragma solidity >=0.0; - import {C as msg} from "B.sol"; - )"); + import {C as msg} from "B.sol";)"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); auto numErrors = c.errors().size(); @@ -351,7 +398,8 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_alias) BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_1) { CompilerStack c; - c.addSource("A.sol", R"( + c.setSources({ + {"A.sol", R"( pragma solidity >=0.0; pragma experimental ABIEncoderV2; @@ -361,22 +409,21 @@ BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_1) S public s; function f(S memory _s) returns (S memory,S memory) { } } - )"); - - c.addSource("B.sol", R"( + )"}, + {"B.sol", R"( pragma solidity >=0.0; pragma experimental ABIEncoderV2; import "./A.sol"; contract B is A { } - )"); - - c.addSource("C.sol", R"( + )"}, + {"C.sol", R"( pragma solidity >=0.0; import "./B.sol"; contract C is B { } - )"); + )"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); @@ -401,7 +448,8 @@ BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_1) BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_2) { CompilerStack c; - c.addSource("A.sol", R"( + c.setSources({ + {"A.sol", R"( pragma solidity >=0.0; pragma experimental ABIEncoderV2; @@ -411,21 +459,20 @@ BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_2) S public s; function f(S memory _s) returns (S memory,S memory) { } } - )"); - - c.addSource("B.sol", R"( + )"}, + {"B.sol", R"( pragma solidity >=0.0; import "./A.sol"; contract B is A { } - )"); - - c.addSource("C.sol", R"( + )"}, + {"C.sol", R"( pragma solidity >=0.0; import "./B.sol"; contract C is B { } - )"); + )"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); @@ -450,7 +497,8 @@ BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_2) BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_match) { CompilerStack c; - c.addSource("A.sol", R"( + c.setSources({ + {"A.sol", R"( pragma solidity >=0.0; pragma experimental ABIEncoderV2; @@ -460,23 +508,22 @@ BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_match) S public s; function f(S memory _s) public returns (S memory,S memory) { } } - )"); - - c.addSource("B.sol", R"( + )"}, + {"B.sol", R"( pragma solidity >=0.0; pragma experimental ABIEncoderV2; import "./A.sol"; contract B is A { } - )"); - - c.addSource("C.sol", R"( + )"}, + {"C.sol", R"( pragma solidity >=0.0; pragma experimental ABIEncoderV2; import "./B.sol"; contract C is B { } - )"); + )"} + }); c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 149f7679caa9..0479be2f31ff 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -61,14 +61,13 @@ boost::optional parseAndReturnFirstError( AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM ) { - AssemblyStack stack(dev::test::Options::get().evmVersion(), _language); + AssemblyStack stack(dev::test::Options::get().evmVersion(), _language, dev::solidity::OptimiserSettings::none()); bool success = false; try { success = stack.parseAndAnalyze("", _source); - bool const optimize = false; if (success && _assemble) - stack.assemble(_machine, optimize); + stack.assemble(_machine); } catch (FatalError const&) { @@ -124,7 +123,7 @@ Error expectError( void parsePrintCompare(string const& _source, bool _canWarn = false) { - AssemblyStack stack(dev::test::Options::get().evmVersion()); + AssemblyStack stack(dev::test::Options::get().evmVersion(), AssemblyStack::Language::Assembly, OptimiserSettings::none()); BOOST_REQUIRE(stack.parseAndAnalyze("", _source)); if (_canWarn) BOOST_REQUIRE(Error::containsOnlyWarnings(stack.errors())); @@ -598,7 +597,7 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) { string source = "{ let x := \"\\u1bac\" }"; string parsed = "object \"object\" {\n code {\n let x := \"\\xe1\\xae\\xac\"\n }\n}\n"; - AssemblyStack stack(dev::test::Options::get().evmVersion()); + AssemblyStack stack(dev::test::Options::get().evmVersion(), AssemblyStack::Language::Assembly, OptimiserSettings::none()); BOOST_REQUIRE(stack.parseAndAnalyze("", source)); BOOST_REQUIRE(stack.errors().empty()); BOOST_CHECK_EQUAL(stack.print(), parsed); diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index d1e0cd678c62..ee30a31fa83e 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(metadata_stamp) } )"; CompilerStack compilerStack; - compilerStack.addSource("", std::string(sourceCode)); + compilerStack.setSources({{"", std::string(sourceCode)}}); compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(metadata_stamp_experimental) } )"; CompilerStack compilerStack; - compilerStack.addSource("", std::string(sourceCode)); + compilerStack.setSources({{"", std::string(sourceCode)}}); compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); @@ -105,20 +105,22 @@ BOOST_AUTO_TEST_CASE(metadata_stamp_experimental) BOOST_AUTO_TEST_CASE(metadata_relevant_sources) { CompilerStack compilerStack; - char const* sourceCode = R"( + char const* sourceCodeA = R"( pragma solidity >=0.0; contract A { function g(function(uint) external returns (uint) x) public {} } )"; - compilerStack.addSource("A", std::string(sourceCode)); - sourceCode = R"( + char const* sourceCodeB = R"( pragma solidity >=0.0; contract B { function g(function(uint) external returns (uint) x) public {} } )"; - compilerStack.addSource("B", std::string(sourceCode)); + compilerStack.setSources({ + {"A", std::string(sourceCodeA)}, + {"B", std::string(sourceCodeB)}, + }); compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); @@ -135,29 +137,31 @@ BOOST_AUTO_TEST_CASE(metadata_relevant_sources) BOOST_AUTO_TEST_CASE(metadata_relevant_sources_imports) { CompilerStack compilerStack; - char const* sourceCode = R"( + char const* sourceCodeA = R"( pragma solidity >=0.0; contract A { function g(function(uint) external returns (uint) x) public {} } )"; - compilerStack.addSource("A", std::string(sourceCode)); - sourceCode = R"( + char const* sourceCodeB = R"( pragma solidity >=0.0; import "./A"; contract B is A { function g(function(uint) external returns (uint) x) public {} } )"; - compilerStack.addSource("B", std::string(sourceCode)); - sourceCode = R"( + char const* sourceCodeC = R"( pragma solidity >=0.0; import "./B"; contract C is B { function g(function(uint) external returns (uint) x) public {} } )"; - compilerStack.addSource("C", std::string(sourceCode)); + compilerStack.setSources({ + {"A", std::string(sourceCodeA)}, + {"B", std::string(sourceCodeB)}, + {"C", std::string(sourceCodeC)} + }); compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); @@ -173,6 +177,44 @@ BOOST_AUTO_TEST_CASE(metadata_relevant_sources_imports) BOOST_CHECK(metadata["sources"].isMember("C")); } +BOOST_AUTO_TEST_CASE(metadata_useLiteralContent) +{ + // Check that the metadata contains "useLiteralContent" + char const* sourceCode = R"( + pragma solidity >=0.0; + contract test { + } + )"; + + auto check = [](char const* _src, bool _literal) + { + CompilerStack compilerStack; + compilerStack.setSources({{"", std::string(_src)}}); + compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); + compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); + compilerStack.useMetadataLiteralSources(_literal); + BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); + string metadata_str = compilerStack.metadata("test"); + Json::Value metadata; + jsonParse(metadata_str, metadata); + BOOST_CHECK(dev::test::isValidMetadata(metadata_str)); + BOOST_CHECK(metadata.isMember("settings")); + if (_literal) + { + BOOST_CHECK(metadata["settings"].isMember("metadata")); + BOOST_CHECK(metadata["settings"]["metadata"].isMember("useLiteralContent")); + BOOST_CHECK(metadata["settings"]["metadata"]["useLiteralContent"].asBool()); + } + else + { + BOOST_CHECK(!metadata["settings"].isMember("metadata")); + } + }; + + check(sourceCode, true); + check(sourceCode, false); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 4fd304079701..a08721f17fa2 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -188,6 +188,64 @@ BOOST_AUTO_TEST_CASE(division_truncates_correctly) CHECK_SUCCESS_NO_WARNINGS(text); } +BOOST_AUTO_TEST_CASE(compound_assignment_division) +{ + string text = R"( + contract C { + function f(uint x) public pure { + require(x == 2); + uint y = 10; + y /= y / x; + assert(y == x); + assert(y == 0); + } + } + )"; + CHECK_WARNING(text, "Assertion violation"); + text = R"( + contract C { + uint[] array; + function f(uint x, uint p) public { + require(x == 2); + require(array[p] == 10); + array[p] /= array[p] / x; + assert(array[p] == x); + assert(array[p] == 0); + } + } + )"; + CHECK_WARNING(text, "Assertion violation"); + text = R"( + contract C { + mapping (uint => uint) map; + function f(uint x, uint p) public { + require(x == 2); + require(map[p] == 10); + map[p] /= map[p] / x; + assert(map[p] == x); + assert(map[p] == 0); + } + } + )"; + CHECK_WARNING(text, "Assertion violation"); +} + +BOOST_AUTO_TEST_CASE(mod) +{ + string text = R"( + contract C { + function f(int x, int y) public pure { + require(y == -10); + require(x == 100); + int z1 = x % y; + int z2 = x % -y; + assert(z1 == z2); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SemVerMatcher.cpp b/test/libsolidity/SemVerMatcher.cpp index 2980acd1b1d8..4a6c123efa23 100644 --- a/test/libsolidity/SemVerMatcher.cpp +++ b/test/libsolidity/SemVerMatcher.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include using namespace std; diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 1dcf91719f67..1a65d0d6bb62 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -43,7 +43,13 @@ SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, lang soltestAssert(file, "Cannot open test contract: \"" + _filename + "\"."); file.exceptions(ios::badbit); - m_source = parseSource(file); + m_source = parseSourceAndSettings(file); + if (m_settings.count("compileViaYul")) + { + m_validatedSettings["compileViaYul"] = m_settings["compileViaYul"]; + m_compileViaYul = true; + m_settings.erase("compileViaYul"); + } parseExpectations(file); } @@ -105,7 +111,7 @@ void SemanticTest::parseExpectations(istream& _stream) { TestFileParser parser{_stream}; auto functionCalls = parser.parseFunctionCalls(); - move(functionCalls.begin(), functionCalls.end(), back_inserter(m_tests)); + std::move(functionCalls.begin(), functionCalls.end(), back_inserter(m_tests)); } bool SemanticTest::deploy(string const& _contractName, u256 const& _value, bytes const& _arguments) diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index 27e64422041d..ef2be88359ac 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -40,7 +40,7 @@ namespace test * section from the given file. This comment section should define a set of functions to be called * and an expected result they return after being executed. */ -class SemanticTest: public SolidityExecutionFramework, public TestCase +class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrictedTestCase { public: static std::unique_ptr create(Config const& _options) diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 3acb562575b0..7905d0b37084 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -40,8 +40,8 @@ class JSONInterfaceChecker public: void checkInterface(std::string const& _code, std::string const& _contractName, std::string const& _expectedInterfaceString) { - m_compilerStack.reset(false); - m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + m_compilerStack.reset(); + m_compilerStack.setSources({{"", "pragma solidity >=0.0;\n" + _code}}); m_compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); m_compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed"); diff --git a/test/libsolidity/SolidityCompiler.cpp b/test/libsolidity/SolidityCompiler.cpp index 10bdf9a86fca..8d8405f8b328 100644 --- a/test/libsolidity/SolidityCompiler.cpp +++ b/test/libsolidity/SolidityCompiler.cpp @@ -42,11 +42,11 @@ BOOST_AUTO_TEST_CASE(does_not_include_creation_time_only_internal_functions) function f() internal { for (uint i = 0; i < 10; ++i) x += 3 + i; } } )"; - m_compiler.setOptimiserSettings(dev::test::Options::get().optimize); + compiler().setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE(success(sourceCode)); - BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); - bytes const& creationBytecode = dev::test::bytecodeSansMetadata(m_compiler.object("C").bytecode); - bytes const& runtimeBytecode = dev::test::bytecodeSansMetadata(m_compiler.runtimeObject("C").bytecode); + BOOST_REQUIRE_MESSAGE(compiler().compile(), "Compiling contract failed"); + bytes const& creationBytecode = dev::test::bytecodeSansMetadata(compiler().object("C").bytecode); + bytes const& runtimeBytecode = dev::test::bytecodeSansMetadata(compiler().runtimeObject("C").bytecode); BOOST_CHECK(creationBytecode.size() >= 90); BOOST_CHECK(creationBytecode.size() <= 120); BOOST_CHECK(runtimeBytecode.size() >= 10); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 127e60e193b8..b90a47ddb329 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -6773,16 +6773,17 @@ BOOST_AUTO_TEST_CASE(bool_conversion) } )"; compileAndRun(sourceCode, 0, "C"); + bool v2 = dev::test::Options::get().useABIEncoderV2; ABI_CHECK(callContractFunction("f(bool)", 0), encodeArgs(0)); ABI_CHECK(callContractFunction("f(bool)", 1), encodeArgs(1)); - ABI_CHECK(callContractFunction("f(bool)", 2), encodeArgs(1)); - ABI_CHECK(callContractFunction("f(bool)", 3), encodeArgs(1)); - ABI_CHECK(callContractFunction("f(bool)", 255), encodeArgs(1)); + ABI_CHECK(callContractFunction("f(bool)", 2), v2 ? encodeArgs() : encodeArgs(1)); + ABI_CHECK(callContractFunction("f(bool)", 3), v2 ? encodeArgs() : encodeArgs(1)); + ABI_CHECK(callContractFunction("f(bool)", 255), v2 ? encodeArgs() : encodeArgs(1)); ABI_CHECK(callContractFunction("g(bool)", 0), encodeArgs(0)); ABI_CHECK(callContractFunction("g(bool)", 1), encodeArgs(1)); - ABI_CHECK(callContractFunction("g(bool)", 2), encodeArgs(1)); - ABI_CHECK(callContractFunction("g(bool)", 3), encodeArgs(1)); - ABI_CHECK(callContractFunction("g(bool)", 255), encodeArgs(1)); + ABI_CHECK(callContractFunction("g(bool)", 2), v2 ? encodeArgs() : encodeArgs(1)); + ABI_CHECK(callContractFunction("g(bool)", 3), v2 ? encodeArgs() : encodeArgs(1)); + ABI_CHECK(callContractFunction("g(bool)", 255), v2 ? encodeArgs() : encodeArgs(1)); } BOOST_AUTO_TEST_CASE(packed_storage_signed) @@ -8244,8 +8245,8 @@ BOOST_AUTO_TEST_CASE(calldata_struct_cleaning) // double check that the valid case goes through ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(0x12), bytes{0x34} + bytes(31,0)), encodeArgs(0x12, bytes{0x34} + bytes(31,0))); - ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(0x1234), bytes{0x56, 0x78} + bytes(30,0)), encodeArgs(0x34, bytes{0x56} + bytes(31,0))); - ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(-1), u256(-1)), encodeArgs(0xFF, bytes{0xFF} + bytes(31,0))); + ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(0x1234), bytes{0x56, 0x78} + bytes(30,0)), encodeArgs()); + ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(-1), u256(-1)), encodeArgs()); } BOOST_AUTO_TEST_CASE(calldata_struct_function_type) @@ -8446,6 +8447,9 @@ BOOST_AUTO_TEST_CASE(calldata_array_two_dimensional) function test()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) { return a[i][j]; } + function reenc()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) { + return this.test(a, i, j); + } } )"; compileAndRun(sourceCode, 0, "C"); @@ -8463,7 +8467,10 @@ BOOST_AUTO_TEST_CASE(calldata_array_two_dimensional) { ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, i, encoding), encodeArgs(data[i].size())); for (size_t j = 0; j < data[i].size(); j++) + { ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j])); + ABI_CHECK(callContractFunction("reenc(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j])); + } // out of bounds access ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, data[i].size(), encoding), encodeArgs()); } @@ -8513,6 +8520,9 @@ BOOST_AUTO_TEST_CASE(calldata_array_dynamic_three_dimensional) function test()" + arrayType + R"( calldata a, uint256 i, uint256 j, uint256 k) external returns (uint256) { return a[i][j][k]; } + function reenc()" + arrayType + R"( calldata a, uint256 i, uint256 j, uint256 k) external returns (uint256) { + return this.test(a, i, j, k); + } } )"; compileAndRun(sourceCode, 0, "C"); @@ -8539,7 +8549,10 @@ BOOST_AUTO_TEST_CASE(calldata_array_dynamic_three_dimensional) { ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j].size())); for (size_t k = 0; k < data[i][j].size(); k++) + { ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, k, encoding), encodeArgs(data[i][j][k])); + ABI_CHECK(callContractFunction("reenc(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, k, encoding), encodeArgs(data[i][j][k])); + } // out of bounds access ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, data[i][j].size(), encoding), encodeArgs()); } @@ -11031,7 +11044,8 @@ BOOST_AUTO_TEST_CASE(cleanup_bytes_types) )"; compileAndRun(sourceCode, 0, "C"); // We input longer data on purpose. - ABI_CHECK(callContractFunction("f(bytes2,uint16)", string("abc"), u256(0x040102)), encodeArgs(0)); + bool v2 = dev::test::Options::get().useABIEncoderV2; + ABI_CHECK(callContractFunction("f(bytes2,uint16)", string("abc"), u256(0x040102)), v2 ? encodeArgs() : encodeArgs(0)); } BOOST_AUTO_TEST_CASE(cleanup_bytes_types_shortening) @@ -11068,9 +11082,11 @@ BOOST_AUTO_TEST_CASE(cleanup_address_types) } )"; compileAndRun(sourceCode, 0, "C"); + + bool v2 = dev::test::Options::get().useABIEncoderV2; // We input longer data on purpose. - ABI_CHECK(callContractFunction("f(address)", u256("0xFFFF1234567890123456789012345678901234567890")), encodeArgs(0)); - ABI_CHECK(callContractFunction("g(address)", u256("0xFFFF1234567890123456789012345678901234567890")), encodeArgs(0)); + ABI_CHECK(callContractFunction("f(address)", u256("0xFFFF1234567890123456789012345678901234567890")), v2 ? encodeArgs() : encodeArgs(0)); + ABI_CHECK(callContractFunction("g(address)", u256("0xFFFF1234567890123456789012345678901234567890")), v2 ? encodeArgs() : encodeArgs(0)); } BOOST_AUTO_TEST_CASE(cleanup_address_types_shortening) @@ -12325,8 +12341,9 @@ BOOST_AUTO_TEST_CASE(shift_right_garbled) } )"; compileAndRun(sourceCode, 0, "C"); + bool v2 = dev::test::Options::get().useABIEncoderV2; ABI_CHECK(callContractFunction("f(uint8,uint8)", u256(0x0), u256(4)), encodeArgs(u256(0xf))); - ABI_CHECK(callContractFunction("f(uint8,uint8)", u256(0x0), u256(0x1004)), encodeArgs(u256(0xf))); + ABI_CHECK(callContractFunction("f(uint8,uint8)", u256(0x0), u256(0x1004)), v2 ? encodeArgs() : encodeArgs(u256(0xf))); } BOOST_AUTO_TEST_CASE(shift_right_garbled_signed) @@ -12350,16 +12367,17 @@ BOOST_AUTO_TEST_CASE(shift_right_garbled_signed) } )"; compileAndRun(sourceCode, 0, "C"); + bool v2 = dev::test::Options::get().useABIEncoderV2; ABI_CHECK(callContractFunction("f(int8,uint8)", u256(0x0), u256(3)), encodeArgs(u256(-2))); ABI_CHECK(callContractFunction("f(int8,uint8)", u256(0x0), u256(4)), encodeArgs(u256(-1))); ABI_CHECK(callContractFunction("f(int8,uint8)", u256(0x0), u256(0xFF)), encodeArgs(u256(-1))); - ABI_CHECK(callContractFunction("f(int8,uint8)", u256(0x0), u256(0x1003)), encodeArgs(u256(-2))); - ABI_CHECK(callContractFunction("f(int8,uint8)", u256(0x0), u256(0x1004)), encodeArgs(u256(-1))); + ABI_CHECK(callContractFunction("f(int8,uint8)", u256(0x0), u256(0x1003)), v2 ? encodeArgs() : encodeArgs(u256(-2))); + ABI_CHECK(callContractFunction("f(int8,uint8)", u256(0x0), u256(0x1004)), v2 ? encodeArgs() : encodeArgs(u256(-1))); ABI_CHECK(callContractFunction("g(int8,uint8)", u256(0x0), u256(3)), encodeArgs(u256(-2))); ABI_CHECK(callContractFunction("g(int8,uint8)", u256(0x0), u256(4)), encodeArgs(u256(-1))); ABI_CHECK(callContractFunction("g(int8,uint8)", u256(0x0), u256(0xFF)), encodeArgs(u256(-1))); - ABI_CHECK(callContractFunction("g(int8,uint8)", u256(0x0), u256(0x1003)), encodeArgs(u256(-2))); - ABI_CHECK(callContractFunction("g(int8,uint8)", u256(0x0), u256(0x1004)), encodeArgs(u256(-1))); + ABI_CHECK(callContractFunction("g(int8,uint8)", u256(0x0), u256(0x1003)), v2 ? encodeArgs() : encodeArgs(u256(-2))); + ABI_CHECK(callContractFunction("g(int8,uint8)", u256(0x0), u256(0x1004)), v2 ? encodeArgs() : encodeArgs(u256(-1))); } BOOST_AUTO_TEST_CASE(shift_right_uint32) @@ -12541,11 +12559,12 @@ BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int8) } )"; 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(-52))); - ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(2)), encodeArgs(u256(-26))); - ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(4)), encodeArgs(u256(-7))); - ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(8)), encodeArgs(u256(-1))); + bool v2 = dev::test::Options::get().useABIEncoderV2; + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(0)), v2 ? encodeArgs() : encodeArgs(u256(-103))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(1)), v2 ? encodeArgs() : encodeArgs(u256(-52))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(2)), v2 ? encodeArgs() : encodeArgs(u256(-26))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(4)), v2 ? encodeArgs() : encodeArgs(u256(-7))); + ABI_CHECK(callContractFunction("f(int8,int8)", u256(0x99u), u256(8)), v2 ? encodeArgs() : encodeArgs(u256(-1))); } BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int16) @@ -12558,11 +12577,12 @@ BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int16) } )"; 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(-52))); - ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(2)), encodeArgs(u256(-26))); - ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(4)), encodeArgs(u256(-7))); - ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(8)), encodeArgs(u256(-1))); + bool v2 = dev::test::Options::get().useABIEncoderV2; + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(0)), v2 ? encodeArgs() : encodeArgs(u256(-103))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(1)), v2 ? encodeArgs() : encodeArgs(u256(-52))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(2)), v2 ? encodeArgs() : encodeArgs(u256(-26))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(4)), v2 ? encodeArgs() : encodeArgs(u256(-7))); + ABI_CHECK(callContractFunction("f(int16,int16)", u256(0xFF99u), u256(8)), v2 ? encodeArgs() : encodeArgs(u256(-1))); } BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int32) @@ -12575,11 +12595,12 @@ BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_signextend_int32) } )"; 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(-52))); - ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(2)), encodeArgs(u256(-26))); - ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(4)), encodeArgs(u256(-7))); - ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(8)), encodeArgs(u256(-1))); + bool v2 = dev::test::Options::get().useABIEncoderV2; + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(0)), v2 ? encodeArgs() : encodeArgs(u256(-103))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(1)), v2 ? encodeArgs() : encodeArgs(u256(-52))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(2)), v2 ? encodeArgs() : encodeArgs(u256(-26))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(4)), v2 ? encodeArgs() : encodeArgs(u256(-7))); + ABI_CHECK(callContractFunction("f(int32,int32)", u256(0xFFFFFF99u), u256(8)), v2 ? encodeArgs() : encodeArgs(u256(-1))); } @@ -15586,6 +15607,33 @@ BOOST_AUTO_TEST_CASE(contract_name) ABI_CHECK(callContractFunction("constantNameAccessor()"), argsLong); } +BOOST_AUTO_TEST_CASE(event_wrong_abi_name) +{ + char const* sourceCode = R"( + library ClientReceipt { + event Deposit(Test indexed _from, bytes32 indexed _id, uint _value); + function deposit(bytes32 _id) public { + Test a; + emit Deposit(a, _id, msg.value); + } + } + contract Test { + function f() public { + ClientReceipt.deposit("123"); + } + } + )"; + compileAndRun(sourceCode, 0, "ClientReceipt", bytes()); + compileAndRun(sourceCode, 0, "Test", bytes(), map{{"ClientReceipt", m_contractAddress}}); + u256 value(18); + u256 id(0x1234); + + callContractFunction("f()"); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 3); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bytes32,uint256)"))); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 900d0b9d8896..a04b5373d5fa 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -27,6 +27,9 @@ #include #include + +#include + #include #include @@ -69,29 +72,50 @@ class SolidityExecutionFramework: public dev::test::ExecutionFramework if (dev::test::Options::get().useABIEncoderV2 && _sourceCode.find("pragma experimental ABIEncoderV2;") == std::string::npos) sourceCode += "pragma experimental ABIEncoderV2;\n"; sourceCode += _sourceCode; - m_compiler.reset(false); - m_compiler.addSource("", sourceCode); + m_compiler.reset(); + m_compiler.setSources({{"", sourceCode}}); m_compiler.setLibraries(_libraryAddresses); m_compiler.setEVMVersion(m_evmVersion); m_compiler.setOptimiserSettings(m_optimiserSettings); + m_compiler.enableIRGeneration(m_compileViaYul); if (!m_compiler.compile()) { langutil::SourceReferenceFormatter formatter(std::cerr); for (auto const& error: m_compiler.errors()) - formatter.printExceptionInformation( - *error, - (error->type() == langutil::Error::Type::Warning) ? "Warning" : "Error" - ); + formatter.printErrorInformation(*error); BOOST_ERROR("Compiling contract failed"); } - eth::LinkerObject obj = m_compiler.object(_contractName.empty() ? m_compiler.lastContractName() : _contractName); + eth::LinkerObject obj; + if (m_compileViaYul) + { + yul::AssemblyStack asmStack( + m_evmVersion, + yul::AssemblyStack::Language::StrictAssembly, + m_optimiserSettings + ); + if (!asmStack.parseAndAnalyze("", m_compiler.yulIROptimized( + _contractName.empty() ? m_compiler.lastContractName() : _contractName + ))) + { + langutil::SourceReferenceFormatter formatter(std::cerr); + + for (auto const& error: m_compiler.errors()) + formatter.printErrorInformation(*error); + BOOST_ERROR("Assembly contract failed. IR: " + m_compiler.yulIROptimized({})); + } + asmStack.optimize(); + obj = std::move(*asmStack.assemble(yul::AssemblyStack::Machine::EVM).bytecode); + } + else + obj = m_compiler.object(_contractName.empty() ? m_compiler.lastContractName() : _contractName); BOOST_REQUIRE(obj.linkReferences.empty()); return obj.bytecode; } protected: dev::solidity::CompilerStack m_compiler; + bool m_compileViaYul = false; }; } diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 65ee97bc0037..36131799e727 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -28,11 +28,13 @@ #include #include #include +#include #include #include #include using namespace std; +using namespace dev::eth; using namespace langutil; namespace dev @@ -596,7 +598,7 @@ BOOST_AUTO_TEST_CASE(blockhash) } )"; - auto blockhashFun = make_shared(strings{"uint256"}, strings{"bytes32"}, + auto blockhashFun = TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View); bytes code = compileFirstExpression(sourceCode, {}, {}, {make_shared("blockhash", blockhashFun)}); @@ -617,7 +619,7 @@ BOOST_AUTO_TEST_CASE(gas_left) )"; bytes code = compileFirstExpression( sourceCode, {}, {}, - {make_shared("gasleft", make_shared(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft))} + {make_shared("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft))} ); bytes expectation = bytes({uint8_t(Instruction::GAS)}); diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index e3f3f077d916..3a51672cf14c 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(getter_is_memory_type) )"; CHECK_SUCCESS_NO_WARNINGS(text); // Check that the getters return a memory strings, not a storage strings. - ContractDefinition const& c = dynamic_cast(*m_compiler.ast("").nodes().at(1)); + ContractDefinition const& c = dynamic_cast(*compiler().ast("").nodes().at(1)); BOOST_CHECK(c.interfaceFunctions().size() == 2); for (auto const& f: c.interfaceFunctions()) { diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index 000a79381845..faaf20da592b 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -46,8 +46,8 @@ class DocumentationChecker bool _userDocumentation ) { - m_compilerStack.reset(false); - m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + m_compilerStack.reset(); + m_compilerStack.setSources({{"", "pragma solidity >=0.0;\n" + _code}}); m_compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed"); @@ -67,8 +67,8 @@ class DocumentationChecker void expectNatspecError(std::string const& _code) { - m_compilerStack.reset(false); - m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + m_compilerStack.reset(); + m_compilerStack.setSources({{"", "pragma solidity >=0.0;\n" + _code}}); m_compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!m_compilerStack.parseAndAnalyze()); BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError)); diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 419bc8ad8b70..bd058a74341c 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -109,7 +109,7 @@ class OptimizerTestFramework: public SolidityExecutionFramework bytes realCode = bytecodeSansMetadata(_bytecode); BOOST_REQUIRE_MESSAGE(!realCode.empty(), "Invalid or missing metadata in bytecode."); size_t instructions = 0; - solidity::eachInstruction(realCode, [&](Instruction _instr, u256 const&) { + dev::eth::eachInstruction(realCode, [&](Instruction _instr, u256 const&) { if (!_which || *_which == _instr) instructions++; }); diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 44624c962cfa..763fda9ca71f 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using namespace std; using namespace langutil; @@ -631,6 +632,42 @@ BOOST_AUTO_TEST_CASE(recursion_depth4) CHECK_PARSE_ERROR(text, "Maximum recursion depth reached during parsing"); } +BOOST_AUTO_TEST_CASE(inline_asm_end_location) +{ + auto sourceCode = std::string(R"( + contract C { + function f() public pure returns (uint y) { + uint a; + assembly { a := 0x12345678 } + uint z = a; + y = z; + } + } + )"); + ErrorList errors; + auto contract = parseText(sourceCode, errors); + + class CheckInlineAsmLocation: public ASTConstVisitor + { + public: + bool visited = false; + virtual bool visit(InlineAssembly const& _inlineAsm) + { + auto loc = _inlineAsm.location(); + auto asmStr = loc.source->source().substr(loc.start, loc.end - loc.start); + BOOST_CHECK_EQUAL(asmStr, "assembly { a := 0x12345678 }"); + visited = true; + + return false; + } + }; + + CheckInlineAsmLocation visitor; + contract->accept(visitor); + + BOOST_CHECK_MESSAGE(visitor.visited, "No inline asm block found?!"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 3f60c3548bba..dc3b18c88ab6 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -39,51 +40,51 @@ BOOST_AUTO_TEST_SUITE(SolidityTypes) BOOST_AUTO_TEST_CASE(int_types) { - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::Int, 0, 0)) == *make_shared(256, IntegerType::Modifier::Signed)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::Int, 0, 0)) == *TypeProvider::integer(256, IntegerType::Modifier::Signed)); for (unsigned i = 8; i <= 256; i += 8) - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::IntM, i, 0)) == *make_shared(i, IntegerType::Modifier::Signed)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::IntM, i, 0)) == *TypeProvider::integer(i, IntegerType::Modifier::Signed)); } BOOST_AUTO_TEST_CASE(uint_types) { - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::UInt, 0, 0)) == *make_shared(256, IntegerType::Modifier::Unsigned)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::UInt, 0, 0)) == *TypeProvider::integer(256, IntegerType::Modifier::Unsigned)); for (unsigned i = 8; i <= 256; i += 8) - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::UIntM, i, 0)) == *make_shared(i, IntegerType::Modifier::Unsigned)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::UIntM, i, 0)) == *TypeProvider::integer(i, IntegerType::Modifier::Unsigned)); } BOOST_AUTO_TEST_CASE(byte_types) { - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::Byte, 0, 0)) == *make_shared(1)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::Byte, 0, 0)) == *TypeProvider::fixedBytes(1)); for (unsigned i = 1; i <= 32; i++) - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::BytesM, i, 0)) == *make_shared(i)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::BytesM, i, 0)) == *TypeProvider::fixedBytes(i)); } BOOST_AUTO_TEST_CASE(fixed_types) { - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::Fixed, 0, 0)) == *make_shared(128, 18, FixedPointType::Modifier::Signed)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::Fixed, 0, 0)) == *TypeProvider::fixedPoint(128, 18, FixedPointType::Modifier::Signed)); for (unsigned i = 8; i <= 256; i += 8) { - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::FixedMxN, i, 0)) == *make_shared(i, 0, FixedPointType::Modifier::Signed)); - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::FixedMxN, i, 2)) == *make_shared(i, 2, FixedPointType::Modifier::Signed)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::FixedMxN, i, 0)) == *TypeProvider::fixedPoint(i, 0, FixedPointType::Modifier::Signed)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::FixedMxN, i, 2)) == *TypeProvider::fixedPoint(i, 2, FixedPointType::Modifier::Signed)); } } BOOST_AUTO_TEST_CASE(ufixed_types) { - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixed, 0, 0)) == *make_shared(128, 18, FixedPointType::Modifier::Unsigned)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixed, 0, 0)) == *TypeProvider::fixedPoint(128, 18, FixedPointType::Modifier::Unsigned)); for (unsigned i = 8; i <= 256; i += 8) { - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixedMxN, i, 0)) == *make_shared(i, 0, FixedPointType::Modifier::Unsigned)); - BOOST_CHECK(*Type::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixedMxN, i, 2)) == *make_shared(i, 2, FixedPointType::Modifier::Unsigned)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixedMxN, i, 0)) == *TypeProvider::fixedPoint(i, 0, FixedPointType::Modifier::Unsigned)); + BOOST_CHECK(*TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken(Token::UFixedMxN, i, 2)) == *TypeProvider::fixedPoint(i, 2, FixedPointType::Modifier::Unsigned)); } } BOOST_AUTO_TEST_CASE(storage_layout_simple) { MemberList members(MemberList::MemberMap({ - {string("first"), Type::fromElementaryTypeName("uint128")}, - {string("second"), Type::fromElementaryTypeName("uint120")}, - {string("wraps"), Type::fromElementaryTypeName("uint16")} + {string("first"), TypeProvider::fromElementaryTypeName("uint128")}, + {string("second"), TypeProvider::fromElementaryTypeName("uint120")}, + {string("wraps"), TypeProvider::fromElementaryTypeName("uint16")} })); BOOST_REQUIRE_EQUAL(u256(2), members.storageSize()); BOOST_REQUIRE(members.memberStorageOffset("first") != nullptr); @@ -97,15 +98,15 @@ BOOST_AUTO_TEST_CASE(storage_layout_simple) BOOST_AUTO_TEST_CASE(storage_layout_mapping) { MemberList members(MemberList::MemberMap({ - {string("first"), Type::fromElementaryTypeName("uint128")}, - {string("second"), make_shared( - Type::fromElementaryTypeName("uint8"), - Type::fromElementaryTypeName("uint8") + {string("first"), TypeProvider::fromElementaryTypeName("uint128")}, + {string("second"), TypeProvider::mapping( + TypeProvider::fromElementaryTypeName("uint8"), + TypeProvider::fromElementaryTypeName("uint8") )}, - {string("third"), Type::fromElementaryTypeName("uint16")}, - {string("final"), make_shared( - Type::fromElementaryTypeName("uint8"), - Type::fromElementaryTypeName("uint8") + {string("third"), TypeProvider::fromElementaryTypeName("uint16")}, + {string("final"), TypeProvider::mapping( + TypeProvider::fromElementaryTypeName("uint8"), + TypeProvider::fromElementaryTypeName("uint8") )}, })); BOOST_REQUIRE_EQUAL(u256(4), members.storageSize()); @@ -121,13 +122,13 @@ BOOST_AUTO_TEST_CASE(storage_layout_mapping) BOOST_AUTO_TEST_CASE(storage_layout_arrays) { - BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(1), 32).storageSize() == 1); - BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(1), 33).storageSize() == 2); - BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(2), 31).storageSize() == 2); - BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(7), 8).storageSize() == 2); - BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(7), 9).storageSize() == 3); - BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(31), 9).storageSize() == 9); - BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(32), 9).storageSize() == 9); + BOOST_CHECK(ArrayType(DataLocation::Storage, TypeProvider::fixedBytes(1), 32).storageSize() == 1); + BOOST_CHECK(ArrayType(DataLocation::Storage, TypeProvider::fixedBytes(1), 33).storageSize() == 2); + BOOST_CHECK(ArrayType(DataLocation::Storage, TypeProvider::fixedBytes(2), 31).storageSize() == 2); + BOOST_CHECK(ArrayType(DataLocation::Storage, TypeProvider::fixedBytes(7), 8).storageSize() == 2); + BOOST_CHECK(ArrayType(DataLocation::Storage, TypeProvider::fixedBytes(7), 9).storageSize() == 3); + BOOST_CHECK(ArrayType(DataLocation::Storage, TypeProvider::fixedBytes(31), 9).storageSize() == 9); + BOOST_CHECK(ArrayType(DataLocation::Storage, TypeProvider::fixedBytes(32), 9).storageSize() == 9); } BOOST_AUTO_TEST_CASE(type_identifier_escaping) @@ -149,12 +150,12 @@ BOOST_AUTO_TEST_CASE(type_identifier_escaping) BOOST_AUTO_TEST_CASE(type_identifiers) { ASTNode::resetID(); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint128")->identifier(), "t_uint128"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("int128")->identifier(), "t_int128"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("address")->identifier(), "t_address"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint8")->identifier(), "t_uint8"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("ufixed64x2")->identifier(), "t_ufixed64x2"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("fixed128x8")->identifier(), "t_fixed128x8"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("uint128")->identifier(), "t_uint128"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("int128")->identifier(), "t_int128"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("address")->identifier(), "t_address"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("uint8")->identifier(), "t_uint8"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("ufixed64x2")->identifier(), "t_ufixed64x2"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("fixed128x8")->identifier(), "t_fixed128x8"); BOOST_CHECK_EQUAL(RationalNumberType(rational(7, 1)).identifier(), "t_rational_7_by_1"); BOOST_CHECK_EQUAL(RationalNumberType(rational(200, 77)).identifier(), "t_rational_200_by_77"); BOOST_CHECK_EQUAL(RationalNumberType(rational(2 * 200, 2 * 77)).identifier(), "t_rational_200_by_77"); @@ -163,22 +164,22 @@ BOOST_AUTO_TEST_CASE(type_identifiers) StringLiteralType(Literal(SourceLocation{}, Token::StringLiteral, make_shared("abc - def"))).identifier(), "t_stringliteral_196a9142ee0d40e274a6482393c762b16dd8315713207365e1e13d8d85b74fc4" ); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("byte")->identifier(), "t_bytes1"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes8")->identifier(), "t_bytes8"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes32")->identifier(), "t_bytes32"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bool")->identifier(), "t_bool"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes")->identifier(), "t_bytes_storage_ptr"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes memory")->identifier(), "t_bytes_memory_ptr"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes storage")->identifier(), "t_bytes_storage_ptr"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes calldata")->identifier(), "t_bytes_calldata_ptr"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string")->identifier(), "t_string_storage_ptr"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string memory")->identifier(), "t_string_memory_ptr"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string storage")->identifier(), "t_string_storage_ptr"); - BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string calldata")->identifier(), "t_string_calldata_ptr"); - ArrayType largeintArray(DataLocation::Memory, Type::fromElementaryTypeName("int128"), u256("2535301200456458802993406410752")); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("byte")->identifier(), "t_bytes1"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("bytes8")->identifier(), "t_bytes8"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("bytes32")->identifier(), "t_bytes32"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("bool")->identifier(), "t_bool"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("bytes")->identifier(), "t_bytes_storage_ptr"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("bytes memory")->identifier(), "t_bytes_memory_ptr"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("bytes storage")->identifier(), "t_bytes_storage_ptr"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("bytes calldata")->identifier(), "t_bytes_calldata_ptr"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("string")->identifier(), "t_string_storage_ptr"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("string memory")->identifier(), "t_string_memory_ptr"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("string storage")->identifier(), "t_string_storage_ptr"); + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("string calldata")->identifier(), "t_string_calldata_ptr"); + ArrayType largeintArray(DataLocation::Memory, TypeProvider::fromElementaryTypeName("int128"), u256("2535301200456458802993406410752")); BOOST_CHECK_EQUAL(largeintArray.identifier(), "t_array$_t_int128_$2535301200456458802993406410752_memory_ptr"); - TypePointer stringArray = make_shared(DataLocation::Storage, Type::fromElementaryTypeName("string"), u256("20")); - TypePointer multiArray = make_shared(DataLocation::Storage, stringArray); + TypePointer stringArray = TypeProvider::array(DataLocation::Storage, TypeProvider::fromElementaryTypeName("string"), u256("20")); + TypePointer multiArray = TypeProvider::array(DataLocation::Storage, stringArray); BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr"); ContractDefinition c(SourceLocation{}, make_shared("MyContract$"), {}, {}, {}, ContractDefinition::ContractKind::Contract); @@ -194,14 +195,14 @@ BOOST_AUTO_TEST_CASE(type_identifiers) TupleType t({e.type(), s.type(), stringArray, nullptr}); BOOST_CHECK_EQUAL(t.identifier(), "t_tuple$_t_type$_t_enum$_Enum_$4_$_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_t_array$_t_string_storage_$20_storage_ptr_$__$"); - TypePointer keccak256fun = make_shared(strings{}, strings{}, FunctionType::Kind::KECCAK256); + TypePointer keccak256fun = TypeProvider::function(strings{}, strings{}, FunctionType::Kind::KECCAK256); BOOST_CHECK_EQUAL(keccak256fun->identifier(), "t_function_keccak256_nonpayable$__$returns$__$"); FunctionType metaFun(TypePointers{keccak256fun}, TypePointers{s.type()}, strings{""}, strings{""}); BOOST_CHECK_EQUAL(metaFun.identifier(), "t_function_internal_nonpayable$_t_function_keccak256_nonpayable$__$returns$__$_$returns$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$"); - TypePointer m = make_shared(Type::fromElementaryTypeName("bytes32"), s.type()); - MappingType m2(Type::fromElementaryTypeName("uint64"), m); + TypePointer m = TypeProvider::mapping(TypeProvider::fromElementaryTypeName("bytes32"), s.type()); + MappingType m2(TypeProvider::fromElementaryTypeName("uint64"), m); BOOST_CHECK_EQUAL(m2.identifier(), "t_mapping$_t_uint64_$_t_mapping$_t_bytes32_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_$"); // TypeType is tested with contract @@ -230,9 +231,9 @@ BOOST_AUTO_TEST_CASE(encoded_sizes) BOOST_CHECK_EQUAL(BoolType().calldataEncodedSize(true), 32); BOOST_CHECK_EQUAL(BoolType().calldataEncodedSize(false), 1); - shared_ptr uint24Array = make_shared( + ArrayType const* uint24Array = TypeProvider::array( DataLocation::Memory, - make_shared(24), + TypeProvider::uint(24), 9 ); BOOST_CHECK_EQUAL(uint24Array->calldataEncodedSize(true), 9 * 32); diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 4b837d6e7aef..a6f43082af59 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -83,10 +83,10 @@ Json::Value getContractResult(Json::Value const& _compilerResult, string const& return _compilerResult["contracts"][_file][_name]; } -Json::Value compile(string const& _input) +Json::Value compile(string _input) { StandardCompiler compiler; - string output = compiler.compile(_input); + string output = compiler.compile(std::move(_input)); Json::Value ret; BOOST_REQUIRE(jsonParseStrict(output, ret)); return ret; diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index d9900ab2bbf4..6806c8047578 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -59,21 +59,21 @@ SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion BOOST_THROW_EXCEPTION(runtime_error("Cannot open test contract: \"" + _filename + "\".")); file.exceptions(ios::badbit); - m_source = parseSource(file); + m_source = parseSourceAndSettings(file); m_expectations = parseExpectations(file); } bool SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) { string const versionPragma = "pragma solidity >=0.0;\n"; - m_compiler.reset(); - m_compiler.addSource("", versionPragma + m_source); - m_compiler.setEVMVersion(m_evmVersion); + compiler().reset(); + compiler().setSources({{"", versionPragma + m_source}}); + compiler().setEVMVersion(m_evmVersion); - if (m_compiler.parse()) - m_compiler.analyze(); + if (compiler().parse()) + compiler().analyze(); - for (auto const& currentError: filterErrors(m_compiler.errors(), true)) + for (auto const& currentError: filterErrors(compiler().errors(), true)) { int locationStart = -1, locationEnd = -1; if (auto location = boost::get_error_info(*currentError)) diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index a7f5d0b98b6e..c6267d018675 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -50,7 +50,7 @@ struct SyntaxTestError }; -class SyntaxTest: AnalysisFramework, public TestCase +class SyntaxTest: AnalysisFramework, public EVMVersionRestrictedTestCase { public: static std::unique_ptr create(Config const& _config) diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array.sol new file mode 100644 index 000000000000..6a1bbcffa0b4 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array.sol @@ -0,0 +1,22 @@ +pragma experimental ABIEncoderV2; + +contract C { + function g(uint256[] calldata) external pure returns (bytes memory) { + return msg.data; + } + function f(uint256[][1] calldata s) external view returns (bool) { + bytes memory a = this.g(s[0]); + uint256[] memory m = s[0]; + bytes memory b = this.g(m); + assert(a.length == b.length); + for (uint i = 0; i < a.length; i++) + assert(a[i] == b[i]); + return true; + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[][1]): 32, 32, 0 -> true +// f(uint256[][1]): 32, 32, 1, 42 -> true +// f(uint256[][1]): 32, 32, 8, 421, 422, 423, 424, 425, 426, 427, 428 -> true diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic.sol new file mode 100644 index 000000000000..b2076b370971 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic.sol @@ -0,0 +1,33 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function g(uint256[] calldata s) external view returns (bytes memory) { + return this.f(s); + } + function h(uint8[] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function i(uint8[] calldata s) external view returns (bytes memory) { + return this.h(s); + } + function j(bytes calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function k(bytes calldata s) external view returns (bytes memory) { + return this.j(s); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87 +// g(uint256[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87 +// h(uint8[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87 +// i(uint8[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87 +// h(uint8[]): 32, 3, 0xFF23, 0x1242, 0xAB87 -> FAILURE +// i(uint8[]): 32, 3, 0xAB23, 0x1242, 0xFF87 -> FAILURE +// j(bytes): 32, 3, hex"123456" -> 32, 96, 32, 3, left(0x123456) +// k(bytes): 32, 3, hex"AB33FF" -> 32, 96, 32, 3, left(0xAB33FF) diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_index_access.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_index_access.sol new file mode 100644 index 000000000000..f2224c803cf5 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_index_access.sol @@ -0,0 +1,34 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function g(uint256[][2] calldata s, uint256 which) external view returns (bytes memory) { + return this.f(s[which]); + } + function h(uint8[] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function i(uint8[][2] calldata s, uint256 which) external view returns (bytes memory) { + return this.h(s[which]); + } + function j(bytes calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function k(bytes[2] calldata s, uint256 which) external view returns (bytes memory) { + return this.j(s[which]); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[]): 32, 3, 42, 23, 87 -> 32, 160, 32, 3, 42, 23, 87 +// g(uint256[][2],uint256): 0x40, 0, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17 -> 32, 160, 32, 3, 42, 23, 87 +// g(uint256[][2],uint256): 0x40, 1, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17, 27 -> 32, 192, 32, 4, 11, 13, 17, 27 +// h(uint8[]): 32, 3, 42, 23, 87 -> 32, 160, 32, 3, 42, 23, 87 +// i(uint8[][2],uint256): 0x40, 0, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17 -> 32, 160, 32, 3, 42, 23, 87 +// i(uint8[][2],uint256): 0x40, 1, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17, 27 -> 32, 192, 32, 4, 11, 13, 17, 27 +// j(bytes): 32, 3, hex"AB11FF" -> 32, 96, 32, 3, left(0xAB11FF) +// k(bytes[2],uint256): 0x40, 0, 0x40, 0x63, 3, hex"AB11FF", 4, hex"FF791432" -> 32, 96, 32, 3, left(0xAB11FF) +// k(bytes[2],uint256): 0x40, 1, 0x40, 0x63, 3, hex"AB11FF", 4, hex"FF791432" -> 32, 96, 32, 4, left(0xFF791432) diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_dynamic.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_dynamic.sol new file mode 100644 index 000000000000..6bb10c143661 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_dynamic.sol @@ -0,0 +1,49 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint8[][1][] calldata s) external pure returns (bytes memory) { + return msg.data; + } + function f2(uint256[][2][] calldata s) external pure returns (bytes memory) { + return msg.data; + } + function reenc_f(uint8[][1][] calldata s) external view returns (bytes memory) { + return this.f(s); + } + function reenc_f2(uint256[][2][] calldata s) external view returns (bytes memory) { + return this.f2(s); + } + function g() external returns (bytes memory) { + uint8[][1][] memory m = new uint8[][1][](1); + m[0][0] = new uint8[](1); + m[0][0][0] = 42; + return this.f(m); + } + function h() external returns (bytes memory) { + uint8[][1][] memory m = new uint8[][1][](1); + m[0][0] = new uint8[](1); + m[0][0][0] = 42; + return this.reenc_f(m); + } + function i() external returns (bytes memory) { + uint256[][2][] memory m = new uint256[][2][](1); + m[0][0] = new uint256[](1); + m[0][1] = new uint256[](1); + m[0][0][0] = 42; + m[0][1][0] = 42; + return this.f2(m); + } + function j() external returns (bytes memory) { + uint256[][2][] memory m = new uint256[][2][](1); + m[0][0] = new uint256[](1); + m[0][1] = new uint256[](1); + m[0][0][0] = 42; + m[0][1][0] = 42; + return this.reenc_f2(m); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// g() -> 32, 196, hex"eccb829a", 32, 1, 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000" +// h() -> 32, 196, hex"eccb829a", 32, 1, 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000" diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_function_types.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_function_types.sol new file mode 100644 index 000000000000..c9ccc88a8b3a --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_function_types.sol @@ -0,0 +1,30 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(function() external returns (uint)[] calldata s) external returns (uint, uint, uint) { + assert(s.length == 3); + return (s[0](), s[1](), s[2]()); + } + function f_reenc(function() external returns (uint)[] calldata s) external returns (uint, uint, uint) { + return this.f(s); + } + function getter1() external returns (uint) { + return 23; + } + function getter2() external returns (uint) { + return 37; + } + function getter3() external returns (uint) { + return 71; + } + function g(bool reenc) external returns (uint, uint, uint) { + function() external returns (uint)[] memory a = new function() external returns (uint)[](3); + a[0] = this.getter1; + a[1] = this.getter2; + a[2] = this.getter3; + return reenc ? this.f_reenc(a) : this.f(a); + } +} +// ---- +// g(bool): false -> 23, 37, 71 +// g(bool): true -> 23, 37, 71 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_multi_dynamic.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_multi_dynamic.sol new file mode 100644 index 000000000000..f981a6e463b3 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_multi_dynamic.sol @@ -0,0 +1,31 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[][] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function g(uint256[][] calldata s) external view returns (bytes memory) { + return this.f(s); + } + function h(uint8[][] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function i(uint8[][] calldata s) external view returns (bytes memory) { + return this.h(s); + } + function j(bytes[] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function k(bytes[] calldata s) external view returns (bytes memory) { + return this.j(s); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41 +// g(uint256[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41 +// h(uint8[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41 +// i(uint8[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41 +// j(bytes[]): 0x20, 2, 0x40, 0x63, 3, hex"131723", 4, hex"27313741" -> 32, 256, 32, 2, 64, 128, 3, left(0x131723), 4, left(0x27313741) +// k(bytes[]): 0x20, 2, 0x40, 0x63, 3, hex"131723", 4, hex"27313741" -> 32, 256, 32, 2, 64, 128, 3, left(0x131723), 4, left(0x27313741) diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static.sol new file mode 100644 index 000000000000..9c6386e315e1 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static.sol @@ -0,0 +1,25 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[3] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function g(uint256[3] calldata s) external view returns (bytes memory) { + return this.f(s); + } + function h(uint8[3] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function i(uint8[3] calldata s) external view returns (bytes memory) { + return this.h(s); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[3]): 23, 42, 87 -> 32, 96, 23, 42, 87 +// g(uint256[3]): 23, 42, 87 -> 32, 96, 23, 42, 87 +// h(uint8[3]): 23, 42, 87 -> 32, 96, 23, 42, 87 +// i(uint8[3]): 23, 42, 87 -> 32, 96, 23, 42, 87 +// h(uint8[3]): 0xFF23, 0x1242, 0xAB87 -> FAILURE +// i(uint8[3]): 0xAB23, 0x1242, 0xFF87 -> FAILURE diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static_dynamic_static.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static_dynamic_static.sol new file mode 100644 index 000000000000..6e8361d4abd0 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static_dynamic_static.sol @@ -0,0 +1,49 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint8[1][][1] calldata s) external pure returns (bytes memory) { + return msg.data; + } + function f2(uint256[2][][2] calldata s) external pure returns (bytes memory) { + return msg.data; + } + function reenc_f(uint8[1][][1] calldata s) external view returns (bytes memory) { + return this.f(s); + } + function reenc_f2(uint256[2][][2] calldata s) external view returns (bytes memory) { + return this.f2(s); + } + function g() external returns (bytes memory) { + uint8[1][][1] memory m = [new uint8[1][](1)]; + m[0][0][0] = 42; + return this.f(m); + } + function h() external returns (bytes memory) { + uint8[1][][1] memory m = [new uint8[1][](1)]; + m[0][0][0] = 42; + return this.reenc_f(m); + } + function i() external returns (bytes memory) { + uint256[2][][2] memory m = [new uint256[2][](1),new uint256[2][](1)]; + m[0][0][0] = 0x00042; + m[0][0][1] = 0x00142; + m[1][0][0] = 0x10042; + m[1][0][1] = 0x10142; + return this.f2(m); + } + function j() external returns (bytes memory) { + uint256[2][][2] memory m = [new uint256[2][](1),new uint256[2][](1)]; + m[0][0][0] = 0x00042; + m[0][0][1] = 0x00142; + m[1][0][0] = 0x10042; + m[1][0][1] = 0x10142; + return this.reenc_f2(m); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// g() -> 32, 132, hex"15cfcc01", 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000" +// h() -> 32, 132, hex"15cfcc01", 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000" +// i() -> 32, 292, hex"dc0ee233", 32, 64, 160, 1, 0x42, 0x000142, 1, 0x010042, 0x010142, hex"00000000000000000000000000000000000000000000000000000000" +// j() -> 32, 292, hex"dc0ee233", 32, 64, 160, 1, 0x42, 0x000142, 1, 0x010042, 0x010142, hex"00000000000000000000000000000000000000000000000000000000" diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static_index_access.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static_index_access.sol new file mode 100644 index 000000000000..4bf181db3394 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_static_index_access.sol @@ -0,0 +1,25 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[3] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function g(uint256[3][2] calldata s, uint256 which) external view returns (bytes memory) { + return this.f(s[which]); + } + function h(uint8[3] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function i(uint8[3][2] calldata s, uint256 which) external view returns (bytes memory) { + return this.h(s[which]); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[3]): 23, 42, 87 -> 32, 96, 23, 42, 87 +// g(uint256[3][2],uint256): 23, 42, 87, 123, 142, 187, 0 -> 32, 96, 23, 42, 87 +// g(uint256[3][2],uint256): 23, 42, 87, 123, 142, 187, 1 -> 32, 96, 123, 142, 187 +// h(uint8[3]): 23, 42, 87 -> 32, 96, 23, 42, 87 +// i(uint8[3][2],uint256): 23, 42, 87, 123, 142, 187, 0 -> 32, 96, 23, 42, 87 +// i(uint8[3][2],uint256): 23, 42, 87, 123, 142, 187, 1 -> 32, 96, 123, 142, 187 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_struct_dynamic.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_struct_dynamic.sol new file mode 100644 index 000000000000..02322d504416 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_struct_dynamic.sol @@ -0,0 +1,16 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S { uint256[] a; } + function f(S[] calldata s) external pure returns (bytes memory) { + return abi.encode(s); + } + function g(S[] calldata s) external view returns (bytes memory) { + return this.f(s); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f((uint256[])[]): 32, 1, 32, 32, 3, 17, 42, 23 -> 32, 256, 32, 1, 32, 32, 3, 17, 42, 23 +// g((uint256[])[]): 32, 1, 32, 32, 3, 17, 42, 23 -> 32, 256, 32, 1, 32, 32, 3, 17, 42, 23 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_two_dynamic.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_two_dynamic.sol new file mode 100644 index 000000000000..16ba406307ba --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_two_dynamic.sol @@ -0,0 +1,20 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[] calldata s1, uint256[] calldata s2, bool which) external pure returns (bytes memory) { + if (which) + return abi.encode(s1); + else + return abi.encode(s2); + } + function g(uint256[] calldata s1, uint256[] calldata s2, bool which) external view returns (bytes memory) { + return this.f(s1, s2, which); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[],uint256[],bool): 0x60, 0xE0, true, 3, 23, 42, 87, 2, 51, 72 -> 32, 160, 0x20, 3, 23, 42, 87 +// f(uint256[],uint256[],bool): 0x60, 0xE0, false, 3, 23, 42, 87, 2, 51, 72 -> 32, 128, 0x20, 2, 51, 72 +// g(uint256[],uint256[],bool): 0x60, 0xE0, true, 3, 23, 42, 87, 2, 51, 72 -> 32, 160, 0x20, 3, 23, 42, 87 +// g(uint256[],uint256[],bool): 0x60, 0xE0, false, 3, 23, 42, 87, 2, 51, 72 -> 32, 128, 0x20, 2, 51, 72 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_two_static.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_two_static.sol new file mode 100644 index 000000000000..a4d1af66d215 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_two_static.sol @@ -0,0 +1,20 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[3] calldata s1, uint256[2] calldata s2, bool which) external pure returns (bytes memory) { + if (which) + return abi.encode(s1); + else + return abi.encode(s2); + } + function g(uint256[3] calldata s1, uint256[2] calldata s2, bool which) external view returns (bytes memory) { + return this.f(s1, s2, which); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, true -> 32, 96, 23, 42, 87 +// f(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, false -> 32, 64, 51, 72 +// g(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, true -> 32, 96, 23, 42, 87 +// g(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, false -> 32, 64, 51, 72 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_dynamic.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_dynamic.sol new file mode 100644 index 000000000000..29ff15692f4b --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_dynamic.sol @@ -0,0 +1,18 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S { uint256[] a; } + + function f(S calldata s) external returns (bytes memory) { + return abi.encode(s); + } + + function g(S calldata s) external returns (bytes memory) { + return this.f(s); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f((uint256[])): 0x20, 0x20, 3, 42, 23, 17 -> 32, 192, 0x20, 0x20, 3, 42, 23, 17 +// g((uint256[])): 0x20, 0x20, 3, 42, 23, 17 -> 32, 192, 0x20, 0x20, 3, 42, 23, 17 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_simple.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_simple.sol new file mode 100644 index 000000000000..f369321c3623 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_simple.sol @@ -0,0 +1,18 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S { uint256 a; } + + function f(S calldata s) external returns (bytes memory) { + return abi.encode(s); + } + + function g(S calldata s) external returns (bytes memory) { + return this.f(s); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// f((uint256)): 3 -> 32, 32, 3 +// g((uint256)): 3 -> 32, 32, 3 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol new file mode 100644 index 000000000000..d74db942c6a6 --- /dev/null +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol @@ -0,0 +1,13 @@ +contract C { + function f() public returns (uint i) { + assembly { + for {} lt(i, 10) { i := add(i, 1) } + { + if eq(i, 6) { break } + i := add(i, 1) + } + } + } +} +// ---- +// f() -> 6 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol new file mode 100644 index 000000000000..f25a3c06a0bc --- /dev/null +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol @@ -0,0 +1,13 @@ +contract C { + function f() public returns (uint k) { + assembly { + for {let i := 0} lt(i, 10) { i := add(i, 1) } + { + if eq(mod(i, 2), 0) { continue } + k := add(k, 1) + } + } + } +} +// ---- +// f() -> 5 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol new file mode 100644 index 000000000000..3a13e9f0e674 --- /dev/null +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol @@ -0,0 +1,20 @@ +contract C { + function f(uint x) public returns (uint i) { + assembly { + for {} lt(i, 10) { i := add(i, 1) } + { + if eq(x, 0) { i := 2 break } + for {} lt(x, 3) { i := 17 x := 9 } { + if eq(x, 1) { continue } + if eq(x, 2) { break } + } + if eq(x, 4) { i := 90 } + } + } + } +} +// ---- +// f(uint256): 0 -> 2 +// f(uint256): 1 -> 18 +// f(uint256): 2 -> 10 +// f(uint256): 4 -> 91 diff --git a/test/libsolidity/semanticTests/functionCall/base_base_overload.sol b/test/libsolidity/semanticTests/functionCall/base_base_overload.sol new file mode 100644 index 000000000000..ecc29b7a20c4 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/base_base_overload.sol @@ -0,0 +1,50 @@ +contract BaseBase { + uint public x; + uint public y; + function init(uint a, uint b) public { + x = b; + y = a; + } + function init(uint a) public { + x = a + 1; + } +} + +contract Base is BaseBase { + function init(uint a, uint b) public { + x = a; + y = b; + } + function init(uint a) public { + x = a; + } +} + +contract Child is Base { + function cInit(uint c) public { + Base.init(c); + } + function cInit(uint c, uint d) public { + Base.init(c, d); + } + function bInit(uint c) public { + BaseBase.init(c); + } + function bInit(uint c, uint d) public { + BaseBase.init(c, d); + } +} +// ---- +// x() -> 0 +// y() -> 0 +// cInit(uint256): 2 -> +// x() -> 2 +// y() -> 0 +// cInit(uint256,uint256): 3, 3 -> +// x() -> 3 +// y() -> 3 +// bInit(uint256): 4 -> +// x() -> 5 +// bInit(uint256,uint256): 9, 10 -> +// x() -> 10 +// y() -> 9 diff --git a/test/libsolidity/semanticTests/functionCall/base_overload.sol b/test/libsolidity/semanticTests/functionCall/base_overload.sol new file mode 100644 index 000000000000..924431ec53f5 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/base_overload.sol @@ -0,0 +1,29 @@ +contract Base { + uint public x; + uint public y; + function init(uint a, uint b) public { + x = a; + y = b; + } + function init(uint a) public { + x = a; + } +} + +contract Child is Base { + function cInit(uint c) public { + Base.init(c); + } + function cInit(uint c, uint d) public { + Base.init(c, d); + } +} +// ---- +// x() -> 0 +// y() -> 0 +// cInit(uint256): 2 -> +// x() -> 2 +// y() -> 0 +// cInit(uint256,uint256): 3, 3 -> +// x() -> 3 +// y() -> 3 diff --git a/test/libsolidity/semanticTests/shifts.sol b/test/libsolidity/semanticTests/shifts.sol new file mode 100644 index 000000000000..e202f2889799 --- /dev/null +++ b/test/libsolidity/semanticTests/shifts.sol @@ -0,0 +1,9 @@ +contract C { + function f(uint x) public returns (uint y) { + assembly { y := shl(2, x) } + } +} +// ==== +// EVMVersion: >=constantinople +// ---- +// f(uint256): 7 -> 28 diff --git a/test/libsolidity/semanticTests/specialFunctions/abi_functions_member_access.sol b/test/libsolidity/semanticTests/specialFunctions/abi_functions_member_access.sol new file mode 100644 index 000000000000..b255b1880b0e --- /dev/null +++ b/test/libsolidity/semanticTests/specialFunctions/abi_functions_member_access.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure { + abi.encode; + abi.encodePacked; + abi.encodeWithSelector; + abi.encodeWithSignature; + abi.decode; + } +} +// ---- +// f() -> diff --git a/test/libsolidity/semanticTests/structs/conversion/recursive_storage_memory.sol b/test/libsolidity/semanticTests/structs/conversion/recursive_storage_memory.sol new file mode 100644 index 000000000000..e941c94321b1 --- /dev/null +++ b/test/libsolidity/semanticTests/structs/conversion/recursive_storage_memory.sol @@ -0,0 +1,20 @@ +contract CopyTest { + struct Tree { + Tree[] children; + } + Tree storageTree; + + constructor() public { + storageTree.children.length = 2; + storageTree.children[0].children.length = 23; + storageTree.children[1].children.length = 42; + } + + function run() public returns (uint256, uint256, uint256) { + Tree memory memoryTree; + memoryTree = storageTree; + return (memoryTree.children.length, memoryTree.children[0].children.length, memoryTree.children[1].children.length); + } +} +// ---- +// run() -> 2, 23, 42 diff --git a/test/libsolidity/semanticTests/structs/conversion/recursive_storage_memory_complex.sol b/test/libsolidity/semanticTests/structs/conversion/recursive_storage_memory_complex.sol new file mode 100644 index 000000000000..44a9fb51b288 --- /dev/null +++ b/test/libsolidity/semanticTests/structs/conversion/recursive_storage_memory_complex.sol @@ -0,0 +1,46 @@ +contract CopyTest { + struct Tree { + uint256 data; + Tree[] children; + } + Tree storageTree; + + constructor() public { + storageTree.data = 0x42; + storageTree.children.length = 2; + storageTree.children[0].data = 0x4200; + storageTree.children[1].data = 0x4201; + storageTree.children[0].children.length = 3; + for (uint i = 0; i < 3; i++) + storageTree.children[0].children[i].data = 0x420000 + i; + storageTree.children[1].children.length = 4; + for (uint i = 0; i < 4; i++) + storageTree.children[1].children[i].data = 0x420100 + i; + } + + function countData(Tree memory tree) internal returns (uint256 c) { + c = 1; + for (uint i = 0; i < tree.children.length; i++) { + c += countData(tree.children[i]); + } + } + + function copyFromTree(Tree memory tree, uint256[] memory data, uint256 offset) internal returns (uint256) { + data[offset++] = tree.data; + for (uint i = 0; i < tree.children.length; i++) { + offset = copyFromTree(tree.children[i], data, offset); + } + return offset; + } + + function run() public returns (uint256[] memory) { + Tree memory memoryTree; + memoryTree = storageTree; + uint256 length = countData(memoryTree); + uint256[] memory result = new uint256[](length); + copyFromTree(memoryTree, result, 0); + return result; + } +} +// ---- +// run() -> 0x20, 10, 0x42, 0x4200, 0x420000, 0x420001, 0x420002, 0x4201, 0x420100, 0x420101, 0x420102, 0x420103 diff --git a/test/libsolidity/semanticTests/uninitializedFunctionPointer/invalidInConstructor.sol b/test/libsolidity/semanticTests/uninitializedFunctionPointer/invalidInConstructor.sol new file mode 100644 index 000000000000..26c02f3e37d7 --- /dev/null +++ b/test/libsolidity/semanticTests/uninitializedFunctionPointer/invalidInConstructor.sol @@ -0,0 +1,23 @@ +contract C { + + function() internal storedFn; + + bool flag; + + constructor() public { + if (!flag) { + flag = true; + function() internal invalid; + storedFn = invalid; + invalid(); + } + } + function f() public pure {} +} +contract Test { + function f() public { + new C(); + } +} +// ---- +// f() -> FAILURE diff --git a/test/libsolidity/semanticTests/uninitializedFunctionPointer/invalidStoredInConstructor.sol b/test/libsolidity/semanticTests/uninitializedFunctionPointer/invalidStoredInConstructor.sol new file mode 100644 index 000000000000..710348a1ad36 --- /dev/null +++ b/test/libsolidity/semanticTests/uninitializedFunctionPointer/invalidStoredInConstructor.sol @@ -0,0 +1,23 @@ +contract C { + + function() internal storedFn; + + bool flag; + + constructor() public { + if (!flag) { + flag = true; + function() internal invalid; + storedFn = invalid; + storedFn(); + } + } + function f() public pure {} +} +contract Test { + function f() public { + new C(); + } +} +// ---- +// f() -> FAILURE diff --git a/test/libsolidity/semanticTests/uninitializedFunctionPointer/store2.sol b/test/libsolidity/semanticTests/uninitializedFunctionPointer/store2.sol new file mode 100644 index 000000000000..9d37ebed4f29 --- /dev/null +++ b/test/libsolidity/semanticTests/uninitializedFunctionPointer/store2.sol @@ -0,0 +1,39 @@ +pragma solidity ^0.5.7; + +contract InvalidTest { + + function() internal storedFn; + uint public x; + + constructor() public { + uint _y1; + uint _y2; + uint _y3; + uint _y4; + uint _y5; + uint _y6; + uint _y7; + uint _y8; + uint _y9; + uint _y10; + uint _y11; + uint _y12; + uint _y13; + uint _y14; + + + function() internal invalid; + storedFn = invalid; + } + + function run() public { + // this did not always cause revert in the past + storedFn(); + } + + function z() public { + x++; + } +} +// ---- +// run() -> FAILURE diff --git a/test/libsolidity/semanticTests/uninitializedFunctionPointer/storeInConstructor.sol b/test/libsolidity/semanticTests/uninitializedFunctionPointer/storeInConstructor.sol new file mode 100644 index 000000000000..8a11832c8c9f --- /dev/null +++ b/test/libsolidity/semanticTests/uninitializedFunctionPointer/storeInConstructor.sol @@ -0,0 +1,19 @@ +contract InvalidTest { + + function() internal storedFn; + + bool flag; + + constructor() public { + function() internal invalid; + storedFn = invalid; + } + function f() public returns (uint) { + if (flag) return 2; + flag = true; + storedFn(); + } +} +// ---- +// f() -> FAILURE +// f() -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/detect_add_overflow.yul b/test/libsolidity/semanticTests/viaYul/detect_add_overflow.yul new file mode 100644 index 000000000000..f16e4dac589b --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_add_overflow.yul @@ -0,0 +1,18 @@ +contract C { + function f(uint a, uint b) public pure returns (uint x) { + x = a + b; + } + function g(uint8 a, uint8 b) public pure returns (uint8 x) { + x = a + b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(uint256,uint256): 5, 6 -> 11 +// f(uint256,uint256): -2, 1 -> -1 +// f(uint256,uint256): -2, 2 -> FAILURE +// f(uint256,uint256): 2, -2 -> FAILURE +// g(uint8,uint8): 128, 64 -> 192 +// g(uint8,uint8): 128, 127 -> 255 +// g(uint8,uint8): 128, 128 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/explicit_cast_assignment.sol b/test/libsolidity/semanticTests/viaYul/explicit_cast_assignment.sol new file mode 100644 index 000000000000..6a9291c32b04 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/explicit_cast_assignment.sol @@ -0,0 +1,10 @@ +contract C { + function f() public pure returns (uint16 x) { + uint8 y = uint8(0x12345678); + x = y; + } +} +// ==== +// compileViaYul: true +// ---- +// f() -> 0x78 diff --git a/test/libsolidity/semanticTests/viaYul/explicit_cast_function_call.sol b/test/libsolidity/semanticTests/viaYul/explicit_cast_function_call.sol new file mode 100644 index 000000000000..c239b07e447c --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/explicit_cast_function_call.sol @@ -0,0 +1,12 @@ +contract C { + function f(bytes32 b) public pure returns (bytes32 x) { + x = b; + } + function g() public pure returns (bytes32 x) { + x = f(bytes4(uint32(0x12345678))); + } +} +// ==== +// compileViaYul: true +// ---- +// g() -> 0x1234567800000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/viaYul/explicit_cast_local_assignment.sol b/test/libsolidity/semanticTests/viaYul/explicit_cast_local_assignment.sol new file mode 100644 index 000000000000..22bb1f7ea358 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/explicit_cast_local_assignment.sol @@ -0,0 +1,10 @@ +contract C { + function f(uint a) public pure returns (uint8 x) { + uint8 b = uint8(a); + x = b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(uint256): 0x12345678 -> 0x78 diff --git a/test/libsolidity/semanticTests/viaYul/function_entry_checks.sol b/test/libsolidity/semanticTests/viaYul/function_entry_checks.sol new file mode 100644 index 000000000000..3c224032d883 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/function_entry_checks.sol @@ -0,0 +1,30 @@ +contract C { + function f() public returns (uint) { + } + function g(uint x, uint y) public returns (uint) { + } + function h() public payable returns (uint) { + } + function i(bytes32 b) public returns (bytes32) { + } + function j(bool b) public returns (bool) { + } + function k(bytes32 b) public returns (bytes32) { + } + function s() public returns (uint256[] memory) { + } + function t(uint) public pure { + } +} +// === +// compileViaYul: true +// ---- +// f() -> 0 +// g(uint256,uint256): 1, -2 -> 0 +// h(), 1 ether -> 0 +// i(bytes32), 1 ether: 2 -> FAILURE +// i(bytes32): 2 -> 0 +// j(bool): true -> false +// k(bytes32): 0x31 -> 0x00 +// s(): hex"4200ef" -> 0x20, 0 +// t(uint256) -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/implicit_cast_assignment.sol b/test/libsolidity/semanticTests/viaYul/implicit_cast_assignment.sol new file mode 100644 index 000000000000..d6bba1bf55a3 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/implicit_cast_assignment.sol @@ -0,0 +1,14 @@ +// Tests IRGeneratorForStatements::visit(Assignment const& _assignment) +contract C { + function f() public pure returns (uint16 x) { + uint8 y; + assembly { + y := 0x12345678 + } + x = y; + } +} +// ==== +// compileViaYul: true +// ---- +// f() -> 0x78 diff --git a/test/libsolidity/semanticTests/viaYul/implicit_cast_function_call.sol b/test/libsolidity/semanticTests/viaYul/implicit_cast_function_call.sol new file mode 100644 index 000000000000..47a9b1cea5a9 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/implicit_cast_function_call.sol @@ -0,0 +1,17 @@ +// IRGeneratorForStatements::visit(FunctionCall const& _functionCall) +contract C { + function f(uint b) public pure returns (uint x) { + x = b; + } + function g() public pure returns (uint x) { + uint8 a; + assembly { + a := 0x12345678 + } + x = f(a); + } +} +// ==== +// compileViaYul: true +// ---- +// g() -> 0x78 diff --git a/test/libsolidity/semanticTests/viaYul/implicit_cast_local_assignment.sol b/test/libsolidity/semanticTests/viaYul/implicit_cast_local_assignment.sol new file mode 100644 index 000000000000..c99b50969034 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/implicit_cast_local_assignment.sol @@ -0,0 +1,13 @@ +// IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDeclStatement) +contract C { + function f() public pure returns (uint y) { + uint8 a; + assembly { a := 0x12345678 } + uint z = a; + y = z; + } +} +// ==== +// compileViaYul: true +// ---- +// f() -> 0x78 diff --git a/test/libsolidity/semanticTests/viaYul/local_address_assignment.sol b/test/libsolidity/semanticTests/viaYul/local_address_assignment.sol new file mode 100644 index 000000000000..84c38a324a76 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/local_address_assignment.sol @@ -0,0 +1,10 @@ +contract C { + function f(address a) public pure returns (address x) { + address b = a; + x = b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(address): 0x1234 -> 0x1234 diff --git a/test/libsolidity/semanticTests/viaYul/local_assignment.sol b/test/libsolidity/semanticTests/viaYul/local_assignment.sol new file mode 100644 index 000000000000..b423cbf25b77 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/local_assignment.sol @@ -0,0 +1,10 @@ +contract C { + function f(uint a) public pure returns (uint x) { + uint b = a; + x = b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(uint256): 6 -> 6 diff --git a/test/libsolidity/semanticTests/viaYul/local_bool_assignment.sol b/test/libsolidity/semanticTests/viaYul/local_bool_assignment.sol new file mode 100644 index 000000000000..07189b6fb73a --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/local_bool_assignment.sol @@ -0,0 +1,10 @@ +contract C { + function f(bool a) public pure returns (bool x) { + bool b = a; + x = b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(bool): true -> true diff --git a/test/libsolidity/semanticTests/viaYul/simple_assignment.sol b/test/libsolidity/semanticTests/viaYul/simple_assignment.sol new file mode 100644 index 000000000000..4ed155322536 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/simple_assignment.sol @@ -0,0 +1,10 @@ +contract C { + function f(uint a, uint b) public pure returns (uint x, uint y) { + x = a; + y = b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(uint256,uint256): 5, 6 -> 5, 6 diff --git a/test/libsolidity/semanticTests/viaYul/simple_inline_asm.sol b/test/libsolidity/semanticTests/viaYul/simple_inline_asm.sol new file mode 100644 index 000000000000..f0779639feec --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/simple_inline_asm.sol @@ -0,0 +1,17 @@ +contract C { + function f() public pure returns (uint32 x) { + uint32 a; + uint32 b; + uint32 c; + assembly { + a := 1 + b := 2 + c := 3 + } + x = a + b + c; + } +} +// ==== +// compileViaYul: true +// ---- +// f() -> 6 diff --git a/test/libsolidity/semanticTests/viaYul/smoke_test.sol b/test/libsolidity/semanticTests/viaYul/smoke_test.sol new file mode 100644 index 000000000000..0e0a7d4d545d --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/smoke_test.sol @@ -0,0 +1,6 @@ +contract C { +} +// ==== +// compileViaYul: true +// ---- +// f() -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/various_inline_asm.sol b/test/libsolidity/semanticTests/viaYul/various_inline_asm.sol new file mode 100644 index 000000000000..ebdbb7522863 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/various_inline_asm.sol @@ -0,0 +1,23 @@ +contract C { + function f() public pure returns (uint32 x) { + uint32 a; + uint32 b; + uint32 c; + assembly { + function myAwesomeFunction(param) -> returnMe { + let localVar := 10 + returnMe := add(localVar, param) + } + let abc := sub(10, a) + let xyz := 20 + a := abc + b := myAwesomeFunction(30) + c := xyz + } + x = a + b + c; + } +} +// ==== +// compileViaYul: true +// ---- +// f() -> 70 diff --git a/test/libsolidity/semanticTests/viaYul/virtual_functions.sol b/test/libsolidity/semanticTests/viaYul/virtual_functions.sol new file mode 100644 index 000000000000..38d8b8f87d37 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/virtual_functions.sol @@ -0,0 +1,32 @@ +contract X { + function f() public returns (uint x) { + x = g(); + } + function g() public returns (uint x) { + x = 2; + } +} +contract C is X { + function f1() public returns (uint x) { + // direct call + x = g(); + } + function f2() public returns (uint x) { + // call via base + x = f(); + } + function f3() public returns (uint x) { + // explicit call via base + //x = super.g(); + } + function g() public returns (uint x) { + x = 3; + } +} +// === +// compileViaYul: true +// ---- +// f() -> 3 +// f1() -> 3 +// f2() -> 3 +// g() -> 3 diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and.sol new file mode 100644 index 000000000000..0bf30e46d568 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public returns (bool) { + x = 0; + bool b = (f() == 0) && (f() == 0); + assert(x == 1); + assert(!b); + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_fail.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_fail.sol new file mode 100644 index 000000000000..ee2307521cb2 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_fail.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public returns (bool) { + x = 0; + bool b = (f() == 0) && (f() == 0); + assert(x == 1); + assert(b); + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (227-236): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_inside_branch.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_inside_branch.sol new file mode 100644 index 000000000000..f49a572cfa47 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_inside_branch.sol @@ -0,0 +1,28 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g(bool a) public returns (bool) { + bool b; + if (a) { + x = 0; + b = (f() == 0) && (f() == 0); + assert(x == 1); + assert(!b); + } else { + x = 100; + b = (f() > 0) && (f() > 0); + assert(x == 102); + // Should fail. + assert(!b); + } + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (362-372): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_need_both.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_need_both.sol new file mode 100644 index 000000000000..1a4b1edf5ef6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_need_both.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public returns (bool) { + x = 0; + bool b = (f() > 0) && (f() > 0); + assert(x == 2); + assert(b); + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_need_both_fail.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_need_both_fail.sol new file mode 100644 index 000000000000..c3b6eb8fc140 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_and_need_both_fail.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public returns (bool) { + x = 0; + bool b = (f() > 0) && (f() > 0); + assert(x == 2); + assert(!b); + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (225-235): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol index 0b2e080fb91a..55c218968e1b 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or.sol @@ -6,15 +6,13 @@ contract c { x = x + 1; return x; } - function g() public { + function g() public returns (bool) { x = 0; - assert((f() > 0) || (f() > 0)); - // This assertion should NOT fail. - // It currently does because the SMTChecker does not - // handle short-circuiting properly and inlines f() twice. + bool b = (f() > 0) || (f() > 0); assert(x == 1); + assert(b); + return b; } } // ---- // Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here -// Warning: (344-358): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_fail.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_fail.sol new file mode 100644 index 000000000000..31abebec90d5 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_fail.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public returns (bool) { + x = 0; + bool b = (f() > 0) || (f() > 0); + assert(x == 1); + assert(!b); + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (225-235): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_inside_branch.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_inside_branch.sol new file mode 100644 index 000000000000..c699eaac2706 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_inside_branch.sol @@ -0,0 +1,28 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g(bool a) public returns (bool) { + bool b; + if (a) { + x = 0; + b = (f() > 0) || (f() > 0); + assert(x == 1); + assert(b); + } else { + x = 100; + b = (f() == 0) || (f() > 0); + assert(x == 102); + // Should fail. + assert(!b); + } + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (360-370): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_need_both.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_need_both.sol new file mode 100644 index 000000000000..323ba8f94600 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_need_both.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public returns (bool) { + x = 0; + bool b = (f() > 1) || (f() > 1); + assert(x == 2); + assert(b); + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_need_both_fail.sol b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_need_both_fail.sol new file mode 100644 index 000000000000..4169717cf94c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/control_flow/short_circuit_or_need_both_fail.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract c { + uint x; + function f() internal returns (uint) { + x = x + 1; + return x; + } + function g() public returns (bool) { + x = 0; + bool b = (f() > 1) || (f() > 1); + assert(x == 2); + assert(!b); + return b; + } +} +// ---- +// Warning: (101-106): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (225-235): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/function_inline_chain.sol b/test/libsolidity/smtCheckerTests/functions/function_inline_chain.sol new file mode 100644 index 000000000000..2647e0771d71 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/function_inline_chain.sol @@ -0,0 +1,41 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + uint y; + uint z; + + function f() public { + if (x == 1) + x = 2; + else + x = 1; + g(); + assert(y == 1); + } + + function g() public { + y = 1; + h(); + assert(z == 1); + } + + function h() public { + z = 1; + x = 1; + f(); + // This fails for the following calls to the contract: + // h() + // g() h() + // It does not fail for f() g() h() because in that case + // h() will not inline f() since it already is in the callstack. + assert(x == 1); + } +} +// ---- +// Warning: (271-274): Assertion checker does not support recursive function calls. +// Warning: (140-143): Assertion checker does not support recursive function calls. +// Warning: (483-497): Assertion violation happens here +// Warning: (201-204): Assertion checker does not support recursive function calls. +// Warning: (483-497): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var.sol b/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var.sol new file mode 100644 index 000000000000..9ea612aa3ccd --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + function f() internal { + require(x < 10000); + x = x + 1; + } + function g(bool b) public { + x = 0; + if (b) + f(); + // Should fail for `b == true`. + assert(x == 0); + } +} +// ---- +// Warning: (209-223): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var_2.sol b/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var_2.sol new file mode 100644 index 000000000000..63e4c6d06791 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var_2.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + function f() internal { + require(x < 10000); + x = x + 1; + } + function g(bool b) public { + x = 0; + if (b) + f(); + else + f(); + assert(x == 1); + } +} diff --git a/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var_3.sol b/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var_3.sol new file mode 100644 index 000000000000..e5f312e93a98 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/function_inside_branch_modify_state_var_3.sol @@ -0,0 +1,28 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + function f() internal { + require(x < 10000); + x = x + 1; + } + function g(bool b) public { + x = 0; + if (b) + f(); + // Should fail for `b == true`. + assert(x == 0); + } + function h(bool b) public { + x = 0; + if (!b) + f(); + // Should fail for `b == false`. + assert(x == 0); + } + +} +// ---- +// Warning: (209-223): Assertion violation happens here +// Warning: (321-335): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/functions_external_1.sol b/test/libsolidity/smtCheckerTests/functions/functions_external_1.sol index 16482e7a772f..a12f04f7a066 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_external_1.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_external_1.sol @@ -17,5 +17,4 @@ contract C } } // ---- -// Warning: (119-122): Assertion checker does not yet support the type of this variable. // Warning: (240-254): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol b/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol index 1e704c9dbe01..b09b013ceffd 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol @@ -17,5 +17,4 @@ contract C } } // ---- -// Warning: (139-142): Assertion checker does not yet support the type of this variable. // Warning: (280-304): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/functions_external_3.sol b/test/libsolidity/smtCheckerTests/functions/functions_external_3.sol index dd36ec7365a9..517deaca2c2d 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_external_3.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_external_3.sol @@ -18,5 +18,4 @@ contract C } } // ---- -// Warning: (146-149): Assertion checker does not yet support the type of this variable. // Warning: (338-362): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inline_assembly/empty.sol b/test/libsolidity/smtCheckerTests/inline_assembly/empty.sol new file mode 100644 index 000000000000..bb221e58b84c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inline_assembly/empty.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + assembly { + } + } +} +// ---- +// Warning: (76-90): Assertion checker does not support inline assembly. diff --git a/test/libsolidity/smtCheckerTests/inline_assembly/local_var.sol b/test/libsolidity/smtCheckerTests/inline_assembly/local_var.sol new file mode 100644 index 000000000000..888f7ef16286 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inline_assembly/local_var.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x) public pure returns (uint) { + assembly { + x := 2 + } + return x; + } +} +// ---- +// Warning: (97-121): Assertion checker does not support inline assembly. diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_assignment_outside_branch.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_assignment_outside_branch.sol new file mode 100644 index 000000000000..b3c9ab0d6d42 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_assignment_outside_branch.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + address owner; + + modifier onlyOwner { + if (msg.sender == owner) _; + } + + function f() public onlyOwner { + } + + function g(uint y) public { + y = 1; + if (y > x) f(); + } +} diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch.sol new file mode 100644 index 000000000000..45202db3c328 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C { + address owner; + modifier onlyOwner { + if (msg.sender == owner) _; + } + function g() public onlyOwner { + } + function f(uint x) public { + if (x > 0) g(); + } +} diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment.sol new file mode 100644 index 000000000000..089cfffe3136 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment.sol @@ -0,0 +1,23 @@ +pragma experimental SMTChecker; + +contract C { + uint x; + address owner; + + modifier onlyOwner { + if (msg.sender == owner) _; + } + + function f() public onlyOwner { + x = 0; + } + function g(uint y) public { + x = 1; + if (y > 0) + f(); + // Fails for {y = >0, msg.sender == owner, x = 0}. + assert(x > 0); + } +} +// ---- +// Warning: (287-300): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment_branch.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment_branch.sol new file mode 100644 index 000000000000..eb56bfdd51ef --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment_branch.sol @@ -0,0 +1,27 @@ +pragma experimental SMTChecker; + +contract C { + uint x; + address owner; + + modifier onlyOwner { + if (msg.sender == owner) { + require(x > 0); + _; + } + } + + function f() public onlyOwner { + // Condition is always true due to `require(x > 0)` in the modifier. + if (x > 0) + x -= 1; + } + function g(uint y) public { + x = 2; + if (y > 0) + f(); + assert(x > 0); + } +} +// ---- +// Warning: (266-271): Condition is always true. diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment_multi_branches.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment_multi_branches.sol new file mode 100644 index 000000000000..387047bb716f --- /dev/null +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_inside_branch_assignment_multi_branches.sol @@ -0,0 +1,35 @@ +pragma experimental SMTChecker; + +contract C { + uint x; + address owner; + + modifier onlyOwner { + if (msg.sender == owner) { + require(x > 0); + _; + } + } + + function f() public onlyOwner { + x -= 1; + h(); + } + function h() public onlyOwner { + require(x < 10000); + x += 2; + } + function g(uint y) public { + require(y > 0 && y < 10000); + require(msg.sender == owner); + x = y; + if (y > 1) { + f(); + assert(x == y + 1); + } + // Fails for {y = 0, x = 0}. + assert(x == 0); + } +} +// ---- +// Warning: (461-475): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_add.sol b/test/libsolidity/smtCheckerTests/operators/compound_add.sol new file mode 100644 index 000000000000..fcbd7edc7d3f --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_add.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x) public pure { + require(x < 100); + uint y = 100; + y += y + x; + assert(y < 300); + assert(y < 110); + } +} +// ---- +// Warning: (151-166): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol b/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol new file mode 100644 index 000000000000..816aff23ad7f --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint x, uint p) public { + require(x < 100); + require(array[p] == 100); + array[p] += array[p] + x; + assert(array[p] < 300); + assert(array[p] < 110); + } +} +// ---- +// Warning: (202-224): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_add_chain.sol b/test/libsolidity/smtCheckerTests/operators/compound_add_chain.sol new file mode 100644 index 000000000000..be6c363e1a08 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_add_chain.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint a = 1; + uint b = 3; + uint c = 7; + a += b += c; + assert(b == 10 && a == 11); + a += (b += c); + assert(b == 17 && a == 28); + a += a += a; + assert(a == 112); + } +} diff --git a/test/libsolidity/smtCheckerTests/operators/compound_add_mapping.sol b/test/libsolidity/smtCheckerTests/operators/compound_add_mapping.sol new file mode 100644 index 000000000000..f342ab97d8c1 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_add_mapping.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + mapping (uint => uint) map; + function f(uint x, uint p) public { + require(x < 100); + require(map[p] == 100); + map[p] += map[p] + x; + assert(map[p] < 300); + assert(map[p] < 110); + } +} +// ---- +// Warning: (208-228): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_mul.sol b/test/libsolidity/smtCheckerTests/operators/compound_mul.sol new file mode 100644 index 000000000000..a62cefa0661b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_mul.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x) public pure { + require(x < 10); + uint y = 10; + y *= y + x; + assert(y <= 190); + assert(y < 50); + } +} +// ---- +// Warning: (150-164): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_mul_array_index.sol b/test/libsolidity/smtCheckerTests/operators/compound_mul_array_index.sol new file mode 100644 index 000000000000..ea837801df65 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_mul_array_index.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint x, uint p) public { + require(x < 10); + require(array[p] == 10); + array[p] *= array[p] + x; + assert(array[p] <= 190); + assert(array[p] < 50); + } +} +// ---- +// Warning: (201-222): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_mul_mapping.sol b/test/libsolidity/smtCheckerTests/operators/compound_mul_mapping.sol new file mode 100644 index 000000000000..fbcb5d88c9c8 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_mul_mapping.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + mapping (uint => uint) map; + function f(uint x, uint p) public { + require(x < 10); + require(map[p] == 10); + map[p] *= map[p] + x; + assert(map[p] <= 190); + assert(map[p] < 50); + } +} +// ---- +// Warning: (207-226): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_sub.sol b/test/libsolidity/smtCheckerTests/operators/compound_sub.sol new file mode 100644 index 000000000000..6e9cfa3cdcf6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_sub.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x) public pure { + require(x < 100); + uint y = 200; + y -= y - x; + assert(y >= 0); + assert(y < 90); + } +} +// ---- +// Warning: (150-164): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_sub_array_index.sol b/test/libsolidity/smtCheckerTests/operators/compound_sub_array_index.sol new file mode 100644 index 000000000000..b966337e9aaa --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_sub_array_index.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint x, uint p) public { + require(x < 100); + require(array[p] == 200); + array[p] -= array[p] - x; + assert(array[p] >= 0); + assert(array[p] < 90); + } +} +// ---- +// Warning: (201-222): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_sub_mapping.sol b/test/libsolidity/smtCheckerTests/operators/compound_sub_mapping.sol new file mode 100644 index 000000000000..df20d3712802 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/compound_sub_mapping.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C +{ + mapping (uint => uint) map; + function f(uint x, uint p) public { + require(x < 100); + require(map[p] == 200); + map[p] -= map[p] - x; + assert(map[p] >= 0); + assert(map[p] < 90); + } +} +// ---- +// Warning: (207-226): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/mod_even.sol b/test/libsolidity/smtCheckerTests/operators/mod_even.sol new file mode 100644 index 000000000000..184653eaca3a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/mod_even.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x) public pure { + require(x < 10000); + uint y = x * 2; + assert((y % 2) == 0); + } +} diff --git a/test/libsolidity/smtCheckerTests/operators/mod_n.sol b/test/libsolidity/smtCheckerTests/operators/mod_n.sol new file mode 100644 index 000000000000..8df3ac4be733 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/mod_n.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x, uint y) public pure { + require(y > 0); + uint z = x % y; + assert(z < y); + } +} diff --git a/test/libsolidity/smtCheckerTests/operators/mod_n_uint16.sol b/test/libsolidity/smtCheckerTests/operators/mod_n_uint16.sol new file mode 100644 index 000000000000..fc6c7a8d6675 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/mod_n_uint16.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint16 x, uint16 y) public pure { + require(y > 0); + uint z = x % y; + assert(z < 100_000); + } +} diff --git a/test/libsolidity/smtCheckerTests/operators/unary_add.sol b/test/libsolidity/smtCheckerTests/operators/unary_add.sol new file mode 100644 index 000000000000..9cb463696f55 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/unary_add.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint x = 2; + uint a = ++x; + assert(x == 3); + assert(a == 3); + uint b = x++; + assert(x == 4); + // Should fail. + assert(b < 3); + } +} +// ---- +// Warning: (194-207): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/unary_add_array.sol b/test/libsolidity/smtCheckerTests/operators/unary_add_array.sol new file mode 100644 index 000000000000..c6370b34f8cd --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/unary_add_array.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint x) public { + array[x] = 2; + uint a = ++array[x]; + assert(array[x] == 3); + assert(a == 3); + uint b = array[x]++; + assert(array[x] == 4); + // Should fail. + assert(b < 3); + } +} +// ---- +// Warning: (240-253): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/unary_add_mapping.sol b/test/libsolidity/smtCheckerTests/operators/unary_add_mapping.sol new file mode 100644 index 000000000000..e75d5e113604 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/unary_add_mapping.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + mapping (uint => uint) map; + function f(uint x) public { + map[x] = 2; + uint a = ++map[x]; + assert(map[x] == 3); + assert(a == 3); + uint b = map[x]++; + assert(map[x] == 4); + // Should fail. + assert(b < 3); + } +} +// ---- +// Warning: (244-257): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/unary_sub.sol b/test/libsolidity/smtCheckerTests/operators/unary_sub.sol new file mode 100644 index 000000000000..3a2aaf408fd6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/unary_sub.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint x = 5; + uint a = --x; + assert(x == 4); + assert(a == 4); + uint b = x--; + assert(x == 3); + // Should fail. + assert(b > 4); + } +} +// ---- +// Warning: (194-207): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/unary_sub_array.sol b/test/libsolidity/smtCheckerTests/operators/unary_sub_array.sol new file mode 100644 index 000000000000..220ae0a444ae --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/unary_sub_array.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + uint[] array; + function f(uint x) public { + array[x] = 5; + uint a = --array[x]; + assert(array[x] == 4); + assert(a == 4); + uint b = array[x]--; + assert(array[x] == 3); + // Should fail. + assert(b > 4); + } +} +// ---- +// Warning: (240-253): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/unary_sub_mapping.sol b/test/libsolidity/smtCheckerTests/operators/unary_sub_mapping.sol new file mode 100644 index 000000000000..a1c3df860992 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/unary_sub_mapping.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + mapping (uint => uint) map; + function f(uint x) public { + map[x] = 5; + uint a = --map[x]; + assert(map[x] == 4); + assert(a == 4); + uint b = map[x]--; + assert(map[x] == 3); + // Should fail. + assert(b > 4); + } +} +// ---- +// Warning: (244-257): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/special/this.sol b/test/libsolidity/smtCheckerTests/special/this.sol new file mode 100644 index 000000000000..06fc07404fd7 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/special/this.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(address a) public view { + assert(a == address(this)); + } +} +// ---- +// Warning: (85-111): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/special/this_state.sol b/test/libsolidity/smtCheckerTests/special/this_state.sol new file mode 100644 index 000000000000..48e496d7a29d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/special/this_state.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + address thisAddr; + function f(address a) public { + require(a == address(this)); + thisAddr = a; + assert(thisAddr == address(this)); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/address_balance.sol b/test/libsolidity/smtCheckerTests/types/address_balance.sol new file mode 100644 index 000000000000..445888f05d9c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/address_balance.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(address a, address b) public view { + uint x = b.balance + 1000 ether; + assert(a.balance > b.balance); + } +} +// ---- +// Warning: (96-102): Unused local variable. +// Warning: (131-160): Assertion violation happens here +// Warning: (105-127): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_call.sol b/test/libsolidity/smtCheckerTests/types/address_call.sol new file mode 100644 index 000000000000..05c4234062d4 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/address_call.sol @@ -0,0 +1,23 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + mapping (uint => uint) map; + function f(address a, bytes memory data) public { + x = 0; + map[0] = 0; + mapping (uint => uint) storage localMap = map; + (bool success, bytes memory ret) = a.call(data); + assert(success); + assert(x == 0); + assert(map[0] == 0); + assert(localMap[0] == 0); + } +} +// ==== +// EVMVersion: >spuriousDragon +// ---- +// Warning: (224-240): Unused local variable. +// Warning: (209-256): Assertion checker does not yet support such variable declarations. +// Warning: (260-275): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_delegatecall.sol b/test/libsolidity/smtCheckerTests/types/address_delegatecall.sol new file mode 100644 index 000000000000..ce49350ef394 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/address_delegatecall.sol @@ -0,0 +1,23 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + mapping (uint => uint) map; + function f(address a, bytes memory data) public { + x = 0; + map[0] = 0; + mapping (uint => uint) storage localMap = map; + (bool success, bytes memory ret) = a.delegatecall(data); + assert(success); + assert(x == 0); + assert(map[0] == 0); + assert(localMap[0] == 0); + } +} +// ==== +// EVMVersion: >spuriousDragon +// ---- +// Warning: (224-240): Unused local variable. +// Warning: (209-264): Assertion checker does not yet support such variable declarations. +// Warning: (268-283): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_staticcall.sol b/test/libsolidity/smtCheckerTests/types/address_staticcall.sol new file mode 100644 index 000000000000..5336a14e61ef --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/address_staticcall.sol @@ -0,0 +1,23 @@ +pragma experimental SMTChecker; + +contract C +{ + uint x; + mapping (uint => uint) map; + function f(address a, bytes memory data) public { + x = 0; + map[0] = 0; + mapping (uint => uint) storage localMap = map; + (bool success, bytes memory ret) = a.staticcall(data); + assert(success); + assert(x == 0); + assert(map[0] == 0); + assert(localMap[0] == 0); + } +} +// ==== +// EVMVersion: >spuriousDragon +// ---- +// Warning: (224-240): Unused local variable. +// Warning: (209-262): Assertion checker does not yet support such variable declarations. +// Warning: (266-281): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_transfer.sol b/test/libsolidity/smtCheckerTests/types/address_transfer.sol new file mode 100644 index 000000000000..08a5244c3015 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/address_transfer.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(address payable a) public { + uint x = 100; + require(x == a.balance); + a.transfer(600); + // This fails since a == this is possible. + assert(a.balance == 700); + } +} +// ---- +// Warning: (131-146): Insufficient funds happens here +// Warning: (131-146): Assertion checker does not yet implement this type. +// Warning: (195-219): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_transfer_2.sol b/test/libsolidity/smtCheckerTests/types/address_transfer_2.sol new file mode 100644 index 000000000000..d2f9e69398ab --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/address_transfer_2.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x, address payable a, address payable b) public { + require(a != b); + require(x == 100); + require(x == a.balance); + require(a.balance == b.balance); + a.transfer(600); + b.transfer(100); + // Fails since a == this is possible. + assert(a.balance > b.balance); + } +} +// ---- +// Warning: (217-232): Insufficient funds happens here +// Warning: (217-232): Assertion checker does not yet implement this type. +// Warning: (236-251): Insufficient funds happens here +// Warning: (236-251): Assertion checker does not yet implement this type. +// Warning: (295-324): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_transfer_insufficient.sol b/test/libsolidity/smtCheckerTests/types/address_transfer_insufficient.sol new file mode 100644 index 000000000000..d3bfbe302f3d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/address_transfer_insufficient.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(address payable a, address payable b) public { + require(a.balance == 0); + a.transfer(600); + b.transfer(1000); + // Fails since a == this is possible. + assert(a.balance == 600); + } +} +// ---- +// Warning: (134-149): Insufficient funds happens here +// Warning: (134-149): Assertion checker does not yet implement this type. +// Warning: (153-169): Insufficient funds happens here +// Warning: (153-169): Assertion checker does not yet implement this type. +// Warning: (213-237): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/contract.sol b/test/libsolidity/smtCheckerTests/types/contract.sol new file mode 100644 index 000000000000..3d8af499eb5f --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/contract.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(C c, C d) public pure { + assert(c == d); + } +} +// ---- +// Warning: (84-98): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/contract_2.sol b/test/libsolidity/smtCheckerTests/types/contract_2.sol new file mode 100644 index 000000000000..8cb8bc9cfbc6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/contract_2.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract D +{ + uint x; +} + +contract C +{ + function f(D c, D d) public pure { + assert(c == d); + } +} +// ---- +// Warning: (109-123): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/contract_3.sol b/test/libsolidity/smtCheckerTests/types/contract_3.sol new file mode 100644 index 000000000000..5508a6263bcc --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/contract_3.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(C c, C d, C e) public pure { + require(c == d); + require(d == e); + assert(c == e); + } +} diff --git a/test/libsolidity/smtCheckerTests/types/contract_address_conversion.sol b/test/libsolidity/smtCheckerTests/types/contract_address_conversion.sol new file mode 100644 index 000000000000..96baf62a5a5d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/contract_address_conversion.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(C c, address a) public pure { + assert(address(c) == a); + } +} +// ---- +// Warning: (90-113): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/contract_address_conversion_2.sol b/test/libsolidity/smtCheckerTests/types/contract_address_conversion_2.sol new file mode 100644 index 000000000000..e37c7acba440 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/contract_address_conversion_2.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(C c, C d) public pure { + assert(address(c) == address(c)); + address a = address(c); + require(c == d); + assert(a == address(d)); + } +} diff --git a/test/libsolidity/smtCheckerTestsJSON/multi.json b/test/libsolidity/smtCheckerTestsJSON/multi.json index df45fbf4ba5b..c44198234c68 100644 --- a/test/libsolidity/smtCheckerTestsJSON/multi.json +++ b/test/libsolidity/smtCheckerTestsJSON/multi.json @@ -3,9 +3,9 @@ { "smtlib2responses": { - "0x47826ec8b83cfec30171b34f944aed6c33ecd12f02b9ad522ca45317c9bd5f45": "sat\n((|EVALEXPR_0| 1))", - "0x734c058efe9d6ec224de3cf2bdfa6c936a1c9c159c040c128d631145b2fed195": "unsat\n", - "0xa0c072acdbe5181dd56cbad8960cb5bb0b9e97fd598cfd895467bd73bbcca028": "sat\n((|EVALEXPR_0| 0))" + "0x47a038dd9021ecb218726ea6bf1f75c215a50b1981bae4341e89c9f2b7ac5db7": "sat\n((|EVALEXPR_0| 1))", + "0xf057b272f2ceb99a2f714cb132960babdeedfb84ff8ffb96106a58bc0c2060cb": "sat\n((|EVALEXPR_0| 0))", + "0xf49b9d0eb7b6d2f2ac9e1604288e52ee1a08cda57058e26d7843ed109ca6d7c9": "unsat\n" } } } diff --git a/test/libsolidity/smtCheckerTestsJSON/simple.json b/test/libsolidity/smtCheckerTestsJSON/simple.json index b82d93b8c84a..f9935ab0106f 100644 --- a/test/libsolidity/smtCheckerTestsJSON/simple.json +++ b/test/libsolidity/smtCheckerTestsJSON/simple.json @@ -3,7 +3,7 @@ { "smtlib2responses": { - "0xa0c072acdbe5181dd56cbad8960cb5bb0b9e97fd598cfd895467bd73bbcca028": "sat\n((|EVALEXPR_0| 0))\n" + "0xf057b272f2ceb99a2f714cb132960babdeedfb84ff8ffb96106a58bc0c2060cb": "sat\n((|EVALEXPR_0| 0))\n" } } } diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol index bfcbbef51d41..9ffba6fa2b7a 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol @@ -6,4 +6,4 @@ contract C { } } // ---- -// TypeError: (92-126): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (92-116): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/period_in_identifer.sol b/test/libsolidity/syntaxTests/inlineAssembly/period_in_identifer.sol new file mode 100644 index 000000000000..6788c891fae0 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/period_in_identifer.sol @@ -0,0 +1,10 @@ +contract C { + function f() pure public { + // Periods are part of identifiers in assembly, + // but not in Solidity. This tests that this scanner + // setting is properly reset early enough. + assembly { } + C.f(); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/specialFunctions/abi_functions_member_access.sol b/test/libsolidity/syntaxTests/specialFunctions/abi_functions_member_access.sol new file mode 100644 index 000000000000..c1f6537940da --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abi_functions_member_access.sol @@ -0,0 +1,15 @@ +contract C { + function f() public pure { + abi.encode; + abi.encodePacked; + abi.encodeWithSelector; + abi.encodeWithSignature; + abi.decode; + } +} +// ---- +// Warning: (52-62): Statement has no effect. +// Warning: (72-88): Statement has no effect. +// Warning: (98-120): Statement has no effect. +// Warning: (130-153): Statement has no effect. +// Warning: (163-173): Statement has no effect. diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index 8782fe01d7ae..397550e5dcff 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -550,6 +550,11 @@ void TestFileParser::Scanner::scanNextToken() token = selectToken(Token::Whitespace); else if (isEndOfLine()) token = selectToken(Token::EOS); + else + throw Error( + Error::Type::ParserError, + "Unexpected character: '" + string{current()} + "'" + ); break; } } diff --git a/test/libsolidity/util/TestFileParserTests.cpp b/test/libsolidity/util/TestFileParserTests.cpp index 03325dea2e8c..38e29968cce8 100644 --- a/test/libsolidity/util/TestFileParserTests.cpp +++ b/test/libsolidity/util/TestFileParserTests.cpp @@ -775,6 +775,14 @@ BOOST_AUTO_TEST_CASE(call_arrow_missing) BOOST_REQUIRE_THROW(parse(source), langutil::Error); } +BOOST_AUTO_TEST_CASE(call_unexpected_character) +{ + char const* source = R"( + // f() -> ?? + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index 21e3e591f957..485590fecf67 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -40,7 +40,6 @@ using namespace std; using namespace langutil; using namespace yul; -using namespace dev::solidity; namespace { @@ -55,10 +54,7 @@ void yul::test::printErrors(ErrorList const& _errors) SourceReferenceFormatter formatter(cout); for (auto const& error: _errors) - formatter.printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + formatter.printErrorInformation(*error); } @@ -66,7 +62,10 @@ pair, shared_ptr> yul::test::parse(strin { AssemblyStack stack( dev::test::Options::get().evmVersion(), - _yul ? AssemblyStack::Language::Yul : AssemblyStack::Language::StrictAssembly + _yul ? AssemblyStack::Language::Yul : AssemblyStack::Language::StrictAssembly, + dev::test::Options::get().optimize ? + dev::solidity::OptimiserSettings::standard() : + dev::solidity::OptimiserSettings::minimal() ); if (!stack.parseAndAnalyze("", _source) || !stack.errors().empty()) BOOST_FAIL("Invalid source."); diff --git a/test/libyul/Inliner.cpp b/test/libyul/Inliner.cpp index 631cda086d43..242828029811 100644 --- a/test/libyul/Inliner.cpp +++ b/test/libyul/Inliner.cpp @@ -37,7 +37,6 @@ using namespace std; using namespace dev; using namespace yul; using namespace yul::test; -using namespace dev::solidity; namespace { @@ -83,8 +82,8 @@ BOOST_AUTO_TEST_CASE(simple_inside_structures) "}" "}"), "g,f"); BOOST_CHECK_EQUAL(inlinableFunctions("{" + "function g(a:u256) -> b:u256 { b := a }" "for {" - "function g(a:u256) -> b:u256 { b := a }" "} 1:u256 {" "function f() -> x:u256 { x := g(2:u256) }" "}" diff --git a/test/libyul/Metrics.cpp b/test/libyul/Metrics.cpp index 185a3755217c..3fa849c23fba 100644 --- a/test/libyul/Metrics.cpp +++ b/test/libyul/Metrics.cpp @@ -110,6 +110,69 @@ BOOST_AUTO_TEST_CASE(assignment_complex) BOOST_CHECK_EQUAL(codeSize("{ let a let x := mload(a) a := sload(x) }"), 2); } +BOOST_AUTO_TEST_CASE(empty_for_loop) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ for {} 1 {} {} }" + ), 4); +} + +BOOST_AUTO_TEST_CASE(break_statement) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ for {} 1 {} { break } }" + ), 6); +} + +BOOST_AUTO_TEST_CASE(continue_statement) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ for {} 1 {} { continue } }" + ), 6); +} + +BOOST_AUTO_TEST_CASE(regular_for_loop) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ for { let x := 0 } lt(x, 10) { x := add(x, 1) } { mstore(x, 1) } }" + ), 10); +} + +BOOST_AUTO_TEST_CASE(if_statement) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ if 1 {} }" + ), 3); +} + +BOOST_AUTO_TEST_CASE(switch_statement_tiny) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ switch calldatasize() default {} }" + ), 4); +} + +BOOST_AUTO_TEST_CASE(switch_statement_small) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ switch calldatasize() case 0 {} default {} }" + ), 6); +} + +BOOST_AUTO_TEST_CASE(switch_statement_medium) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ switch calldatasize() case 0 {} case 1 {} case 2 {} }" + ), 8); +} + +BOOST_AUTO_TEST_CASE(switch_statement_large) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ switch calldatasize() case 0 {} case 1 {} case 2 {} default {} }" + ), 10); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index 4c43157331c3..d350f54d0eef 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -64,17 +64,20 @@ ObjectCompilerTest::ObjectCompilerTest(string const& _filename) bool ObjectCompilerTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) { - AssemblyStack stack(EVMVersion(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + EVMVersion(), + AssemblyStack::Language::StrictAssembly, + m_optimize ? OptimiserSettings::full() : OptimiserSettings::minimal() + ); if (!stack.parseAndAnalyze("source", m_source)) { AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; printErrors(_stream, stack.errors()); return false; } - if (m_optimize) - stack.optimize(); + stack.optimize(); - MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM, m_optimize); + MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM); solAssert(obj.bytecode, ""); m_obtainedResult = "Assembly:\n" + obj.assembly; @@ -85,7 +88,7 @@ bool ObjectCompilerTest::run(ostream& _stream, string const& _linePrefix, bool c "Bytecode: " + toHex(obj.bytecode->bytecode) + "\nOpcodes: " + - boost::trim_copy(solidity::disassemble(obj.bytecode->bytecode)) + + boost::trim_copy(dev::eth::disassemble(obj.bytecode->bytecode)) + "\n"; if (m_expectation != m_obtainedResult) @@ -127,8 +130,5 @@ void ObjectCompilerTest::printErrors(ostream& _stream, ErrorList const& _errors) SourceReferenceFormatter formatter(_stream); for (auto const& error: _errors) - formatter.printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + formatter.printErrorInformation(*error); } diff --git a/test/libyul/ObjectParser.cpp b/test/libyul/ObjectParser.cpp index fc05a3e7a4ab..c54cee762f56 100644 --- a/test/libyul/ObjectParser.cpp +++ b/test/libyul/ObjectParser.cpp @@ -25,6 +25,8 @@ #include +#include + #include #include @@ -48,7 +50,8 @@ std::pair parse(string const& _source) { AssemblyStack asmStack( dev::test::Options::get().evmVersion(), - AssemblyStack::Language::StrictAssembly + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::none() ); bool success = asmStack.parseAndAnalyze("source", _source); return {success, asmStack.errors()}; @@ -242,7 +245,8 @@ BOOST_AUTO_TEST_CASE(to_string) expectation = boost::replace_all_copy(expectation, "\t", " "); AssemblyStack asmStack( dev::test::Options::get().evmVersion(), - AssemblyStack::Language::StrictAssembly + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::none() ); BOOST_REQUIRE(asmStack.parseAndAnalyze("source", code)); BOOST_CHECK_EQUAL(asmStack.print(), expectation); diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index 8dc4bac369d5..c643cfd141c8 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -151,6 +151,33 @@ BOOST_AUTO_TEST_CASE(assignment) BOOST_CHECK(successParse("{ let x:u256 := 2:u256 let y:u256 := x }")); } +BOOST_AUTO_TEST_CASE(period_in_identifier) +{ + BOOST_CHECK(successParse("{ let x.y:u256 := 2:u256 }")); +} + +BOOST_AUTO_TEST_CASE(period_not_as_identifier_start) +{ + CHECK_ERROR("{ let .y:u256 }", ParserError, "Expected identifier but got '.'"); +} + +BOOST_AUTO_TEST_CASE(period_in_identifier_spaced) +{ + CHECK_ERROR("{ let x. y:u256 }", ParserError, "Expected ':' but got identifier"); + CHECK_ERROR("{ let x .y:u256 }", ParserError, "Expected ':' but got '.'"); + CHECK_ERROR("{ let x . y:u256 }", ParserError, "Expected ':' but got '.'"); +} + +BOOST_AUTO_TEST_CASE(period_in_identifier_start) +{ + BOOST_CHECK(successParse("{ x.y(2:u256) function x.y(a:u256) {} }")); +} + +BOOST_AUTO_TEST_CASE(period_in_identifier_start_with_comment) +{ + BOOST_CHECK(successParse("/// comment\n{ x.y(2:u256) function x.y(a:u256) {} }")); +} + BOOST_AUTO_TEST_CASE(vardecl_complex) { BOOST_CHECK(successParse("{ function add(a:u256, b:u256) -> c:u256 {} let y:u256 := 2:u256 let x:u256 := add(7:u256, add(6:u256, y)) }")); @@ -295,6 +322,28 @@ BOOST_AUTO_TEST_CASE(if_statement) BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }")); } +BOOST_AUTO_TEST_CASE(break_outside_of_for_loop) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ let x if x { break } }", + SyntaxError, + "Keyword \"break\" needs to be inside a for-loop body.", + dialect + ); +} + +BOOST_AUTO_TEST_CASE(continue_outside_of_for_loop) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ let x if x { continue } }", + SyntaxError, + "Keyword \"continue\" needs to be inside a for-loop body.", + dialect + ); +} + BOOST_AUTO_TEST_CASE(for_statement) { auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); @@ -313,8 +362,9 @@ BOOST_AUTO_TEST_CASE(for_statement_break_init) CHECK_ERROR_DIALECT( "{ for {let i := 0 break} iszero(eq(i, 10)) {i := add(i, 1)} {} }", SyntaxError, - "Keyword break outside for-loop body is not allowed.", - dialect); + "Keyword \"break\" in for-loop init block is not allowed.", + dialect + ); } BOOST_AUTO_TEST_CASE(for_statement_break_post) @@ -323,8 +373,9 @@ BOOST_AUTO_TEST_CASE(for_statement_break_post) CHECK_ERROR_DIALECT( "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) break} {} }", SyntaxError, - "Keyword break outside for-loop body is not allowed.", - dialect); + "Keyword \"break\" in for-loop post block is not allowed.", + dialect + ); } BOOST_AUTO_TEST_CASE(for_statement_nested_break) @@ -333,8 +384,9 @@ BOOST_AUTO_TEST_CASE(for_statement_nested_break) CHECK_ERROR_DIALECT( "{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { break } } }", SyntaxError, - "Keyword break outside for-loop body is not allowed.", - dialect); + "Keyword \"break\" needs to be inside a for-loop body.", + dialect + ); } BOOST_AUTO_TEST_CASE(for_statement_continue) @@ -349,8 +401,9 @@ BOOST_AUTO_TEST_CASE(for_statement_continue_fail_init) CHECK_ERROR_DIALECT( "{ for {let i := 0 continue} iszero(eq(i, 10)) {i := add(i, 1)} {} }", SyntaxError, - "Keyword continue outside for-loop body is not allowed.", - dialect); + "Keyword \"continue\" in for-loop init block is not allowed.", + dialect + ); } BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post) @@ -359,8 +412,89 @@ BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post) CHECK_ERROR_DIALECT( "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) continue} {} }", SyntaxError, - "Keyword continue outside for-loop body is not allowed.", - dialect); + "Keyword \"continue\" in for-loop post block is not allowed.", + dialect + ); +} + +BOOST_AUTO_TEST_CASE(for_statement_nested_continue) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { continue } } }", + SyntaxError, + "Keyword \"continue\" needs to be inside a for-loop body.", + dialect + ); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue_nested_init_in_body) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {} 1 {} {let x for { continue } x {} {}} }", + SyntaxError, + "Keyword \"continue\" in for-loop init block is not allowed.", + dialect + ); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue_nested_body_in_init) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}); + BOOST_CHECK(successParse("{ for {let x for {} x {} { continue }} 1 {} {} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_break_nested_body_in_init) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}); + BOOST_CHECK(successParse("{ for {let x for {} x {} { break }} 1 {} {} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue_nested_body_in_post) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}); + BOOST_CHECK(successParse("{ for {} 1 {let x for {} x {} { continue }} {} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_break_nested_body_in_post) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}); + BOOST_CHECK(successParse("{ for {} 1 {let x for {} x {} { break }} {} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(function_defined_in_init_block) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}); + BOOST_CHECK(successParse("{ for { } 1 { function f() {} } {} }", dialect)); + BOOST_CHECK(successParse("{ for { } 1 {} { function f() {} } }", dialect)); + CHECK_ERROR_DIALECT( + "{ for { function f() {} } 1 {} {} }", + SyntaxError, + "Functions cannot be defined inside a for-loop init block.", + dialect + ); +} + +BOOST_AUTO_TEST_CASE(function_defined_in_init_nested) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}); + BOOST_CHECK(successParse( + "{ for {" + "for { } 1 { function f() {} } {}" + "} 1 {} {} }", dialect)); + CHECK_ERROR_DIALECT( + "{ for { for {function foo() {}} 1 {} {} } 1 {} {} }", + SyntaxError, + "Functions cannot be defined inside a for-loop init block.", + dialect + ); + CHECK_ERROR_DIALECT( + "{ for {} 1 {for {function foo() {}} 1 {} {} } {} }", + SyntaxError, + "Functions cannot be defined inside a for-loop init block.", + dialect + ); } BOOST_AUTO_TEST_CASE(if_statement_invalid) @@ -390,6 +524,18 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case_different_literal) BOOST_CHECK(successParse("{ switch 1:u256 case \"1\":u256 {} case \"2\":u256 {} }")); } +BOOST_AUTO_TEST_CASE(switch_case_string_literal_too_long) +{ + BOOST_CHECK(successParse("{let x:u256 switch x case \"01234567890123456789012345678901\":u256 {}}")); + CHECK_ERROR("{let x:u256 switch x case \"012345678901234567890123456789012\":u256 {}}", TypeError, "String literal too long (33 > 32)"); +} + +BOOST_AUTO_TEST_CASE(function_shadowing_outside_vars) +{ + CHECK_ERROR("{ let x:u256 function f() -> x:u256 {} }", DeclarationError, "already taken in this scope"); + BOOST_CHECK(successParse("{ { let x:u256 } function f() -> x:u256 {} }")); +} + BOOST_AUTO_TEST_CASE(builtins_parser) { struct SimpleDialect: public Dialect diff --git a/test/libyul/StackReuseCodegen.cpp b/test/libyul/StackReuseCodegen.cpp index e1a3fd8c302b..d386c83f85fa 100644 --- a/test/libyul/StackReuseCodegen.cpp +++ b/test/libyul/StackReuseCodegen.cpp @@ -34,9 +34,12 @@ namespace { string assemble(string const& _input) { - AssemblyStack asmStack; + dev::solidity::OptimiserSettings settings = dev::solidity::OptimiserSettings::full(); + settings.runYulOptimiser = false; + settings.optimizeStackAllocation = true; + AssemblyStack asmStack(langutil::EVMVersion{}, AssemblyStack::Language::StrictAssembly, settings); BOOST_REQUIRE_MESSAGE(asmStack.parseAndAnalyze("", _input), "Source did not parse: " + _input); - return dev::solidity::disassemble(asmStack.assemble(AssemblyStack::Machine::EVM, true).bytecode->bytecode); + return dev::eth::disassemble(asmStack.assemble(AssemblyStack::Machine::EVM).bytecode->bytecode); } } diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index 4a699ca9045b..cfbb2ab0875a 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -107,7 +107,11 @@ void YulInterpreterTest::printIndented(ostream& _stream, string const& _output, bool YulInterpreterTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) { - AssemblyStack stack(dev::test::Options::get().evmVersion(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + dev::test::Options::get().evmVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::none() + ); if (stack.parseAndAnalyze("", m_source)) { m_ast = stack.parserResult()->code; @@ -126,12 +130,14 @@ string YulInterpreterTest::interpret() { InterpreterState state; state.maxTraceSize = 10000; + state.maxSteps = 10000; + state.maxMemSize = 0x20000000; Interpreter interpreter(state); try { interpreter(*m_ast); } - catch (InterpreterTerminated const&) + catch (InterpreterTerminatedGeneric const&) { } @@ -153,8 +159,5 @@ void YulInterpreterTest::printErrors(ostream& _stream, ErrorList const& _errors) SourceReferenceFormatter formatter(_stream); for (auto const& error: _errors) - formatter.printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + formatter.printErrorInformation(*error); } diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 7d05ea9f5ad4..3eec1233fe01 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -77,19 +78,23 @@ YulOptimizerTest::YulOptimizerTest(string const& _filename) m_optimizerStep = std::prev(std::prev(path.end()))->string(); ifstream file(_filename); - if (!file) - BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\".")); + soltestAssert(file, "Cannot open test contract: \"" + _filename + "\"."); file.exceptions(ios::badbit); - string line; - while (getline(file, line)) + m_source = parseSourceAndSettings(file); + if (m_settings.count("yul")) { - if (boost::algorithm::starts_with(line, "// ----")) - break; - if (m_source.empty() && boost::algorithm::starts_with(line, "// yul")) - m_yul = true; - m_source += line + "\n"; + m_yul = true; + m_validatedSettings["yul"] = "true"; + m_settings.erase("yul"); } + if (m_settings.count("step")) + { + m_validatedSettings["step"] = m_settings["step"]; + m_settings.erase("step"); + } + + string line; while (getline(file, line)) if (boost::algorithm::starts_with(line, "// ")) m_expectation += line.substr(3) + "\n"; @@ -187,9 +192,11 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con disambiguate(); NameDispenser nameDispenser{*m_dialect, *m_ast}; ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); + ForLoopInitRewriter{}(*m_ast); CommonSubexpressionEliminator{*m_dialect}(*m_ast); ExpressionSimplifier::run(*m_dialect, *m_ast); UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); + DeadCodeEliminator{}(*m_ast); ExpressionJoiner::run(*m_ast); ExpressionJoiner::run(*m_ast); } @@ -198,6 +205,12 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con disambiguate(); UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); } + else if (m_optimizerStep == "deadCodeEliminator") + { + disambiguate(); + ForLoopInitRewriter{}(*m_ast); + DeadCodeEliminator{}(*m_ast); + } else if (m_optimizerStep == "ssaTransform") { disambiguate(); @@ -259,8 +272,22 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con return false; } - m_obtainedResult = m_optimizerStep + "\n" + AsmPrinter{m_yul}(*m_ast) + "\n"; + m_obtainedResult = AsmPrinter{m_yul}(*m_ast) + "\n"; + bool success = true; + if (m_optimizerStep != m_validatedSettings["step"]) + { + string nextIndentLevel = _linePrefix + " "; + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << + _linePrefix << + "Invalid optimizer step. Given: \"" << + m_validatedSettings["step"] << + "\", should be: \"" << + m_optimizerStep << + "\"." << + endl; + success = false; + } if (m_expectation != m_obtainedResult) { string nextIndentLevel = _linePrefix + " "; @@ -269,9 +296,9 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con printIndented(_stream, m_expectation, nextIndentLevel); AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl; printIndented(_stream, m_obtainedResult, nextIndentLevel); - return false; + success = false; } - return true; + return success; } void YulOptimizerTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const @@ -279,6 +306,12 @@ void YulOptimizerTest::printSource(ostream& _stream, string const& _linePrefix, printIndented(_stream, m_source, _linePrefix); } +void YulOptimizerTest::printUpdatedSettings(ostream& _stream, const string& _linePrefix, const bool _formatted) +{ + m_validatedSettings["step"] = m_optimizerStep; + EVMVersionRestrictedTestCase::printUpdatedSettings(_stream, _linePrefix, _formatted); +} + void YulOptimizerTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const { printIndented(_stream, m_obtainedResult, _linePrefix); @@ -296,7 +329,8 @@ bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool c { AssemblyStack stack( dev::test::Options::get().evmVersion(), - m_yul ? AssemblyStack::Language::Yul : AssemblyStack::Language::StrictAssembly + m_yul ? AssemblyStack::Language::Yul : AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::none() ); if (!stack.parseAndAnalyze("", m_source) || !stack.errors().empty()) { @@ -321,8 +355,5 @@ void YulOptimizerTest::printErrors(ostream& _stream, ErrorList const& _errors) SourceReferenceFormatter formatter(_stream); for (auto const& error: _errors) - formatter.printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + formatter.printErrorInformation(*error); } diff --git a/test/libyul/YulOptimizerTest.h b/test/libyul/YulOptimizerTest.h index cb21090050e4..060d81383956 100644 --- a/test/libyul/YulOptimizerTest.h +++ b/test/libyul/YulOptimizerTest.h @@ -38,7 +38,7 @@ namespace yul namespace test { -class YulOptimizerTest: public dev::solidity::test::TestCase +class YulOptimizerTest: public dev::solidity::test::EVMVersionRestrictedTestCase { public: static std::unique_ptr create(Config const& _config) @@ -51,6 +51,7 @@ class YulOptimizerTest: public dev::solidity::test::TestCase bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; void printSource(std::ostream& _stream, std::string const &_linePrefix = "", bool const _formatted = false) const override; + void printUpdatedSettings(std::ostream &_stream, std::string const &_linePrefix = "", bool const _formatted = false) override; void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; private: diff --git a/test/libyul/yulOptimizerTests/blockFlattener/basic.yul b/test/libyul/yulOptimizerTests/blockFlattener/basic.yul index adcaedd07557..8558737cfc52 100644 --- a/test/libyul/yulOptimizerTests/blockFlattener/basic.yul +++ b/test/libyul/yulOptimizerTests/blockFlattener/basic.yul @@ -8,8 +8,9 @@ } let z := mload(2) } +// ==== +// step: blockFlattener // ---- -// blockFlattener // { // let _1 := mload(0) // let f_a := mload(1) diff --git a/test/libyul/yulOptimizerTests/blockFlattener/for_stmt.yul b/test/libyul/yulOptimizerTests/blockFlattener/for_stmt.yul index 07bd5c18f33b..83f9832e9684 100644 --- a/test/libyul/yulOptimizerTests/blockFlattener/for_stmt.yul +++ b/test/libyul/yulOptimizerTests/blockFlattener/for_stmt.yul @@ -3,8 +3,9 @@ a := add(a, 1) } } +// ==== +// step: blockFlattener // ---- -// blockFlattener // { // for { // let a := 1 diff --git a/test/libyul/yulOptimizerTests/blockFlattener/if_stmt.yul b/test/libyul/yulOptimizerTests/blockFlattener/if_stmt.yul index 4d6ccf0e888a..ca5055ac9cb8 100644 --- a/test/libyul/yulOptimizerTests/blockFlattener/if_stmt.yul +++ b/test/libyul/yulOptimizerTests/blockFlattener/if_stmt.yul @@ -8,8 +8,9 @@ } let t := add(3, 9) } +// ==== +// step: blockFlattener // ---- -// blockFlattener // { // if add(mload(7), sload(mload(3))) // { diff --git a/test/libyul/yulOptimizerTests/blockFlattener/many_nested_blocks.yul b/test/libyul/yulOptimizerTests/blockFlattener/many_nested_blocks.yul index ae2a066bac57..a397000e416a 100644 --- a/test/libyul/yulOptimizerTests/blockFlattener/many_nested_blocks.yul +++ b/test/libyul/yulOptimizerTests/blockFlattener/many_nested_blocks.yul @@ -14,8 +14,9 @@ a := add(a, c) } } +// ==== +// step: blockFlattener // ---- -// blockFlattener // { // let a := 3 // let b := 4 diff --git a/test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul b/test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul index 2df4f9d09e28..f3243296b67d 100644 --- a/test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul +++ b/test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul @@ -5,8 +5,9 @@ default { a := 3 { a := 4 } } a := 5 } +// ==== +// step: blockFlattener // ---- -// blockFlattener // { // let a := 1 // switch calldataload(0) diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_for.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_for.yul index c59bced76c3b..721f78add56e 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_for.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_for.yul @@ -1,12 +1,14 @@ { - let a := 1 let b := codesize() + let a := 1 + let b := codesize() for { } lt(1, codesize()) { mstore(1, codesize()) a := add(a, codesize()) } { mstore(1, codesize()) } mstore(1, codesize()) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let a := 1 // let b := codesize() diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_if.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_if.yul index 5b8200d9c3e3..0fd259b86761 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_if.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/branches_if.yul @@ -3,8 +3,9 @@ if b { b := 1 } let c := 1 } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let b := 1 // if b diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/case2.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/case2.yul index fd8b4bc8152d..ab08c0dee173 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/case2.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/case2.yul @@ -23,8 +23,9 @@ p_1 := add(array, _22) } } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let _13 := 0x20 // let _14 := allocate(_13) diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/function_scopes.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/function_scopes.yul index 28e840cffdd1..957232a8f19f 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/function_scopes.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/function_scopes.yul @@ -23,8 +23,9 @@ let _11 := array_index_access(x, _10) mstore(_11, _9) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // function allocate(size) -> p // { diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr.yul index cb0ca38dd017..42b4274ce65d 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr.yul @@ -2,8 +2,9 @@ let a := mload(1) let b := mload(1) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let a := mload(1) // let b := mload(1) diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr2.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr2.yul index ebc17446c3b8..6fee35665d3a 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr2.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/non_movable_instr2.yul @@ -2,8 +2,9 @@ let a := gas() let b := gas() } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let a := gas() // let b := gas() diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/object_access.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/object_access.yul index 5cfa3e6e626f..b2b01550a1d0 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/object_access.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/object_access.yul @@ -11,8 +11,9 @@ datacopy("abc", x, y) mstore(a, x) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let r := "abc" // let a := datasize("abc") diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul index 49b4c91640db..44d08679fa13 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul @@ -10,8 +10,9 @@ mstore(0, calldataload(0)) mstore(0, x) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let a := 10 // let x := 20 diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/smoke.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/smoke.yul index b945722945e6..6ecbe105c961 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/smoke.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/smoke.yul @@ -1,5 +1,6 @@ { } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // } diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/trivial.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/trivial.yul index 684272f57593..1fdc65d43c97 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/trivial.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/trivial.yul @@ -2,8 +2,9 @@ let a := mul(1, codesize()) let b := mul(1, codesize()) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let a := mul(1, codesize()) // let b := a diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_return.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_return.yul index 5283ef9a0e53..178b8c52366b 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_return.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_return.yul @@ -9,8 +9,9 @@ let b := 0 sstore(a, b) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // function f() -> x // { diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_variables.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_variables.yul index a790ca654399..0012af03717a 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_variables.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/unassigned_variables.yul @@ -5,8 +5,9 @@ let b mstore(sub(a, b), 7) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let a // let b diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/variable_for_variable.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/variable_for_variable.yul index ab94afc22549..9f1bda6651fd 100644 --- a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/variable_for_variable.yul +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/variable_for_variable.yul @@ -12,8 +12,9 @@ a := b mstore(2, a) } +// ==== +// step: commonSubexpressionEliminator // ---- -// commonSubexpressionEliminator // { // let a := mload(0) // let b := add(a, 7) diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/conditional_break.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/conditional_break.yul new file mode 100644 index 000000000000..ca36109ead81 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/conditional_break.yul @@ -0,0 +1,34 @@ +{ + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + if lt(a, 0) + { break } + } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let a := 20 +// for { +// } +// lt(a, 40) +// { +// a := add(a, 2) +// } +// { +// a := a +// if lt(a, 0) +// { +// break +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/early_break.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_break.yul new file mode 100644 index 000000000000..9d08c02eacf3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_break.yul @@ -0,0 +1,32 @@ +{ + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + break + mstore(0, a) + a := add(a, 10) + } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let a := 20 +// for { +// } +// lt(a, 40) +// { +// a := add(a, 2) +// } +// { +// a := a +// break +// } +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/early_continue.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_continue.yul new file mode 100644 index 000000000000..dee962c7c506 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_continue.yul @@ -0,0 +1,32 @@ +{ + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + continue + mstore(0, a) + a := add(a, 10) + } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let a := 20 +// for { +// } +// lt(a, 40) +// { +// a := add(a, 2) +// } +// { +// a := a +// continue +// } +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/early_revert.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_revert.yul new file mode 100644 index 000000000000..be13641c20cb --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_revert.yul @@ -0,0 +1,24 @@ +{ + let b := 20 + revert(0, 0) + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + mstore(0, a) + a := add(a, 10) + } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let b := 20 +// revert(0, 0) +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/early_stop.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_stop.yul new file mode 100644 index 000000000000..ee4227b6adee --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/early_stop.yul @@ -0,0 +1,24 @@ +{ + let b := 20 + stop() + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + mstore(0, a) + a := add(a, 10) + } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let b := 20 +// stop() +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/for_loop_init_decl.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/for_loop_init_decl.yul new file mode 100644 index 000000000000..30753c9ad17f --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/for_loop_init_decl.yul @@ -0,0 +1,12 @@ +{ + for { stop() let i_0 := 0 } lt(i_0,2) { i_0 := add(i_0,1) } + { + let i_1 := i_0 + } +} +// ==== +// step: deadCodeEliminator +// ---- +// { +// stop() +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/function_after_revert.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/function_after_revert.yul new file mode 100644 index 000000000000..4821a985b0d5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/function_after_revert.yul @@ -0,0 +1,25 @@ +{ + fun() + + revert(0, 0) + + function fun() + { + return(1, 1) + + pop(sub(10, 5)) + } + + pop(add(1, 1)) +} +// ==== +// step: deadCodeEliminator +// ---- +// { +// fun() +// revert(0, 0) +// function fun() +// { +// return(1, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/nested_revert.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/nested_revert.yul new file mode 100644 index 000000000000..625b468ce2e5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/nested_revert.yul @@ -0,0 +1,31 @@ +{ + let y := mload(0) + switch y + case 0 { + y := 8 } + case 1 { + y := 9 + revert(0, 0) + y := 10 + } + default { + y := 10 } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let y := mload(0) +// switch y +// case 0 { +// y := 8 +// } +// case 1 { +// y := 9 +// revert(0, 0) +// } +// default { +// y := 10 +// } +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/no_removal.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/no_removal.yul new file mode 100644 index 000000000000..f647be25ed69 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/no_removal.yul @@ -0,0 +1,15 @@ +{ + { + revert(0, 0) + } + mstore(0, 0) +} +// ==== +// step: deadCodeEliminator +// ---- +// { +// { +// revert(0, 0) +// } +// mstore(0, 0) +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_break.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_break.yul new file mode 100644 index 000000000000..fadf98981c40 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_break.yul @@ -0,0 +1,30 @@ +{ + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + break + } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let a := 20 +// for { +// } +// lt(a, 40) +// { +// a := add(a, 2) +// } +// { +// a := a +// break +// } +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_continue.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_continue.yul new file mode 100644 index 000000000000..cc4770dc56c3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_continue.yul @@ -0,0 +1,30 @@ +{ + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + continue + } +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let a := 20 +// for { +// } +// lt(a, 40) +// { +// a := add(a, 2) +// } +// { +// a := a +// continue +// } +// } diff --git a/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_stop.yul b/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_stop.yul new file mode 100644 index 000000000000..fa02b2c882b8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/deadCodeEliminator/normal_stop.yul @@ -0,0 +1,36 @@ +{ + let b := 20 + for { + let a := 20 + } + lt(a, 40) + { + a := add(a, 2) + } + { + a := a + mstore(0, a) + a := add(a, 10) + } + stop() +} + +// ==== +// step: deadCodeEliminator +// ---- +// { +// let b := 20 +// let a := 20 +// for { +// } +// lt(a, 40) +// { +// a := add(a, 2) +// } +// { +// a := a +// mstore(0, a) +// a := add(a, 10) +// } +// stop() +// } diff --git a/test/libyul/yulOptimizerTests/disambiguator/for_statement.yul b/test/libyul/yulOptimizerTests/disambiguator/for_statement.yul index 6875abec6904..5158d0f2b453 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/for_statement.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/for_statement.yul @@ -1,4 +1,3 @@ -// yul { { let a:u256, b:u256 } { @@ -7,8 +6,10 @@ } } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let a:u256, b:u256 diff --git a/test/libyul/yulOptimizerTests/disambiguator/funtion_call.yul b/test/libyul/yulOptimizerTests/disambiguator/funtion_call.yul index df49b92a7412..b081ebe6f119 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/funtion_call.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/funtion_call.yul @@ -1,4 +1,3 @@ -// yul { { let a:u256, b:u256, c:u256, d:u256, f:u256 } { @@ -7,8 +6,10 @@ } } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let a:u256, b:u256, c:u256, d:u256, f:u256 diff --git a/test/libyul/yulOptimizerTests/disambiguator/if_statement.yul b/test/libyul/yulOptimizerTests/disambiguator/if_statement.yul index bc3aa30f91e6..375030564593 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/if_statement.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/if_statement.yul @@ -1,4 +1,3 @@ -// yul { { let a:u256, b:u256, c:u256 } { @@ -6,8 +5,10 @@ if a { let b:bool := a } } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let a:u256, b:u256, c:u256 diff --git a/test/libyul/yulOptimizerTests/disambiguator/long_names.yul b/test/libyul/yulOptimizerTests/disambiguator/long_names.yul index 933e1e8ff480..2b9f3e522cb3 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/long_names.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/long_names.yul @@ -1,7 +1,8 @@ -// yul { { let aanteuhdaoneudbrgkjiuaothduiathudaoeuh:u256 } { let aanteuhdaoneudbrgkjiuaothduiathudaoeuh:u256 } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let aanteuhdaoneudbrgkjiuaothduiathudaoeuh:u256 diff --git a/test/libyul/yulOptimizerTests/disambiguator/smoke.yul b/test/libyul/yulOptimizerTests/disambiguator/smoke.yul index d6cd8a619890..c753de82917a 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/smoke.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/smoke.yul @@ -1,5 +1,6 @@ { } +// ==== +// step: disambiguator // ---- -// disambiguator // { // } diff --git a/test/libyul/yulOptimizerTests/disambiguator/smoke_yul.yul b/test/libyul/yulOptimizerTests/disambiguator/smoke_yul.yul index e55f4cd3e7a4..18e47d0bc7b8 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/smoke_yul.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/smoke_yul.yul @@ -1,6 +1,7 @@ -// yul { } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // } diff --git a/test/libyul/yulOptimizerTests/disambiguator/switch_statement.yul b/test/libyul/yulOptimizerTests/disambiguator/switch_statement.yul index e62e957f86c4..d17afaf63234 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/switch_statement.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/switch_statement.yul @@ -1,4 +1,3 @@ -// yul { { let a:u256, b:u256, c:u256 } { @@ -8,8 +7,10 @@ default { let c:u256 := a } } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let a:u256, b:u256, c:u256 diff --git a/test/libyul/yulOptimizerTests/disambiguator/variables.yul b/test/libyul/yulOptimizerTests/disambiguator/variables.yul index 65bd4c8f805f..7ae1a11b78d8 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/variables.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/variables.yul @@ -1,7 +1,8 @@ -// yul { { let a:u256 } { let a:u256 } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let a:u256 diff --git a/test/libyul/yulOptimizerTests/disambiguator/variables_clash.yul b/test/libyul/yulOptimizerTests/disambiguator/variables_clash.yul index e462442a982d..46d0ac897240 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/variables_clash.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/variables_clash.yul @@ -1,7 +1,8 @@ -// yul { { let a:u256 let a_1:u256 } { let a:u256 } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let a:u256 diff --git a/test/libyul/yulOptimizerTests/disambiguator/variables_inside_functions.yul b/test/libyul/yulOptimizerTests/disambiguator/variables_inside_functions.yul index 839692bc62d0..c0d59f923a49 100644 --- a/test/libyul/yulOptimizerTests/disambiguator/variables_inside_functions.yul +++ b/test/libyul/yulOptimizerTests/disambiguator/variables_inside_functions.yul @@ -1,4 +1,3 @@ -// yul { { let c:u256 let b:u256 } function f(a:u256, c:u256) -> b:u256 { let x:u256 } @@ -6,8 +5,10 @@ let a:u256 let x:u256 } } +// ==== +// step: disambiguator +// yul: true // ---- -// disambiguator // { // { // let c:u256 diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/multiple_complex.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/multiple_complex.yul index 380f9f0357b0..97f239ce8afa 100644 --- a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/multiple_complex.yul +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/multiple_complex.yul @@ -54,8 +54,9 @@ } } } +// ==== +// step: equivalentFunctionCombiner // ---- -// equivalentFunctionCombiner // { // pop(f(1, 2, 3)) // pop(f(4, 5, 6)) diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple.yul index 2d5b3ef8f946..aa53c5797728 100644 --- a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple.yul +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple.yul @@ -4,8 +4,9 @@ function f() { mstore(1, mload(0)) } function g() { mstore(1, mload(0)) } } +// ==== +// step: equivalentFunctionCombiner // ---- -// equivalentFunctionCombiner // { // f() // f() diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple_different_vars.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple_different_vars.yul index d38a3d2eae9d..25ffa4017707 100644 --- a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple_different_vars.yul +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple_different_vars.yul @@ -4,8 +4,9 @@ function f() -> b { let a := mload(0) b := a } function g() -> a { let b := mload(0) a := b } } +// ==== +// step: equivalentFunctionCombiner // ---- -// equivalentFunctionCombiner // { // pop(f()) // pop(f()) diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/switch_case_order.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/switch_case_order.yul index 4f3cad360735..9f3ccc6ecdf2 100644 --- a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/switch_case_order.yul +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/switch_case_order.yul @@ -4,8 +4,9 @@ function f(x) { switch x case 0 { mstore(0, 42) } case 1 { mstore(1, 42) } } function g(x) { switch x case 1 { mstore(1, 42) } case 0 { mstore(0, 42) } } } +// ==== +// step: equivalentFunctionCombiner // ---- -// equivalentFunctionCombiner // { // f(0) // f(1) diff --git a/test/libyul/yulOptimizerTests/expressionInliner/complex_with_evm.yul b/test/libyul/yulOptimizerTests/expressionInliner/complex_with_evm.yul index 519a2af855d1..34d4eb9864f0 100644 --- a/test/libyul/yulOptimizerTests/expressionInliner/complex_with_evm.yul +++ b/test/libyul/yulOptimizerTests/expressionInliner/complex_with_evm.yul @@ -2,8 +2,9 @@ function f(a) -> x { x := add(a, a) } let y := f(calldatasize()) } +// ==== +// step: expressionInliner // ---- -// expressionInliner // { // function f(a) -> x // { diff --git a/test/libyul/yulOptimizerTests/expressionInliner/double_calls.yul b/test/libyul/yulOptimizerTests/expressionInliner/double_calls.yul index e1da8e074240..deae8482bc21 100644 --- a/test/libyul/yulOptimizerTests/expressionInliner/double_calls.yul +++ b/test/libyul/yulOptimizerTests/expressionInliner/double_calls.yul @@ -3,8 +3,9 @@ function g(b, c) -> y { y := mul(mload(c), f(b)) } let y := g(calldatasize(), 7) } +// ==== +// step: expressionInliner // ---- -// expressionInliner // { // function f(a) -> x // { diff --git a/test/libyul/yulOptimizerTests/expressionInliner/double_recursive_calls.yul b/test/libyul/yulOptimizerTests/expressionInliner/double_recursive_calls.yul index 082cb53faa8a..80843eca79f5 100644 --- a/test/libyul/yulOptimizerTests/expressionInliner/double_recursive_calls.yul +++ b/test/libyul/yulOptimizerTests/expressionInliner/double_recursive_calls.yul @@ -3,8 +3,9 @@ function g(b, s) -> y { y := f(b, f(s, s)) } let y := g(calldatasize(), 7) } +// ==== +// step: expressionInliner // ---- -// expressionInliner // { // function f(a, r) -> x // { diff --git a/test/libyul/yulOptimizerTests/expressionInliner/no_inline_mload.yul b/test/libyul/yulOptimizerTests/expressionInliner/no_inline_mload.yul index 0fb43a9d020b..fc4ec9720464 100644 --- a/test/libyul/yulOptimizerTests/expressionInliner/no_inline_mload.yul +++ b/test/libyul/yulOptimizerTests/expressionInliner/no_inline_mload.yul @@ -3,8 +3,9 @@ function f(a) -> x { x := a } let y := f(mload(2)) } +// ==== +// step: expressionInliner // ---- -// expressionInliner // { // function f(a) -> x // { diff --git a/test/libyul/yulOptimizerTests/expressionInliner/no_move_with_sideeffects.yul b/test/libyul/yulOptimizerTests/expressionInliner/no_move_with_sideeffects.yul index 7fdad6c41946..eaaa863b1a56 100644 --- a/test/libyul/yulOptimizerTests/expressionInliner/no_move_with_sideeffects.yul +++ b/test/libyul/yulOptimizerTests/expressionInliner/no_move_with_sideeffects.yul @@ -6,8 +6,9 @@ function h() -> z { mstore(0, 4) z := mload(0) } let r := f(g(), h()) } +// ==== +// step: expressionInliner // ---- -// expressionInliner // { // function f(a, b) -> x // { diff --git a/test/libyul/yulOptimizerTests/expressionInliner/simple.yul b/test/libyul/yulOptimizerTests/expressionInliner/simple.yul index c186eafda687..c1e9c12069e0 100644 --- a/test/libyul/yulOptimizerTests/expressionInliner/simple.yul +++ b/test/libyul/yulOptimizerTests/expressionInliner/simple.yul @@ -1,10 +1,11 @@ -// yul { function f() -> x:u256 { x := 2:u256 } let y:u256 := f() } +// ==== +// step: expressionInliner +// yul: true // ---- -// expressionInliner // { // function f() -> x:u256 // { diff --git a/test/libyul/yulOptimizerTests/expressionInliner/with_args.yul b/test/libyul/yulOptimizerTests/expressionInliner/with_args.yul index b5f4d5159748..1d049cdfedab 100644 --- a/test/libyul/yulOptimizerTests/expressionInliner/with_args.yul +++ b/test/libyul/yulOptimizerTests/expressionInliner/with_args.yul @@ -1,10 +1,11 @@ -// yul { function f(a:u256) -> x:u256 { x := a } let y:u256 := f(7:u256) } +// ==== +// step: expressionInliner +// yul: true // ---- -// expressionInliner // { // function f(a:u256) -> x:u256 // { diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/if_condition.yul b/test/libyul/yulOptimizerTests/expressionJoiner/if_condition.yul index a1349511c738..78ecfc88c0df 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/if_condition.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/if_condition.yul @@ -10,8 +10,9 @@ let z := 3 let t := add(z, 9) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // if add(mload(7), sload(mload(3))) // { diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/muli_wrong_order3.yul b/test/libyul/yulOptimizerTests/expressionJoiner/muli_wrong_order3.yul index 0d407c7ca466..07982ca47949 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/muli_wrong_order3.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/muli_wrong_order3.yul @@ -4,8 +4,9 @@ let x := mul(add(b, a), mload(2)) sstore(x, 3) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let a := mload(3) // let b := mload(6) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/multi.yul b/test/libyul/yulOptimizerTests/expressionJoiner/multi.yul index fd53ca51d3dc..9d9a139a2d1f 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/multi.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/multi.yul @@ -4,8 +4,9 @@ let x := mul(add(b, a), 2) sstore(x, 3) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // sstore(mul(add(mload(6), mload(2)), 2), 3) // } diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/multi_reference.yul b/test/libyul/yulOptimizerTests/expressionJoiner/multi_reference.yul index 078a12a56325..95a0eccb3d13 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/multi_reference.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/multi_reference.yul @@ -3,8 +3,9 @@ let a := mload(2) let b := add(a, a) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let a := mload(2) // let b := add(a, a) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order.yul b/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order.yul index 965e07e97bd1..d8624dc14238 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order.yul @@ -7,8 +7,9 @@ let x := mul(a, add(2, b)) sstore(x, 3) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let a := mload(2) // sstore(mul(a, add(2, mload(6))), 3) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order2.yul b/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order2.yul index c577e1820c8d..6296c3778f98 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order2.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/multi_wrong_order2.yul @@ -4,8 +4,9 @@ let x := mul(add(a, b), 2) sstore(x, 3) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let a := mload(2) // sstore(mul(add(a, mload(6)), 2), 3) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_across_blocks.yul b/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_across_blocks.yul index a781bb2a7ef7..02c4466a2e59 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_across_blocks.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_across_blocks.yul @@ -11,8 +11,9 @@ } sstore(x, 3) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let x := calldataload(mload(2)) // sstore(x, 3) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition1.yul b/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition1.yul index 75218a5c4b82..091a125665c5 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition1.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition1.yul @@ -1,8 +1,9 @@ { for { let b := mload(1) } b {} {} } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // for { // let b := mload(1) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition2.yul b/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition2.yul index d5f7d8fa688b..3e86d6078c34 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition2.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/no_replacement_in_loop_condition2.yul @@ -2,8 +2,9 @@ let a := mload(0) for { } a {} {} } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let a := mload(0) // for { diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/only_assignment.yul b/test/libyul/yulOptimizerTests/expressionJoiner/only_assignment.yul index c7411211508f..12fa6ec51752 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/only_assignment.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/only_assignment.yul @@ -5,8 +5,9 @@ x := add(a, 3) } } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // function f(a) -> x // { diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/reassignment.yul b/test/libyul/yulOptimizerTests/expressionJoiner/reassignment.yul index 1e502353d7c0..97661414fc42 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/reassignment.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/reassignment.yul @@ -4,8 +4,9 @@ let b := mload(a) a := 4 } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let a := mload(2) // let b := mload(a) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/simple.yul b/test/libyul/yulOptimizerTests/expressionJoiner/simple.yul index b03bcf45a46e..03d2a7aee463 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/simple.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/simple.yul @@ -3,8 +3,9 @@ let x := calldataload(a) sstore(x, 3) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // sstore(calldataload(mload(2)), 3) // } diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/single_wrong_order.yul b/test/libyul/yulOptimizerTests/expressionJoiner/single_wrong_order.yul index 3b433f78bb49..21bf67c890c7 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/single_wrong_order.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/single_wrong_order.yul @@ -5,8 +5,9 @@ let d := add(b, c) sstore(d, 0) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // let b := sload(mload(3)) // sstore(add(b, mload(7)), 0) diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/smoke.yul b/test/libyul/yulOptimizerTests/expressionJoiner/smoke.yul index c0e2c6f22c30..1161c2092f74 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/smoke.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/smoke.yul @@ -1,5 +1,6 @@ { } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // } diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/switch_expression.yul b/test/libyul/yulOptimizerTests/expressionJoiner/switch_expression.yul index 0e4e540e1a72..9ab4215338eb 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/switch_expression.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/switch_expression.yul @@ -14,8 +14,9 @@ let z := 3 let t := add(z, 9) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // switch add(mload(7), sload(mload(3))) // case 3 { diff --git a/test/libyul/yulOptimizerTests/expressionJoiner/triple.yul b/test/libyul/yulOptimizerTests/expressionJoiner/triple.yul index 7b722be1854e..cf2b4a6155ab 100644 --- a/test/libyul/yulOptimizerTests/expressionJoiner/triple.yul +++ b/test/libyul/yulOptimizerTests/expressionJoiner/triple.yul @@ -5,8 +5,9 @@ let x := mul(add(c, b), a) sstore(x, 3) } +// ==== +// step: expressionJoiner // ---- -// expressionJoiner // { // sstore(mul(add(mload(7), mload(6)), mload(2)), 3) // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/assigned_vars_multi.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/assigned_vars_multi.yul index 51f5df40bfd6..74604e77180a 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/assigned_vars_multi.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/assigned_vars_multi.yul @@ -3,8 +3,9 @@ let c, d := f() let y := add(d, add(c, 7)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // function f() -> x, z // { diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/constant_propagation.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/constant_propagation.yul index 0b55adc51a20..b0f675ac83d6 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/constant_propagation.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/constant_propagation.yul @@ -1,6 +1,7 @@ { let a := add(7, sub(mload(0), 7)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := mload(0) // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/constants.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/constants.yul index bd1a5a53de8b..daebf97f7619 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/constants.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/constants.yul @@ -1,6 +1,7 @@ { let a := add(1, mul(3, 4)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := 13 // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_complex.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_complex.yul index f61906226227..f3298e510740 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_complex.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_complex.yul @@ -1,6 +1,7 @@ { let a := sub(calldataload(0), calldataload(0)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := 0 // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_negative.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_negative.yul index e91403cd822e..2838eb1936b1 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_negative.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_negative.yul @@ -1,6 +1,7 @@ { let a := sub(calldataload(1), calldataload(0)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := sub(calldataload(1), calldataload(0)) // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_simple.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_simple.yul index d35686cd3035..554d080b2f56 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_simple.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/identity_rules_simple.yul @@ -2,8 +2,9 @@ let a := mload(0) let b := sub(a, a) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := mload(0) // let b := 0 diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/including_function_calls.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/including_function_calls.yul index c2ca504a17cf..ad8f3c33a933 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/including_function_calls.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/including_function_calls.yul @@ -2,8 +2,9 @@ function f() -> a {} let b := add(7, sub(f(), 7)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // function f() -> a // { diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/inside_for.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/inside_for.yul index 42c378269699..33677859977e 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/inside_for.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/inside_for.yul @@ -1,11 +1,13 @@ { - for { let a := 10 } iszero(eq(a, 0)) { a := add(a, 1) } {} + let a := 10 + for { } iszero(eq(a, 0)) { a := add(a, 1) } {} } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { +// let a := 10 // for { -// let a := 10 // } // iszero(iszero(a)) // { diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/invariant.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/invariant.yul index e6d84552821a..9ad8892431f1 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/invariant.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/invariant.yul @@ -2,8 +2,9 @@ let a := mload(sub(7, 7)) let b := sub(a, 0) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := mload(0) // let b := a diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_1.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_1.yul index 88714ce0a0fa..6d7b03035ca8 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_1.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_1.yul @@ -1,8 +1,9 @@ { mstore(0, mod(calldataload(0), exp(2, 8))) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // mstore(0, and(calldataload(0), 255)) // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_2.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_2.yul index 4d52abe851cb..d949b2f17f2e 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_2.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/mod_and_2.yul @@ -1,8 +1,9 @@ { mstore(0, mod(calldataload(0), exp(2, 255))) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // mstore(0, and(calldataload(0), 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)) // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_arguments.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_arguments.yul index 53270b7234d2..d769143daf5b 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_arguments.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_arguments.yul @@ -2,8 +2,9 @@ function f(a) -> b { } let c := sub(f(0), f(1)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // function f(a) -> b // { diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_names.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_names.yul index 6ab65d294f33..be80fee19dae 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_names.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_different_names.yul @@ -3,8 +3,9 @@ function f2() -> b { } let c := sub(f1(), f2()) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // function f1() -> a // { diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_equality_not_movable.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_equality_not_movable.yul index ab1bd128744f..bc0664939115 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_equality_not_movable.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_function_call_equality_not_movable.yul @@ -3,8 +3,9 @@ function f() -> a { } let b := sub(f(), f()) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // function f() -> a // { diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_removes_non_constant_and_not_movable.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_removes_non_constant_and_not_movable.yul index fc61c3df7f14..cee563173d27 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_removes_non_constant_and_not_movable.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/not_applied_removes_non_constant_and_not_movable.yul @@ -3,8 +3,9 @@ { let a := div(keccak256(0, 0), 0) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := div(keccak256(0, 0), 0) // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/reassign.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/reassign.yul index 10d5dc67a16f..8178c6d03705 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/reassign.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/reassign.yul @@ -3,8 +3,9 @@ x := 0 mstore(0, add(7, x)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let x := mload(0) // x := 0 diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/remove_redundant_shift_masking.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/remove_redundant_shift_masking.yul new file mode 100644 index 000000000000..cb5303eaff7d --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/remove_redundant_shift_masking.yul @@ -0,0 +1,16 @@ +{ + let a := and(0xff, shr(248, calldataload(0))) + let b := and(shr(248, calldataload(0)), 0xff) + let c := and(shr(249, calldataload(0)), 0xfa) + let d := and(shr(247, calldataload(0)), 0xff) +} +// ==== +// EVMVersion: >=constantinople +// step: expressionSimplifier +// ---- +// { +// let a := shr(248, calldataload(0)) +// let b := shr(248, calldataload(0)) +// let c := and(shr(249, calldataload(0)), 0xfa) +// let d := and(shr(247, calldataload(0)), 0xff) +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/replace_too_large_shift.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/replace_too_large_shift.yul new file mode 100644 index 000000000000..8d69bcc8d7bd --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/replace_too_large_shift.yul @@ -0,0 +1,16 @@ +{ + let a := shl(299, calldataload(0)) + let b := shr(299, calldataload(1)) + let c := shl(255, calldataload(2)) + let d := shr(255, calldataload(3)) +} +// ==== +// EVMVersion: >=constantinople +// step: expressionSimplifier +// ---- +// { +// let a := 0 +// let b := 0 +// let c := shl(255, calldataload(2)) +// let d := shr(255, calldataload(3)) +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/return_vars_zero.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/return_vars_zero.yul index 46f9bc40b6c7..250f5f6fb253 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/return_vars_zero.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/return_vars_zero.yul @@ -4,8 +4,9 @@ let y := add(d, add(c, 7)) } } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // function f() -> c, d // { diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/reversed.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/reversed.yul index 6353cda98bd8..e993763a7507 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/reversed.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/reversed.yul @@ -1,8 +1,9 @@ { let a := add(0, mload(0)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let a := mload(0) // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/smoke.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/smoke.yul index 88420e923be7..7503f997d04c 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/smoke.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/smoke.yul @@ -1,5 +1,6 @@ { } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/unassigend_vars_multi.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/unassigend_vars_multi.yul index f260db0be5f7..e5db5ff76b9a 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/unassigend_vars_multi.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/unassigend_vars_multi.yul @@ -3,8 +3,9 @@ let c, d let y := add(d, add(c, 7)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let c, d // let y := 7 diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/unassigned_vars.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/unassigned_vars.yul index 7b1430f39607..f8480a32734c 100644 --- a/test/libyul/yulOptimizerTests/expressionSimplifier/unassigned_vars.yul +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/unassigned_vars.yul @@ -4,8 +4,9 @@ let d let y := add(d, add(c, 7)) } +// ==== +// step: expressionSimplifier // ---- -// expressionSimplifier // { // let c // let d diff --git a/test/libyul/yulOptimizerTests/expressionSplitter/control_flow.yul b/test/libyul/yulOptimizerTests/expressionSplitter/control_flow.yul index d021129fe8e3..d7a21da12024 100644 --- a/test/libyul/yulOptimizerTests/expressionSplitter/control_flow.yul +++ b/test/libyul/yulOptimizerTests/expressionSplitter/control_flow.yul @@ -7,8 +7,9 @@ } } } +// ==== +// step: expressionSplitter // ---- -// expressionSplitter // { // let _1 := 0 // let x := calldataload(_1) @@ -37,4 +38,4 @@ // sstore(b, _12) // } // } -// } +// } \ No newline at end of file diff --git a/test/libyul/yulOptimizerTests/expressionSplitter/inside_function.yul b/test/libyul/yulOptimizerTests/expressionSplitter/inside_function.yul index 53bbcea76d42..bb73118ec98a 100644 --- a/test/libyul/yulOptimizerTests/expressionSplitter/inside_function.yul +++ b/test/libyul/yulOptimizerTests/expressionSplitter/inside_function.yul @@ -5,8 +5,9 @@ } sstore(x, f(mload(2), mload(2))) } +// ==== +// step: expressionSplitter // ---- -// expressionSplitter // { // let _1 := 3 // let _2 := 7 diff --git a/test/libyul/yulOptimizerTests/expressionSplitter/object_access.yul b/test/libyul/yulOptimizerTests/expressionSplitter/object_access.yul index 2689ab6f21f0..a7b5e9232b22 100644 --- a/test/libyul/yulOptimizerTests/expressionSplitter/object_access.yul +++ b/test/libyul/yulOptimizerTests/expressionSplitter/object_access.yul @@ -6,8 +6,9 @@ // datacopy is fine, though datacopy(mload(0), mload(1), mload(2)) } +// ==== +// step: expressionSplitter // ---- -// expressionSplitter // { // let x := dataoffset("abc") // let y := datasize("abc") diff --git a/test/libyul/yulOptimizerTests/expressionSplitter/smoke.yul b/test/libyul/yulOptimizerTests/expressionSplitter/smoke.yul index f69f60b630b1..bcbc1a6588a1 100644 --- a/test/libyul/yulOptimizerTests/expressionSplitter/smoke.yul +++ b/test/libyul/yulOptimizerTests/expressionSplitter/smoke.yul @@ -1,5 +1,6 @@ { } +// ==== +// step: expressionSplitter // ---- -// expressionSplitter // { // } diff --git a/test/libyul/yulOptimizerTests/expressionSplitter/switch.yul b/test/libyul/yulOptimizerTests/expressionSplitter/switch.yul index aee7976f3f4c..3608cab4fc4c 100644 --- a/test/libyul/yulOptimizerTests/expressionSplitter/switch.yul +++ b/test/libyul/yulOptimizerTests/expressionSplitter/switch.yul @@ -5,8 +5,9 @@ default { mstore(0, mload(3)) } x := add(mload(3), 4) } +// ==== +// step: expressionSplitter // ---- -// expressionSplitter // { // let x := 8 // let _1 := 0 diff --git a/test/libyul/yulOptimizerTests/expressionSplitter/trivial.yul b/test/libyul/yulOptimizerTests/expressionSplitter/trivial.yul index bff70cd88eb5..3d48eed79a16 100644 --- a/test/libyul/yulOptimizerTests/expressionSplitter/trivial.yul +++ b/test/libyul/yulOptimizerTests/expressionSplitter/trivial.yul @@ -1,8 +1,9 @@ { mstore(add(calldataload(2), mload(3)), 8) } +// ==== +// step: expressionSplitter // ---- -// expressionSplitter // { // let _1 := 8 // let _2 := 3 diff --git a/test/libyul/yulOptimizerTests/forLoopInitRewriter/complex_pre.yul b/test/libyul/yulOptimizerTests/forLoopInitRewriter/complex_pre.yul index 3ca00f55779f..6dc96a37e02e 100644 --- a/test/libyul/yulOptimizerTests/forLoopInitRewriter/complex_pre.yul +++ b/test/libyul/yulOptimizerTests/forLoopInitRewriter/complex_pre.yul @@ -5,8 +5,9 @@ a := add(a, 1) } } +// ==== +// step: forLoopInitRewriter // ---- -// forLoopInitRewriter // { // let random := 42 // let a := 1 diff --git a/test/libyul/yulOptimizerTests/forLoopInitRewriter/empty_pre.yul b/test/libyul/yulOptimizerTests/forLoopInitRewriter/empty_pre.yul index 05aceb5209ac..45df8479ccd8 100644 --- a/test/libyul/yulOptimizerTests/forLoopInitRewriter/empty_pre.yul +++ b/test/libyul/yulOptimizerTests/forLoopInitRewriter/empty_pre.yul @@ -4,8 +4,9 @@ a := add(a, 1) } } +// ==== +// step: forLoopInitRewriter // ---- -// forLoopInitRewriter // { // let a := 1 // for { diff --git a/test/libyul/yulOptimizerTests/forLoopInitRewriter/nested.yul b/test/libyul/yulOptimizerTests/forLoopInitRewriter/nested.yul index 3f7aa0892474..afeed5dc528b 100644 --- a/test/libyul/yulOptimizerTests/forLoopInitRewriter/nested.yul +++ b/test/libyul/yulOptimizerTests/forLoopInitRewriter/nested.yul @@ -13,8 +13,9 @@ mstore(b,b) } } +// ==== +// step: forLoopInitRewriter // ---- -// forLoopInitRewriter // { // let random := 42 // let a := 1 diff --git a/test/libyul/yulOptimizerTests/forLoopInitRewriter/simple.yul b/test/libyul/yulOptimizerTests/forLoopInitRewriter/simple.yul index 565b77251028..91078561c520 100644 --- a/test/libyul/yulOptimizerTests/forLoopInitRewriter/simple.yul +++ b/test/libyul/yulOptimizerTests/forLoopInitRewriter/simple.yul @@ -4,8 +4,9 @@ a := add(a, 1) } } +// ==== +// step: forLoopInitRewriter // ---- -// forLoopInitRewriter // { // let random := 42 // let a := 1 diff --git a/test/libyul/yulOptimizerTests/fullInliner/double_inline.yul b/test/libyul/yulOptimizerTests/fullInliner/double_inline.yul index e1ef4ab9c013..b3ec1bbe8b16 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/double_inline.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/double_inline.yul @@ -4,8 +4,9 @@ let b3, c3 := f(a1) let b4, c4 := f(c3) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let a_2 := calldataload(0) diff --git a/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul b/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul index f0bb2e0a7abc..a81d34ef3a5f 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul @@ -8,8 +8,9 @@ r := add(a, calldatasize()) } } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let _2 := mload(0) diff --git a/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul b/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul index 87b8258869d7..a088e1681905 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul @@ -15,8 +15,9 @@ // This should be inlined because it is a constant as well (zero) let s := f(a3) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let a_1 := mload(2) diff --git a/test/libyul/yulOptimizerTests/fullInliner/large_function_single_use.yul b/test/libyul/yulOptimizerTests/fullInliner/large_function_single_use.yul index 2861d58ec300..b8c05b72c416 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/large_function_single_use.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/large_function_single_use.yul @@ -10,8 +10,9 @@ // Single-use functions are always inlined. let r := f(mload(1)) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let a_6 := mload(1) diff --git a/test/libyul/yulOptimizerTests/fullInliner/long_names.yul b/test/libyul/yulOptimizerTests/fullInliner/long_names.yul index 95f8242688e8..8e9c2019d325 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/long_names.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/long_names.yul @@ -7,8 +7,9 @@ mstore(0, verylongfunctionname(verylongvariablename2)) mstore(1, verylongvariablename2) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let verylongvariablename2_1 := 3 diff --git a/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul b/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul index 32db4f0b37be..f40563602cf7 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul @@ -5,8 +5,9 @@ } let y := add(mload(1), add(f(mload(2), mload(3), mload(4)), mload(5))) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let _2 := mload(5) diff --git a/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul b/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul index 6aecf716632a..b372da9aef10 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul @@ -3,8 +3,9 @@ function g(b, c) -> y { y := mul(mload(c), f(b)) } let y := g(f(3), 7) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let _1 := 7 diff --git a/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul b/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul index caccc8b80303..aa4b7cb86ec0 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul @@ -22,8 +22,9 @@ f(100) } } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let x_8 := 100 diff --git a/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul b/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul index 2f00aa30b999..7d90e011830f 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul @@ -6,8 +6,9 @@ let r, s := f(mload(0)) mstore(r, s) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let a_3 := mload(0) diff --git a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul index 0fecbab88a84..cbbe57d29532 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul @@ -9,8 +9,9 @@ x := f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(2))))))))))))))))))) } } +// ==== +// step: fullInliner // ---- -// fullInliner // { // function f(a) -> b // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul index 8541402ee119..354a6eff4f4f 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul @@ -7,8 +7,9 @@ // the global context gets too big. let x := f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(2))))))))))))))))))) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let a_20 := 2 diff --git a/test/libyul/yulOptimizerTests/fullInliner/no_return.yul b/test/libyul/yulOptimizerTests/fullInliner/no_return.yul index fe8bd3996333..258c07a618d0 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/no_return.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/no_return.yul @@ -4,8 +4,9 @@ } f(mload(0)) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let a_3 := mload(0) diff --git a/test/libyul/yulOptimizerTests/fullInliner/not_inside_for.yul b/test/libyul/yulOptimizerTests/fullInliner/not_inside_for.yul index 98a31003eb02..7f146f5e5301 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/not_inside_for.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/not_inside_for.yul @@ -1,5 +1,6 @@ { - for { let x := f(0) } f(x) { x := f(x) } + let x := f(0) + for { } f(x) { x := f(x) } { let t := f(x) } @@ -8,16 +9,17 @@ r := a } } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { +// let a_3 := 0 +// let r_4 := 0 +// sstore(a_3, 0) +// r_4 := a_3 +// let x := r_4 // for { -// let a_3 := 0 -// let r_4 := 0 -// sstore(a_3, 0) -// r_4 := a_3 -// let x := r_4 // } // f(x) // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul b/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul index 63ad8e31e908..373416925bca 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul @@ -8,8 +8,9 @@ } pop(add(f(7), 2)) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let _1 := 2 diff --git a/test/libyul/yulOptimizerTests/fullInliner/recursion.yul b/test/libyul/yulOptimizerTests/fullInliner/recursion.yul index 5ac6ce7725b8..175d44fd2653 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/recursion.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/recursion.yul @@ -4,8 +4,9 @@ } f(mload(0)) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let a_4 := mload(0) diff --git a/test/libyul/yulOptimizerTests/fullInliner/simple.yul b/test/libyul/yulOptimizerTests/fullInliner/simple.yul index 6b043c911b56..af6fa8283954 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/simple.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/simple.yul @@ -5,8 +5,9 @@ } let y := add(f(sload(mload(2))), mload(7)) } +// ==== +// step: fullInliner // ---- -// fullInliner // { // { // let _2 := mload(7) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/constant_propagation.yul b/test/libyul/yulOptimizerTests/fullSimplify/constant_propagation.yul index 90a3e16d9f1b..4c7fa2692135 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/constant_propagation.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/constant_propagation.yul @@ -2,8 +2,9 @@ let a := add(7, sub(mload(0), 7)) mstore(a, 0) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let _2 := 0 // mstore(mload(_2), _2) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/constants.yul b/test/libyul/yulOptimizerTests/fullSimplify/constants.yul index b9c7c1fcd124..814562fadf74 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/constants.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/constants.yul @@ -2,8 +2,9 @@ let a := add(1, mul(3, 4)) mstore(0, a) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // mstore(0, 13) // } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_complex.yul b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_complex.yul index 4b17d7ea1c96..eaa0845da7db 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_complex.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_complex.yul @@ -2,8 +2,9 @@ let a := sub(calldataload(0), calldataload(0)) mstore(a, 0) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // mstore(0, 0) // } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_negative.yul b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_negative.yul index a1737eface5e..bc7bf5b5a594 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_negative.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_negative.yul @@ -2,8 +2,9 @@ let a := sub(calldataload(1), calldataload(0)) mstore(0, a) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let _1 := 0 // mstore(_1, sub(calldataload(1), calldataload(_1))) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_simple.yul b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_simple.yul index 22a358fd5016..fc6463d2ba74 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_simple.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_simple.yul @@ -2,8 +2,9 @@ let a := mload(0) mstore(0, sub(a, a)) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let _1 := 0 // pop(mload(_1)) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/including_function_calls.yul b/test/libyul/yulOptimizerTests/fullSimplify/including_function_calls.yul index fa3ff07c361c..469607d68af6 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/including_function_calls.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/including_function_calls.yul @@ -3,8 +3,9 @@ let b := add(7, sub(f(), 7)) mstore(b, 0) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // function f() -> a // { diff --git a/test/libyul/yulOptimizerTests/fullSimplify/inside_for.yul b/test/libyul/yulOptimizerTests/fullSimplify/inside_for.yul index f1b40301979b..972c9f267148 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/inside_for.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/inside_for.yul @@ -1,12 +1,14 @@ { let x := calldataload(3) - for { let a := 10 } iszero(eq(a, sub(x, calldataload(3)))) { a := add(a, 1) } {} + let a := 10 + for { } iszero(eq(a, sub(x, calldataload(3)))) { a := add(a, 1) } {} } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { +// let a := 10 // for { -// let a := 10 // } // iszero(iszero(a)) // { diff --git a/test/libyul/yulOptimizerTests/fullSimplify/invariant.yul b/test/libyul/yulOptimizerTests/fullSimplify/invariant.yul index a8eedef129f5..453f74e33c85 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/invariant.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/invariant.yul @@ -7,8 +7,9 @@ // run of CSE afterwards. mstore(b, eq(calldataload(0), a)) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let a := calldataload(0) // let _4 := 0 diff --git a/test/libyul/yulOptimizerTests/fullSimplify/mod_and_1.yul b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_1.yul index bba16a9445cc..59dbf172b0f5 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/mod_and_1.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_1.yul @@ -1,8 +1,9 @@ { mstore(0, mod(calldataload(0), exp(2, 8))) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let _4 := 0 // mstore(_4, and(calldataload(_4), 255)) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/mod_and_2.yul b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_2.yul index 4a6eaa526472..41254b818e55 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/mod_and_2.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_2.yul @@ -1,8 +1,9 @@ { mstore(0, mod(calldataload(0), exp(2, 255))) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let _4 := 0 // mstore(_4, and(calldataload(_4), 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_arguments.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_arguments.yul index 0c5e3ed91dfc..f6d7912fd4bd 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_arguments.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_arguments.yul @@ -2,8 +2,9 @@ function f(a) -> b { } mstore(0, sub(f(0), f(1))) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // function f(a) -> b // { diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_names.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_names.yul index 90e89fe1ffbb..655ad00ca30e 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_names.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_names.yul @@ -4,8 +4,9 @@ let c := sub(f1(), f2()) mstore(0, c) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // function f1() -> a // { diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul index 92e50ebe666a..00bba7a79737 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul @@ -4,8 +4,9 @@ let b := sub(f(), f()) mstore(0, b) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // function f() -> a // { diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_removes_non_constant_and_not_movable.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_removes_non_constant_and_not_movable.yul index 7dcdc280ef1b..eeb5ba1ae962 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_removes_non_constant_and_not_movable.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_removes_non_constant_and_not_movable.yul @@ -3,8 +3,9 @@ let a := div(keccak256(0, 0), 0) mstore(0, a) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let _1 := 0 // pop(keccak256(_1, _1)) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/operations.yul b/test/libyul/yulOptimizerTests/fullSimplify/operations.yul index 25467b629169..b0f979276068 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/operations.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/operations.yul @@ -19,8 +19,9 @@ mstore(17, or(x, not(x))) mstore(18, or(not(x), x)) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // pop(mload(0)) // mstore(1, 0) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/reversed.yul b/test/libyul/yulOptimizerTests/fullSimplify/reversed.yul index fb916e6aea36..fceec7ecf89b 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/reversed.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/reversed.yul @@ -2,8 +2,9 @@ let a := add(0, mload(0)) mstore(0, a) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // let _1 := 0 // mstore(_1, mload(_1)) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/signextend.yul b/test/libyul/yulOptimizerTests/fullSimplify/signextend.yul index 714eb860265c..13d4997dde64 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/signextend.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/signextend.yul @@ -4,8 +4,9 @@ let y := 255 mstore(1, signextend(0, y)) } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // mstore(0, 7) // mstore(1, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) diff --git a/test/libyul/yulOptimizerTests/fullSimplify/smoke.yul b/test/libyul/yulOptimizerTests/fullSimplify/smoke.yul index a4fbb89909ce..de485f3d0303 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/smoke.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/smoke.yul @@ -1,5 +1,6 @@ { } +// ==== +// step: fullSimplify // ---- -// fullSimplify // { // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi2.yul b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul index 179dcc3d67ad..1983f2ebed7f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi2.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul @@ -1069,21 +1069,24 @@ result := and(add(value_389, 31), not(31)) } } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// let _1 := mload(1) -// let _2 := mload(0) -// if slt(sub(_1, _2), 64) // { -// revert(0, 0) +// let _1 := mload(1) +// let _2 := mload(0) +// if slt(sub(_1, _2), 64) +// { +// revert(0, 0) +// } +// sstore(0, and(calldataload(_2), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// let x0, x1, x2, x3, x4 := abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(mload(7), mload(8)) +// sstore(x1, x0) +// sstore(x3, x2) +// sstore(1, x4) +// pop(abi_encode_tuple_t_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_$1949_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256__to_t_bytes32_t_address_t_uint256_t_bytes32_t_uint8_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256_(mload(30), mload(31), mload(32), mload(33), mload(34), mload(35), mload(36), mload(37), mload(38), mload(39), mload(40), mload(41))) // } -// sstore(0, and(calldataload(_2), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) -// let x0, x1, x2, x3, x4 := abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(mload(7), mload(8)) -// sstore(x1, x0) -// sstore(x3, x2) -// sstore(1, x4) -// pop(abi_encode_tuple_t_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_$1949_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256__to_t_bytes32_t_address_t_uint256_t_bytes32_t_uint8_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256_(mload(30), mload(31), mload(32), mload(33), mload(34), mload(35), mload(36), mload(37), mload(38), mload(39), mload(40), mload(41))) // function abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(headStart, dataEnd) -> value0, value1, value2, value3, value4 // { // if slt(sub(dataEnd, headStart), 128) diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index bf60aa0a4a79..312de61f02bb 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -455,65 +455,54 @@ converted := cleanup_assert_t_uint160(value) } } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// let _1 := 0x20 -// let _2 := 0 -// let _3 := mload(_2) -// let pos := _1 -// let length := mload(_3) -// mstore(_1, length) -// pos := 64 -// let srcPtr := add(_3, _1) -// let i := _2 -// for { -// } -// lt(i, length) -// { -// i := add(i, 1) -// } // { -// let _4 := mload(srcPtr) -// let pos_1 := pos -// let srcPtr_1 := _4 -// let i_1 := _2 +// let _1 := 0x20 +// let _2 := 0 +// let _3 := mload(_2) +// let pos := _1 +// let length := mload(_3) +// mstore(_1, length) +// pos := 64 +// let srcPtr := add(_3, _1) +// let i := _2 // for { // } -// lt(i_1, 0x3) +// lt(i, length) // { -// i_1 := add(i_1, 1) +// i := add(i, 1) // } // { -// mstore(pos_1, and(mload(srcPtr_1), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) -// srcPtr_1 := add(srcPtr_1, _1) -// pos_1 := add(pos_1, _1) +// abi_encode_t_array$_t_contract$_C_$55_$3_memory_to_t_array$_t_address_$3_memory_ptr(mload(srcPtr), pos) +// srcPtr := add(srcPtr, _1) +// pos := add(pos, 0x60) // } -// srcPtr := add(srcPtr, _1) -// pos := add(pos, 0x60) -// } -// let _5 := mload(64) -// let _6 := mload(_1) -// if slt(sub(_5, _6), 128) -// { -// revert(_2, _2) -// } -// let offset := calldataload(add(_6, 64)) -// let _7 := 0xffffffffffffffff -// if gt(offset, _7) -// { -// revert(_2, _2) -// } -// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_6, offset), _5) -// let offset_1 := calldataload(add(_6, 96)) -// if gt(offset_1, _7) -// { -// revert(_2, _2) +// let _4 := mload(64) +// let _5 := mload(_1) +// if slt(sub(_4, _5), 128) +// { +// revert(_2, _2) +// } +// let offset := calldataload(add(_5, 64)) +// let _6 := 0xffffffffffffffff +// if gt(offset, _6) +// { +// revert(_2, _2) +// } +// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_5, offset), _4) +// let offset_1 := calldataload(add(_5, 96)) +// if gt(offset_1, _6) +// { +// revert(_2, _2) +// } +// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_5, offset_1), _4) +// sstore(calldataload(_5), calldataload(add(_5, _1))) +// sstore(value2, value3) +// sstore(_2, pos) // } -// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_6, offset_1), _5) -// sstore(calldataload(_6), calldataload(add(_6, _1))) -// sstore(value2, value3) -// sstore(_2, pos) // function abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(offset, end) -> array // { // if iszero(slt(add(offset, 0x1f), end)) @@ -598,6 +587,22 @@ // src := add(src, _1) // } // } +// function abi_encode_t_array$_t_contract$_C_$55_$3_memory_to_t_array$_t_address_$3_memory_ptr(value, pos) +// { +// let srcPtr := value +// let i := 0 +// for { +// } +// lt(i, 0x3) +// { +// i := add(i, 1) +// } +// { +// mstore(pos, and(mload(srcPtr), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// srcPtr := add(srcPtr, 0x20) +// pos := add(pos, 0x20) +// } +// } // function allocateMemory(size) -> memPtr // { // memPtr := mload(64) diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index d1690b046216..bfe9ea27dc3f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -228,112 +228,113 @@ mstore(0x00, keccak256(0x300, mul(n, 0x80))) } } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// mstore(0x80, 7673901602397024137095011250362199966051872585513276903826533215767972925880) -// mstore(0xa0, 8489654445897228341090914135473290831551238522473825886865492707826370766375) -// let notes := add(0x04, calldataload(0x04)) -// let m := calldataload(0x24) -// let n := calldataload(notes) -// let gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 -// let challenge := mod(calldataload(0x44), gen_order) -// if gt(m, n) // { -// mstore(0x00, 404) -// revert(0x00, 0x20) -// } -// let kn := calldataload(add(calldatasize(), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40)) -// mstore(0x2a0, caller()) -// mstore(0x2c0, kn) -// mstore(0x2e0, m) -// kn := mulmod(sub(gen_order, kn), challenge, gen_order) -// hashCommitments(notes, n) -// let b := add(0x300, mul(n, 0x80)) -// let i := 0 -// let i_1 := i -// for { -// } -// lt(i, n) -// { -// i := add(i, 0x01) -// } -// { -// let _1 := add(calldataload(0x04), mul(i, 0xc0)) -// let noteIndex := add(_1, 0x24) -// let k := i_1 -// let a := calldataload(add(_1, 0x44)) -// let c := challenge -// let _2 := add(i, 0x01) -// switch eq(_2, n) -// case 1 { -// k := kn -// if eq(m, n) -// { -// k := sub(gen_order, kn) -// } -// } -// case 0 { -// k := calldataload(noteIndex) +// mstore(0x80, 7673901602397024137095011250362199966051872585513276903826533215767972925880) +// mstore(0xa0, 8489654445897228341090914135473290831551238522473825886865492707826370766375) +// let notes := add(0x04, calldataload(0x04)) +// let m := calldataload(0x24) +// let n := calldataload(notes) +// let gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +// let challenge := mod(calldataload(0x44), gen_order) +// if gt(m, n) +// { +// mstore(0x00, 404) +// revert(0x00, 0x20) // } -// validateCommitment(noteIndex, k, a) -// switch gt(_2, m) -// case 1 { -// kn := addmod(kn, sub(gen_order, k), gen_order) -// let x := mod(mload(i_1), gen_order) -// k := mulmod(k, x, gen_order) -// a := mulmod(a, x, gen_order) -// c := mulmod(challenge, x, gen_order) -// mstore(i_1, keccak256(i_1, 0x20)) +// let kn := calldataload(add(calldatasize(), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40)) +// mstore(0x2a0, caller()) +// mstore(0x2c0, kn) +// mstore(0x2e0, m) +// kn := mulmod(sub(gen_order, kn), challenge, gen_order) +// hashCommitments(notes, n) +// let b := add(0x300, mul(n, 0x80)) +// let i := 0 +// let i_1 := i +// for { // } -// case 0 { -// kn := addmod(kn, k, gen_order) +// lt(i, n) +// { +// i := add(i, 0x01) // } -// let _3 := 0x40 -// calldatacopy(0xe0, add(_1, 164), _3) -// calldatacopy(0x20, add(_1, 100), _3) -// mstore(0x120, sub(gen_order, c)) -// mstore(0x60, k) -// mstore(0xc0, a) -// let result := call(gas(), 7, i_1, 0xe0, 0x60, 0x1a0, _3) -// let result_1 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x120, _3)) -// let result_2 := and(result_1, call(gas(), 7, i_1, 0x80, 0x60, 0x160, _3)) -// let result_3 := and(result_2, call(gas(), 6, i_1, 0x120, 0x80, 0x160, _3)) -// result := and(result_3, call(gas(), 6, i_1, 0x160, 0x80, b, _3)) -// if eq(i, m) // { -// mstore(0x260, mload(0x20)) -// mstore(0x280, mload(_3)) -// mstore(0x1e0, mload(0xe0)) -// mstore(0x200, sub(0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47, mload(0x100))) +// let _1 := add(calldataload(0x04), mul(i, 0xc0)) +// let noteIndex := add(_1, 0x24) +// let k := i_1 +// let a := calldataload(add(_1, 0x44)) +// let c := challenge +// let _2 := add(i, 0x01) +// switch eq(_2, n) +// case 1 { +// k := kn +// if eq(m, n) +// { +// k := sub(gen_order, kn) +// } +// } +// case 0 { +// k := calldataload(noteIndex) +// } +// validateCommitment(noteIndex, k, a) +// switch gt(_2, m) +// case 1 { +// kn := addmod(kn, sub(gen_order, k), gen_order) +// let x := mod(mload(i_1), gen_order) +// k := mulmod(k, x, gen_order) +// a := mulmod(a, x, gen_order) +// c := mulmod(challenge, x, gen_order) +// mstore(i_1, keccak256(i_1, 0x20)) +// } +// case 0 { +// kn := addmod(kn, k, gen_order) +// } +// let _3 := 0x40 +// calldatacopy(0xe0, add(_1, 164), _3) +// calldatacopy(0x20, add(_1, 100), _3) +// mstore(0x120, sub(gen_order, c)) +// mstore(0x60, k) +// mstore(0xc0, a) +// let result := call(gas(), 7, i_1, 0xe0, 0x60, 0x1a0, _3) +// let result_1 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x120, _3)) +// let result_2 := and(result_1, call(gas(), 7, i_1, 0x80, 0x60, 0x160, _3)) +// let result_3 := and(result_2, call(gas(), 6, i_1, 0x120, 0x80, 0x160, _3)) +// result := and(result_3, call(gas(), 6, i_1, 0x160, 0x80, b, _3)) +// if eq(i, m) +// { +// mstore(0x260, mload(0x20)) +// mstore(0x280, mload(_3)) +// mstore(0x1e0, mload(0xe0)) +// mstore(0x200, sub(0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47, mload(0x100))) +// } +// if gt(i, m) +// { +// mstore(0x60, c) +// let result_4 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x220, _3)) +// let result_5 := and(result_4, call(gas(), 6, i_1, 0x220, 0x80, 0x260, _3)) +// result := and(result_5, call(gas(), 6, i_1, 0x1a0, 0x80, 0x1e0, _3)) +// } +// if iszero(result) +// { +// mstore(i_1, 400) +// revert(i_1, 0x20) +// } +// b := add(b, _3) // } -// if gt(i, m) +// if lt(m, n) // { -// mstore(0x60, c) -// let result_4 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x220, _3)) -// let result_5 := and(result_4, call(gas(), 6, i_1, 0x220, 0x80, 0x260, _3)) -// result := and(result_5, call(gas(), 6, i_1, 0x1a0, 0x80, 0x1e0, _3)) +// validatePairing(0x64) // } -// if iszero(result) +// if iszero(eq(mod(keccak256(0x2a0, add(b, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd60)), gen_order), challenge)) // { -// mstore(i_1, 400) +// mstore(i_1, 404) // revert(i_1, 0x20) // } -// b := add(b, _3) -// } -// if lt(m, n) -// { -// validatePairing(0x64) -// } -// if iszero(eq(mod(keccak256(0x2a0, add(b, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd60)), gen_order), challenge)) -// { -// mstore(i_1, 404) -// revert(i_1, 0x20) +// mstore(i_1, 0x01) +// return(i_1, 0x20) // } -// mstore(i_1, 0x01) -// return(i_1, 0x20) -// mstore(i_1, 404) -// revert(i_1, 0x20) // function validatePairing(t2) // { // let t2_x := calldataload(t2) diff --git a/test/libyul/yulOptimizerTests/fullSuite/medium.yul b/test/libyul/yulOptimizerTests/fullSuite/medium.yul index dc3cfaaa4fec..bebb8c418c05 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/medium.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/medium.yul @@ -16,13 +16,16 @@ for { switch mul(1,2) case 2 { mstore(0x40, 0x20) } } sub(1,1) {} { mstore(0x80, 0x40) } } } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// let _1 := 0x40 -// mstore(_1, add(mload(_1), 0x20)) -// let p := mload(_1) -// mstore(_1, add(p, _1)) -// mstore(add(p, 96), 2) -// mstore(_1, 0x20) +// { +// let _1 := 0x40 +// mstore(_1, add(mload(_1), 0x20)) +// let p := mload(_1) +// mstore(_1, add(p, _1)) +// mstore(add(p, 96), 2) +// mstore(_1, 0x20) +// } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/reuse_vars_bug_in_simplifier.yul b/test/libyul/yulOptimizerTests/fullSuite/reuse_vars_bug_in_simplifier.yul index 8136a4e9ef7c..ba797fa7b61f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/reuse_vars_bug_in_simplifier.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/reuse_vars_bug_in_simplifier.yul @@ -9,8 +9,11 @@ mstore(sub(1,div(sub(x_9,1),sub(1,sub(x_9,1)))), 1) } } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// mstore(1, 1) +// { +// mstore(1, 1) +// } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul index 325baefbb91b..e9af2c227c5c 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul @@ -31,17 +31,20 @@ a,b := abi_decode_t_bytes_calldata_ptr(a,b) mstore(a,b) } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// let a, b := abi_decode_t_bytes_calldata_ptr(mload(0), mload(1)) -// a, b := abi_decode_t_bytes_calldata_ptr(a, b) -// a, b := abi_decode_t_bytes_calldata_ptr(a, b) -// a, b := abi_decode_t_bytes_calldata_ptr(a, b) -// a, b := abi_decode_t_bytes_calldata_ptr(a, b) -// a, b := abi_decode_t_bytes_calldata_ptr(a, b) -// a, b := abi_decode_t_bytes_calldata_ptr(a, b) -// mstore(a, b) +// { +// let a, b := abi_decode_t_bytes_calldata_ptr(mload(0), mload(1)) +// let a_1, b_1 := abi_decode_t_bytes_calldata_ptr(a, b) +// let a_2, b_2 := abi_decode_t_bytes_calldata_ptr(a_1, b_1) +// let a_3, b_3 := abi_decode_t_bytes_calldata_ptr(a_2, b_2) +// let a_4, b_4 := abi_decode_t_bytes_calldata_ptr(a_3, b_3) +// let a_5, b_5 := abi_decode_t_bytes_calldata_ptr(a_4, b_4) +// let a_6, b_6 := abi_decode_t_bytes_calldata_ptr(a_5, b_5) +// mstore(a_6, b_6) +// } // function abi_decode_t_bytes_calldata_ptr(offset, end) -> arrayPos, length // { // if iszero(slt(add(offset, 0x1f), end)) diff --git a/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul b/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul index 2e178f312ecf..564ed9664cbb 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul @@ -9,15 +9,18 @@ } mstore(a, b) } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// let a := mload(0) -// let b := mload(1) -// if mload(2) // { -// a := mload(mload(mload(b))) -// b := mload(a) +// let a := mload(0) +// let b := mload(1) +// if mload(2) +// { +// a := mload(mload(mload(b))) +// b := mload(a) +// } +// mstore(a, b) // } -// mstore(a, b) // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/switch_inline.yul b/test/libyul/yulOptimizerTests/fullSuite/switch_inline.yul index 876400ce966a..9a463e320d5b 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/switch_inline.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/switch_inline.yul @@ -6,8 +6,11 @@ case 1 { y := 9 } } } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// mstore(9, 0) +// { +// mstore(9, 0) +// } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/switch_inline_match_default.yul b/test/libyul/yulOptimizerTests/fullSuite/switch_inline_match_default.yul index 260bcdc687bd..ad1d92c83d26 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/switch_inline_match_default.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/switch_inline_match_default.yul @@ -7,8 +7,11 @@ default { y := 10 } } } +// ==== +// step: fullSuite // ---- -// fullSuite // { -// mstore(10, 0) +// { +// mstore(10, 0) +// } // } diff --git a/test/libyul/yulOptimizerTests/functionGrouper/already_grouped.yul b/test/libyul/yulOptimizerTests/functionGrouper/already_grouped.yul index 42e8a48e3baa..98a8f2e39294 100644 --- a/test/libyul/yulOptimizerTests/functionGrouper/already_grouped.yul +++ b/test/libyul/yulOptimizerTests/functionGrouper/already_grouped.yul @@ -4,8 +4,9 @@ } function f() -> y { y := 8 } } +// ==== +// step: functionGrouper // ---- -// functionGrouper // { // { // let x := 2 diff --git a/test/libyul/yulOptimizerTests/functionGrouper/empty_block.yul b/test/libyul/yulOptimizerTests/functionGrouper/empty_block.yul index f0d49d7b3942..c5a9065dead7 100644 --- a/test/libyul/yulOptimizerTests/functionGrouper/empty_block.yul +++ b/test/libyul/yulOptimizerTests/functionGrouper/empty_block.yul @@ -1,7 +1,8 @@ -// yul { let a:u256 { } function f() -> x:bool { let b:u256 := 4:u256 {} for {} f() {} {} } } +// ==== +// step: functionGrouper +// yul: true // ---- -// functionGrouper // { // { // let a:u256 diff --git a/test/libyul/yulOptimizerTests/functionGrouper/grouped_but_not_ordered.yul b/test/libyul/yulOptimizerTests/functionGrouper/grouped_but_not_ordered.yul index 0abb5d8769f9..f18a4f6a4207 100644 --- a/test/libyul/yulOptimizerTests/functionGrouper/grouped_but_not_ordered.yul +++ b/test/libyul/yulOptimizerTests/functionGrouper/grouped_but_not_ordered.yul @@ -4,8 +4,9 @@ let x := 2 } } +// ==== +// step: functionGrouper // ---- -// functionGrouper // { // { // { diff --git a/test/libyul/yulOptimizerTests/functionGrouper/multi_fun_mixed.yul b/test/libyul/yulOptimizerTests/functionGrouper/multi_fun_mixed.yul index c830d5da7b54..53baf628dfb7 100644 --- a/test/libyul/yulOptimizerTests/functionGrouper/multi_fun_mixed.yul +++ b/test/libyul/yulOptimizerTests/functionGrouper/multi_fun_mixed.yul @@ -1,12 +1,13 @@ -// yul { let a:u256 function f() { let b:u256 } let c:u256 function g() { let d:u256 } let e:u256 } +// ==== +// step: functionGrouper +// yul: true // ---- -// functionGrouper // { // { // let a:u256 diff --git a/test/libyul/yulOptimizerTests/functionGrouper/nested_fun.yul b/test/libyul/yulOptimizerTests/functionGrouper/nested_fun.yul index 4a8be86a41bf..43bd5b1ad548 100644 --- a/test/libyul/yulOptimizerTests/functionGrouper/nested_fun.yul +++ b/test/libyul/yulOptimizerTests/functionGrouper/nested_fun.yul @@ -1,4 +1,3 @@ -// yul { let a:u256 function f() { @@ -9,8 +8,10 @@ let d:u256 } } +// ==== +// step: functionGrouper +// yul: true // ---- -// functionGrouper // { // { // let a:u256 diff --git a/test/libyul/yulOptimizerTests/functionGrouper/single_fun.yul b/test/libyul/yulOptimizerTests/functionGrouper/single_fun.yul index 149a44eb8c19..15d2ddecfd74 100644 --- a/test/libyul/yulOptimizerTests/functionGrouper/single_fun.yul +++ b/test/libyul/yulOptimizerTests/functionGrouper/single_fun.yul @@ -1,9 +1,10 @@ -// yul { let a:u256 function f() {} } +// ==== +// step: functionGrouper +// yul: true // ---- -// functionGrouper // { // { // let a:u256 diff --git a/test/libyul/yulOptimizerTests/functionGrouper/smoke.yul b/test/libyul/yulOptimizerTests/functionGrouper/smoke.yul index 650a163e7227..285265a4f3b8 100644 --- a/test/libyul/yulOptimizerTests/functionGrouper/smoke.yul +++ b/test/libyul/yulOptimizerTests/functionGrouper/smoke.yul @@ -1,6 +1,7 @@ { } +// ==== +// step: functionGrouper // ---- -// functionGrouper // { // { // } diff --git a/test/libyul/yulOptimizerTests/functionHoister/empty_block.yul b/test/libyul/yulOptimizerTests/functionHoister/empty_block.yul index 6ea9f59d3365..3f5ec85a7494 100644 --- a/test/libyul/yulOptimizerTests/functionHoister/empty_block.yul +++ b/test/libyul/yulOptimizerTests/functionHoister/empty_block.yul @@ -1,4 +1,3 @@ -// yul { let a:u256 { } @@ -8,8 +7,10 @@ for {} f() {} {} } } +// ==== +// step: functionHoister +// yul: true // ---- -// functionHoister // { // let a:u256 // function f() -> x:bool diff --git a/test/libyul/yulOptimizerTests/functionHoister/multi_mixed.yul b/test/libyul/yulOptimizerTests/functionHoister/multi_mixed.yul index 1e3bc5a1a51d..41b263ac040e 100644 --- a/test/libyul/yulOptimizerTests/functionHoister/multi_mixed.yul +++ b/test/libyul/yulOptimizerTests/functionHoister/multi_mixed.yul @@ -1,4 +1,3 @@ -// yul { let a:u256 function f() { let b:u256 } @@ -6,8 +5,10 @@ function g() { let d:u256 } let e:u256 } +// ==== +// step: functionHoister +// yul: true // ---- -// functionHoister // { // let a:u256 // let c:u256 diff --git a/test/libyul/yulOptimizerTests/functionHoister/nested.yul b/test/libyul/yulOptimizerTests/functionHoister/nested.yul index 20f094f180ac..e25bc0cf9c89 100644 --- a/test/libyul/yulOptimizerTests/functionHoister/nested.yul +++ b/test/libyul/yulOptimizerTests/functionHoister/nested.yul @@ -1,4 +1,3 @@ -// yul { let a:u256 function f() { @@ -7,8 +6,10 @@ let d:u256 } } +// ==== +// step: functionHoister +// yul: true // ---- -// functionHoister // { // let a:u256 // function g() diff --git a/test/libyul/yulOptimizerTests/functionHoister/single.yul b/test/libyul/yulOptimizerTests/functionHoister/single.yul index ba922612efef..0366d9a87e65 100644 --- a/test/libyul/yulOptimizerTests/functionHoister/single.yul +++ b/test/libyul/yulOptimizerTests/functionHoister/single.yul @@ -1,10 +1,11 @@ -// yul { let a:u256 function f() {} } +// ==== +// step: functionHoister +// yul: true // ---- -// functionHoister // { // let a:u256 // function f() diff --git a/test/libyul/yulOptimizerTests/functionHoister/smoke.yul b/test/libyul/yulOptimizerTests/functionHoister/smoke.yul index 35c1ce5fbd0c..c730174f3470 100644 --- a/test/libyul/yulOptimizerTests/functionHoister/smoke.yul +++ b/test/libyul/yulOptimizerTests/functionHoister/smoke.yul @@ -1,6 +1,7 @@ { } +// ==== +// step: functionHoister // ---- -// functionHoister // { // } diff --git a/test/libyul/yulOptimizerTests/mainFunction/empty_block.yul b/test/libyul/yulOptimizerTests/mainFunction/empty_block.yul index bae6bd48d7a0..cd5876597866 100644 --- a/test/libyul/yulOptimizerTests/mainFunction/empty_block.yul +++ b/test/libyul/yulOptimizerTests/mainFunction/empty_block.yul @@ -1,4 +1,3 @@ -// yul { let a:u256 { } @@ -8,8 +7,10 @@ for {} f() {} {} } } +// ==== +// step: mainFunction +// yul: true // ---- -// mainFunction // { // function main() // { diff --git a/test/libyul/yulOptimizerTests/mainFunction/multi_fun_mixed.yul b/test/libyul/yulOptimizerTests/mainFunction/multi_fun_mixed.yul index dd5caaec4a15..e9a2b091e426 100644 --- a/test/libyul/yulOptimizerTests/mainFunction/multi_fun_mixed.yul +++ b/test/libyul/yulOptimizerTests/mainFunction/multi_fun_mixed.yul @@ -1,4 +1,3 @@ -// yul { let a:u256 function f() { let b:u256 } @@ -6,8 +5,10 @@ function g() { let d:u256 } let e:u256 } +// ==== +// step: mainFunction +// yul: true // ---- -// mainFunction // { // function main() // { diff --git a/test/libyul/yulOptimizerTests/mainFunction/nested_fun.yul b/test/libyul/yulOptimizerTests/mainFunction/nested_fun.yul index 309b97ccf233..8294172b161c 100644 --- a/test/libyul/yulOptimizerTests/mainFunction/nested_fun.yul +++ b/test/libyul/yulOptimizerTests/mainFunction/nested_fun.yul @@ -1,4 +1,3 @@ -// yul { let a:u256 function f() { @@ -7,8 +6,10 @@ let d:u256 } } +// ==== +// step: mainFunction +// yul: true // ---- -// mainFunction // { // function main() // { diff --git a/test/libyul/yulOptimizerTests/mainFunction/sigle_fun.yul b/test/libyul/yulOptimizerTests/mainFunction/sigle_fun.yul index fa9a8f415a0e..7a7fc94719fe 100644 --- a/test/libyul/yulOptimizerTests/mainFunction/sigle_fun.yul +++ b/test/libyul/yulOptimizerTests/mainFunction/sigle_fun.yul @@ -1,10 +1,11 @@ -// yul { let a:u256 function f() {} } +// ==== +// step: mainFunction +// yul: true // ---- -// mainFunction // { // function main() // { diff --git a/test/libyul/yulOptimizerTests/mainFunction/smoke.yul b/test/libyul/yulOptimizerTests/mainFunction/smoke.yul index 7be147467075..156f8849a4e9 100644 --- a/test/libyul/yulOptimizerTests/mainFunction/smoke.yul +++ b/test/libyul/yulOptimizerTests/mainFunction/smoke.yul @@ -1,7 +1,8 @@ -// yul {} +// ==== +// step: mainFunction +// yul: true // ---- -// mainFunction // { // function main() // { diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for.yul index d9bbd86d329d..ecddd254c8e6 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for.yul @@ -1,20 +1,19 @@ { + let a := 2 + a := 3 for { - let a := 2 - // Should not be removed, even though you might think - // it goes out of scope - a := 3 } a { a := add(a, 1) } { a := 7 } } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { +// let a := 2 +// a := 3 // for { -// let a := 2 -// a := 3 // } // a // { diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_branch.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_branch.yul index 7f5e97ce8dbe..cad75ac9e60b 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_branch.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_branch.yul @@ -13,8 +13,9 @@ y := 8 mstore(x, 0) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let x // let y diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_break.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_break.yul new file mode 100644 index 000000000000..9a5ce0fef203 --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_break.yul @@ -0,0 +1,35 @@ +{ + let x + // Cannot be removed, because we might skip the loop + x := 1 + for { } calldataload(0) { } + { + if callvalue() { + x := 2 // is preserved because of break stmt below. + break + } + x := 3 + } + mstore(x, 0x42) +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x +// x := 1 +// for { +// } +// calldataload(0) +// { +// } +// { +// if callvalue() +// { +// x := 2 +// break +// } +// x := 3 +// } +// mstore(x, 0x42) +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue.yul new file mode 100644 index 000000000000..7242bd37b15f --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue.yul @@ -0,0 +1,36 @@ +{ + let x + // Can be removed, because x is reassigned after the loop + x := 1 + for { } calldataload(0) { } + { + x := 2 // Will not be removed as if-condition can be false. + if callvalue() { + // This can be removed because x is overwritten both after the + // loop at at the start of the next iteration. + x := 3 + continue + } + mstore(x, 2) + } + x := 3 +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x +// for { +// } +// calldataload(0) +// { +// } +// { +// x := 2 +// if callvalue() +// { +// continue +// } +// mstore(x, 2) +// } +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue_2.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue_2.yul new file mode 100644 index 000000000000..61138df4a8a1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue_2.yul @@ -0,0 +1,35 @@ +{ + let x + // Cannot be removed, because we might skip the loop + x := 1 + for { } calldataload(0) { } + { + if callvalue() { + x := 2 // is preserved because of continue stmt below. + continue + } + x := 3 + } + mstore(x, 0x42) +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x +// x := 1 +// for { +// } +// calldataload(0) +// { +// } +// { +// if callvalue() +// { +// x := 2 +// continue +// } +// x := 3 +// } +// mstore(x, 0x42) +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue_3.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue_3.yul new file mode 100644 index 000000000000..286be48dd7e6 --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_continue_3.yul @@ -0,0 +1,33 @@ +{ + let x + // Can be removed, because x is not used after the loop. + x := 1 + for { } calldataload(0) { mstore(x, 0x42) } + { + if callvalue() { + x := 2 // is preserved because of continue stmt below. + continue + } + x := 3 + } +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x +// for { +// } +// calldataload(0) +// { +// mstore(x, 0x42) +// } +// { +// if callvalue() +// { +// x := 2 +// continue +// } +// x := 3 +// } +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_decl_inside_break_continue.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_decl_inside_break_continue.yul new file mode 100644 index 000000000000..70ace5f4bc42 --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_decl_inside_break_continue.yul @@ -0,0 +1,45 @@ +{ + let x := 1 + for { } calldataload(0) { } + { + // This will go out of scope at the end of the block, + // but the continue/break statements still refer to it. + { + let y := 9 + if callvalue() { + y := 2 // will be removed + break + } + if eq(callvalue(), 3) { + y := 12 // will be removed + continue + } + } + } + mstore(x, 0x42) +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x := 1 +// for { +// } +// calldataload(0) +// { +// } +// { +// { +// let y := 9 +// if callvalue() +// { +// break +// } +// if eq(callvalue(), 3) +// { +// continue +// } +// } +// } +// mstore(x, 0x42) +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_deep_noremove.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_deep_noremove.yul new file mode 100644 index 000000000000..b9bbc6d4795e --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_deep_noremove.yul @@ -0,0 +1,88 @@ +{ + let x := 1 + let y := 2 + let a := 3 + let b := 4 + for {} 1 {} { + for {} 1 {} { + for {} 1 {} { + // Here, the nesting is not yet too deep, so the assignment + // should be removed. + for {} 1 { x := 9 } { + y := 10 + for {} 1 {} { + for {} 1 {} { + // Now we are too deep, assignments stay. + for {} 1 { a := 10 } { + b := 12 + b := 11 + } + } + } + } + } + } + } + x := 13 +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x := 1 +// let y := 2 +// let a := 3 +// let b := 4 +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// a := 10 +// } +// { +// b := 12 +// b := 11 +// } +// } +// } +// } +// } +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_deep_simple.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_deep_simple.yul new file mode 100644 index 000000000000..716674b8dd0b --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_deep_simple.yul @@ -0,0 +1,76 @@ +{ + for {} 1 {} { + for {} 1 {} { + for {} 1 {} { + for {} 1 {} { + for {} 1 {} { + for {} 1 {} { + for {} 1 { let a := 1 a := 2 a := 3 } { + // Declarations inside body and post should be handled as normal. + let b := 1 + b := 2 + b := 3 + } + } + } + } + } + } + } +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// } +// { +// for { +// } +// 1 +// { +// let a := 1 +// } +// { +// let b := 1 +// } +// } +// } +// } +// } +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_multi_break.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_multi_break.yul new file mode 100644 index 000000000000..6d5be5741554 --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_multi_break.yul @@ -0,0 +1,75 @@ +{ + let x := 1 + let y := 1 + let z := 1 + for { } calldataload(0) { mstore(x, 2) mstore(z, 2) } + { + y := 3 + switch callvalue() + case 0 { + x := 2 + y := 2 + z := 2 + break + } + case 1 { + x := 3 + y := 3 + z := 3 + continue + } + case 2 { + x := 4 + y := 4 + z := 4 + break + } + case 3 { + x := 5 + y := 5 + z := 5 + continue + } + mstore(y, 9) + } + mstore(x, 0x42) +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x := 1 +// let y := 1 +// let z := 1 +// for { +// } +// calldataload(0) +// { +// mstore(x, 2) +// mstore(z, 2) +// } +// { +// y := 3 +// switch callvalue() +// case 0 { +// x := 2 +// break +// } +// case 1 { +// x := 3 +// z := 3 +// continue +// } +// case 2 { +// x := 4 +// break +// } +// case 3 { +// x := 5 +// z := 5 +// continue +// } +// mstore(y, 9) +// } +// mstore(x, 0x42) +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_nested.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_nested.yul new file mode 100644 index 000000000000..067a999ccb21 --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_nested.yul @@ -0,0 +1,77 @@ +{ + let x := 1 + let y := 1 + let a := 7 + let b := 9 + for { } calldataload(0) { } + { + y := 9 + mstore(a, 7) + if callvalue() { + x := 2 + for {} calldataload(1) {} + { + a := 2 // can be removed + if eq(x, 3) { + b := 3 // cannot be removed + y := 2 // will be removed + continue + } + } + mstore(b, 2) + break + } + if eq(callvalue(), 3) { + x := 12 + y := 12 + continue + } + x := 3 + mstore(y, 3) + } + mstore(x, 0x42) +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x := 1 +// let y := 1 +// let a := 7 +// let b := 9 +// for { +// } +// calldataload(0) +// { +// } +// { +// y := 9 +// mstore(a, 7) +// if callvalue() +// { +// x := 2 +// for { +// } +// calldataload(1) +// { +// } +// { +// if eq(x, 3) +// { +// b := 3 +// continue +// } +// } +// mstore(b, 2) +// break +// } +// if eq(callvalue(), 3) +// { +// x := 12 +// continue +// } +// x := 3 +// mstore(y, 3) +// } +// mstore(x, 0x42) +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_rerun.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_rerun.yul index 65eb2838a675..d9da624d6536 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_rerun.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_rerun.yul @@ -10,8 +10,9 @@ } x := 3 } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let x // x := 1 diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_stmnts_after_break_continue.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_stmnts_after_break_continue.yul new file mode 100644 index 000000000000..aded61cda43d --- /dev/null +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/for_stmnts_after_break_continue.yul @@ -0,0 +1,53 @@ +{ + let x := 1 + let y := 1 + for { } calldataload(0) { } + { + y := 9 + if callvalue() { + x := 2 + y := 2 // will be removed + break + x := 7 // after break, we start with fresh state. + } + if eq(callvalue(), 3) { + x := 12 + y := 12 // will be removed + continue + x := 17 // after continue, we start with fresh state. + y := 9 + } + x := 3 + mstore(y, 3) + } + mstore(x, 0x42) +} +// ==== +// step: redundantAssignEliminator +// ---- +// { +// let x := 1 +// let y := 1 +// for { +// } +// calldataload(0) +// { +// } +// { +// y := 9 +// if callvalue() +// { +// x := 2 +// break +// } +// if eq(callvalue(), 3) +// { +// x := 12 +// continue +// y := 9 +// } +// x := 3 +// mstore(y, 3) +// } +// mstore(x, 0x42) +// } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/function.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/function.yul index 5bb920ec3a90..444f73265966 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/function.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/function.yul @@ -11,8 +11,9 @@ } r := 2 } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let r // function f(x, y) -> a, b diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/if.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/if.yul index 958bfc661697..69c41c0bef41 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/if.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/if.yul @@ -9,8 +9,9 @@ // This enforces that none of the assignments above can be removed. mstore(0, d) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let c // let d diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_overwrite_all_branches.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_overwrite_all_branches.yul index e47c31d1ced6..de9d4dc0a8be 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_overwrite_all_branches.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_overwrite_all_branches.yul @@ -10,8 +10,9 @@ d := 3 mstore(0, d) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let c // let d diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_used_in_one_branch.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_used_in_one_branch.yul index 00065ed2572a..653446aae904 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_used_in_one_branch.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/if_used_in_one_branch.yul @@ -10,8 +10,9 @@ d := 3 mstore(0, d) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let c // let d diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/multi_assign.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/multi_assign.yul index 26bcfc725e1b..918eb89df18b 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/multi_assign.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/multi_assign.yul @@ -8,8 +8,9 @@ x := 3 y := 4 } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // function f() -> a, b // { diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/multivar.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/multivar.yul index cf646126885c..b094f25b7621 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/multivar.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/multivar.yul @@ -5,8 +5,9 @@ b := a a := b } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let a := 2 // a := 7 diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/non_movable.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/non_movable.yul index ae3e5226dc1f..a5e697555cdd 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/non_movable.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/non_movable.yul @@ -3,8 +3,9 @@ a := 0 a := mload(0) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let a // a := mload(0) diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/scopes.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/scopes.yul index 702f854dfab6..7d6afaf028e1 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/scopes.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/scopes.yul @@ -6,8 +6,9 @@ a := 2 } } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let a // { diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/simple.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/simple.yul index 913a7694117d..e05823ac8f31 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/simple.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/simple.yul @@ -3,8 +3,9 @@ a := 1 a := 2 } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let a // } diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_all.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_all.yul index 962655764c49..1df661d1a640 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_all.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_all.yul @@ -7,8 +7,9 @@ default { x := 3 } mstore(x, 0) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let x // switch calldataload(0) diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_one.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_one.yul index cbe859ed6343..47bc9ff4674f 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_one.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_in_one.yul @@ -6,8 +6,9 @@ case 0 { x := 2 } mstore(x, 0) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let x // x := 1 diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_use_combination.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_use_combination.yul index 1a3b26ebd589..25c360531090 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_use_combination.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_overwrite_use_combination.yul @@ -7,8 +7,9 @@ default { mstore(x, 1) } mstore(x, 0) } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let x // x := 1 diff --git a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_unused.yul b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_unused.yul index cc78b74d3add..c45eb8423bf4 100644 --- a/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_unused.yul +++ b/test/libyul/yulOptimizerTests/redundantAssignEliminator/switch_unused.yul @@ -5,8 +5,9 @@ switch calldataload(0) case 0 { mstore(0, 1) } } +// ==== +// step: redundantAssignEliminator // ---- -// redundantAssignEliminator // { // let x // switch calldataload(0) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul index 3160381f6e8c..bd24b0903830 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul @@ -1,15 +1,17 @@ { let a := caller() - for { pop(a) } a { pop(a) } { + pop(a) + for { } a { pop(a) } { pop(a) } } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := caller() +// pop(caller()) // for { -// pop(caller()) // } // caller() // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul index eb092e956da0..a7325ecef464 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul @@ -1,17 +1,19 @@ { let a := caller() - for { pop(a) } a { pop(a) } { + pop(a) + for { } a { pop(a) } { a := address() let c := a } let x := a } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := caller() +// pop(caller()) // for { -// pop(caller()) // } // a // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init1.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init1.yul deleted file mode 100644 index e7c689ca1f67..000000000000 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init1.yul +++ /dev/null @@ -1,23 +0,0 @@ -{ - let b := 0 - for { let a := caller() pop(a) } a { pop(a) } { - b := 1 pop(a) - } -} -// ---- -// rematerialiser -// { -// let b := 0 -// for { -// let a := caller() -// pop(caller()) -// } -// caller() -// { -// pop(caller()) -// } -// { -// b := 1 -// pop(caller()) -// } -// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init2.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init2.yul deleted file mode 100644 index 80ee92332376..000000000000 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init2.yul +++ /dev/null @@ -1,24 +0,0 @@ -{ - let b := 0 - for { let a := caller() pop(a) } lt(a, 0) { pop(a) a := add(a, 3) } { - b := 1 pop(a) - } -} -// ---- -// rematerialiser -// { -// let b := 0 -// for { -// let a := caller() -// pop(caller()) -// } -// lt(a, 0) -// { -// pop(a) -// a := add(a, 3) -// } -// { -// b := 1 -// pop(a) -// } -// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul index 2aff06d473e2..e664291f57ce 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul @@ -4,8 +4,9 @@ if b { pop(b) b := a } let c := b } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := caller() // let b := address() diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_switch.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_switch.yul index 8f70a79d93b7..71a3a2c7560f 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_switch.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_switch.yul @@ -6,8 +6,9 @@ default { let x := a let y := b b := a } pop(add(a, b)) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := 1 // let b := 2 diff --git a/test/libyul/yulOptimizerTests/rematerialiser/cheap_caller.yul b/test/libyul/yulOptimizerTests/rematerialiser/cheap_caller.yul index 7e99e42893c1..95733af88d41 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/cheap_caller.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/cheap_caller.yul @@ -6,8 +6,9 @@ mstore(add(a, a), mload(a)) sstore(a, sload(a)) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := caller() // mstore(caller(), caller()) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/do_not_move_out_of_scope.yul b/test/libyul/yulOptimizerTests/rematerialiser/do_not_move_out_of_scope.yul index 891a5043b66b..074100d899cc 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/do_not_move_out_of_scope.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/do_not_move_out_of_scope.yul @@ -7,8 +7,9 @@ } let b := x } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let x // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/do_remat_large_amounts_of_code_if_used_once.yul b/test/libyul/yulOptimizerTests/rematerialiser/do_remat_large_amounts_of_code_if_used_once.yul index e464d404aa70..cc009509f225 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/do_remat_large_amounts_of_code_if_used_once.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/do_remat_large_amounts_of_code_if_used_once.yul @@ -2,8 +2,9 @@ let x := add(mul(calldataload(2), calldataload(4)), mul(2, calldatasize())) let b := x } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let x := add(mul(calldataload(2), calldataload(4)), mul(2, calldatasize())) // let b := add(mul(calldataload(2), calldataload(4)), mul(2, calldatasize())) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_break.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_break.yul index f835e84ca1eb..b9ec6f2a2757 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/for_break.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_break.yul @@ -1,7 +1,8 @@ { let a let b - for {let i := 0} lt(i, 10) {i := add(a, b)} { + let i := 0 + for {} lt(i, 10) {i := add(a, b)} { a := origin() b := origin() b := caller() @@ -12,13 +13,14 @@ } mstore(a, b) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a // let b +// let i := 0 // for { -// let i := 0 // } // lt(i, 10) // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul index 96f65ddd5538..baf4c09e063d 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_continue.yul @@ -1,7 +1,8 @@ { let a let b - for { let i := 0 } + let i := 0 + for { } lt(i, 10) { i := add(a, b) } // `b` is always known to be caller() but `a` may be origin() or caller(). { @@ -14,13 +15,14 @@ } mstore(a, b) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a // let b +// let i := 0 // for { -// let i := 0 // } // lt(i, 10) // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul index a95d24add489..6e946660c5f8 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_2.yul @@ -2,7 +2,8 @@ let a let b let c - for { let i := 0 } + let i := 0 + for { } lt(i, 10) { i := add(add(a, b), c) } // `b` is always known to be caller() but `a` and `c` may be origin() or caller(). { @@ -19,14 +20,15 @@ } mstore(a, b) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a // let b // let c +// let i := 0 // for { -// let i := 0 // } // lt(i, 10) // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul index 7620791c6c64..325dd22845fe 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/for_continue_with_assignment_in_post.yul @@ -2,10 +2,10 @@ let a let b let c + let i := 0 + b := origin() + c := origin() for { - let i := 0 - b := origin() - c := origin() } lt(i, 10) { @@ -23,16 +23,17 @@ let x := b // does not rematerialize as b may be either origin() or callvalue() (btw: not caller()) let y := c // does not rematerialize as c may be either origin() or caller() } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a // let b // let c +// let i := 0 +// b := origin() +// c := origin() // for { -// let i := 0 -// b := origin() -// c := origin() // } // lt(i, 10) // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/large_constant.yul b/test/libyul/yulOptimizerTests/rematerialiser/large_constant.yul index 9c7c66f146ef..9f57a473961c 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/large_constant.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/large_constant.yul @@ -4,8 +4,9 @@ let a := 0xffffffffffffffffffffff mstore(a, a) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := 0xffffffffffffffffffffff // mstore(a, a) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/large_constant_used_once.yul b/test/libyul/yulOptimizerTests/rematerialiser/large_constant_used_once.yul index b8a861aad977..6d388c19ead3 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/large_constant_used_once.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/large_constant_used_once.yul @@ -5,8 +5,9 @@ let a := 0xffffffffffffffffffffff mstore(0, a) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := 0xffffffffffffffffffffff // mstore(0, 0xffffffffffffffffffffff) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/medium_sized_constant.yul b/test/libyul/yulOptimizerTests/rematerialiser/medium_sized_constant.yul index 98cdbd09c30a..ec79843b1e49 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/medium_sized_constant.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/medium_sized_constant.yul @@ -12,8 +12,9 @@ mstore(add(a, a), a) mstore(a, mload(a)) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let b := 2 // mstore(2, 2) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/non_movable_function.yul b/test/libyul/yulOptimizerTests/rematerialiser/non_movable_function.yul index 9a041dfc059f..663c70ca95ae 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/non_movable_function.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/non_movable_function.yul @@ -5,8 +5,9 @@ let c := a mstore(add(a, b), c) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // function f(x) -> y // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/non_movable_instruction.yul b/test/libyul/yulOptimizerTests/rematerialiser/non_movable_instruction.yul index 8767abc95585..0fb8856049be 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/non_movable_instruction.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/non_movable_instruction.yul @@ -4,8 +4,9 @@ let c := a mstore(add(a, b), c) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := 1 // let b := mload(1) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/reassign.yul b/test/libyul/yulOptimizerTests/rematerialiser/reassign.yul index 471246587856..fe3cf5812684 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/reassign.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/reassign.yul @@ -6,8 +6,9 @@ let d := add(b, c) pop(a) pop(b) pop(c) pop(d) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := extcodesize(0) // let b := a diff --git a/test/libyul/yulOptimizerTests/rematerialiser/reassignment.yul b/test/libyul/yulOptimizerTests/rematerialiser/reassignment.yul index 13238780f97a..28709ac1ff2d 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/reassignment.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/reassignment.yul @@ -5,8 +5,9 @@ let b := mload(a) pop(b) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := 1 // pop(1) diff --git a/test/libyul/yulOptimizerTests/rematerialiser/smoke.yul b/test/libyul/yulOptimizerTests/rematerialiser/smoke.yul index 2423db327af5..ae0286f904ec 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/smoke.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/smoke.yul @@ -1,5 +1,6 @@ {} +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/trivial.yul b/test/libyul/yulOptimizerTests/rematerialiser/trivial.yul index d29ea98adedf..35d4f0332a4a 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/trivial.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/trivial.yul @@ -3,8 +3,9 @@ let b := a mstore(0, b) } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := 1 // let b := 1 diff --git a/test/libyul/yulOptimizerTests/rematerialiser/update_asignment_remat.yul b/test/libyul/yulOptimizerTests/rematerialiser/update_asignment_remat.yul index 7d35fee0456c..076131cde2ec 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/update_asignment_remat.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/update_asignment_remat.yul @@ -4,8 +4,9 @@ a := mul(a, 2) let b := a } +// ==== +// step: rematerialiser // ---- -// rematerialiser // { // let a := extcodesize(0) // a := mul(a, 2) diff --git a/test/libyul/yulOptimizerTests/splitJoin/control_flow.yul b/test/libyul/yulOptimizerTests/splitJoin/control_flow.yul index ad609c74bd98..a87fe4fd0f65 100644 --- a/test/libyul/yulOptimizerTests/splitJoin/control_flow.yul +++ b/test/libyul/yulOptimizerTests/splitJoin/control_flow.yul @@ -6,8 +6,9 @@ } } } +// ==== +// step: splitJoin // ---- -// splitJoin // { // if mul(add(calldataload(0), 2), 3) // { diff --git a/test/libyul/yulOptimizerTests/splitJoin/functions.yul b/test/libyul/yulOptimizerTests/splitJoin/functions.yul index 549fc55032c6..12bbe4cfb860 100644 --- a/test/libyul/yulOptimizerTests/splitJoin/functions.yul +++ b/test/libyul/yulOptimizerTests/splitJoin/functions.yul @@ -8,8 +8,9 @@ sstore(b, mul(b, 2)) } } +// ==== +// step: splitJoin // ---- -// splitJoin // { // let x := f(0) // function f(y) -> r diff --git a/test/libyul/yulOptimizerTests/splitJoin/smoke.yul b/test/libyul/yulOptimizerTests/splitJoin/smoke.yul index 4b1330293bf7..073663ff4284 100644 --- a/test/libyul/yulOptimizerTests/splitJoin/smoke.yul +++ b/test/libyul/yulOptimizerTests/splitJoin/smoke.yul @@ -1,5 +1,6 @@ {} +// ==== +// step: splitJoin // ---- -// splitJoin // { // } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul index 18498e6117aa..99d3d56e5fc5 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul @@ -1,7 +1,7 @@ { + let a := mload(0) + let b := mload(1) for { - let a := mload(0) - let b := mload(1) } lt(mload(a),mload(b)) { @@ -15,12 +15,13 @@ b := mload(a) } } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { +// let a := mload(0) +// let b := mload(1) // for { -// let a := mload(0) -// let b := mload(1) // } // lt(mload(a), mload(b)) // { diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul index 23c433d1345a..2dd2d98b0dd6 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul @@ -6,8 +6,9 @@ a := mload(4) mstore(a, 0) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // pop(mload(0)) // pop(mload(1)) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul index fd5981efced6..fcdb644379a5 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul @@ -8,8 +8,9 @@ } mstore(a, 0) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // let a := mload(0) // if mload(1) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul index b0b3efb54f1b..b6eb0b6cf9cf 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul @@ -9,8 +9,9 @@ } mstore(a, b) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // let a := mload(0) // let b := mload(1) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul index 50f56b870973..e747e85032b7 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul @@ -16,8 +16,9 @@ } mstore(a, b) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // let a := mload(0) // let b := mload(1) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul index 1efbbde75f6c..024254525037 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul @@ -13,8 +13,9 @@ } mstore(a, 0) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // let a := mload(0) // switch mload(1) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul b/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul index 912940c504bc..dacbc44849e5 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul @@ -3,8 +3,9 @@ a := mload(1) mstore(a, 0) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // pop(mload(0)) // let a_2 := mload(1) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul index 5185245cc4c6..04faa44dce1b 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul @@ -6,8 +6,9 @@ } mstore(a, 0) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // let a := mload(0) // if mload(1) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul index e0e53b3fefda..d31fb549ac26 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul @@ -9,8 +9,9 @@ } mstore(a, 0) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // let a := mload(0) // switch mload(1) diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul b/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul index d2ba64715f70..f14b1fe3201f 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul @@ -20,8 +20,9 @@ let a,b := abi_decode_t_bytes_calldata_ptr(mload(0),mload(1)) mstore(a,b) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 // { diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul b/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul index 9f2a046e121f..1c246a754d16 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul @@ -7,8 +7,9 @@ b := mload(a) mstore(a, b) } +// ==== +// step: ssaAndBack // ---- -// ssaAndBack // { // let a_1 := mload(0) // let b_2 := mload(a_1) diff --git a/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul b/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul index d2408343b0ac..e4f06a781e03 100644 --- a/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul +++ b/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul @@ -4,13 +4,15 @@ mstore(to, length) from := add(from, 0x20) to := add(to, 0x20) - for { let x := 1 } lt(x, length) { x := add(x, 0x20) } { + let x := 1 + for { } lt(x, length) { x := add(x, 0x20) } { mstore(add(to, x), mload(add(from, x))) } } } +// ==== +// step: ssaPlusCleanup // ---- -// ssaPlusCleanup // { // function copy(from, to) -> length // { @@ -19,9 +21,9 @@ // mstore(to, length_1) // let from_2 := add(from, 0x20) // let to_3 := add(to, 0x20) +// let x_4 := 1 +// let x := x_4 // for { -// let x_4 := 1 -// let x := x_4 // } // lt(x, length_1) // { diff --git a/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign.yul b/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign.yul index ddb33aa01119..105970c3c24d 100644 --- a/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign.yul +++ b/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign.yul @@ -5,8 +5,9 @@ a := 4 mstore(0, a) } +// ==== +// step: ssaPlusCleanup // ---- -// ssaPlusCleanup // { // let a_1 := 1 // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign_with_use.yul b/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign_with_use.yul index 67a6c5d3ec76..6770fee2b5a2 100644 --- a/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign_with_use.yul +++ b/test/libyul/yulOptimizerTests/ssaPlusCleanup/multi_reassign_with_use.yul @@ -5,8 +5,9 @@ a := mload(add(a, 4)) mstore(0, a) } +// ==== +// step: ssaPlusCleanup // ---- -// ssaPlusCleanup // { // let a_1 := 1 // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul b/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul index 923a42ba6657..32e05ad35386 100644 --- a/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul +++ b/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul @@ -19,8 +19,9 @@ } } } +// ==== +// step: ssaReverser // ---- -// ssaReverser // { // function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 // { diff --git a/test/libyul/yulOptimizerTests/ssaReverser/simple.yul b/test/libyul/yulOptimizerTests/ssaReverser/simple.yul index eba1f5f18401..03dab25f91cd 100644 --- a/test/libyul/yulOptimizerTests/ssaReverser/simple.yul +++ b/test/libyul/yulOptimizerTests/ssaReverser/simple.yul @@ -4,8 +4,9 @@ a := a_1 mstore(a_1, 0) } +// ==== +// step: ssaReverser // ---- -// ssaReverser // { // let a := mload(1) // a := mload(0) diff --git a/test/libyul/yulOptimizerTests/ssaTransform/branches.yul b/test/libyul/yulOptimizerTests/ssaTransform/branches.yul index c089fe7083c3..c2fc3c7746b9 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/branches.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/branches.yul @@ -7,8 +7,9 @@ a := add(a, 1) mstore(a, 1) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := 1 // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul index 416403465529..b16257401ebf 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul @@ -6,8 +6,9 @@ } mstore(0, a) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := mload(0) // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul index 821a5b2a68e0..8796453d0859 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul @@ -6,8 +6,9 @@ } mstore(0, a) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := mload(0) // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul index 1fc075bc1451..cd34f2c8d6e8 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul @@ -6,8 +6,9 @@ } mstore(0, a) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := mload(0) // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul index 273d3811a6f8..d5e1a5404637 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul @@ -13,8 +13,9 @@ } a := add(a, 8) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := mload(0) // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/function.yul b/test/libyul/yulOptimizerTests/ssaTransform/function.yul index 995d16cc7c90..16f5b03cd703 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/function.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/function.yul @@ -6,8 +6,9 @@ a := add(a, d) } } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // function f(a, b) -> c, d // { diff --git a/test/libyul/yulOptimizerTests/ssaTransform/multi_assign.yul b/test/libyul/yulOptimizerTests/ssaTransform/multi_assign.yul new file mode 100644 index 000000000000..41905cc7983c --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaTransform/multi_assign.yul @@ -0,0 +1,29 @@ +{ + let a := mload(0) + let b := mload(1) + a, b := f() + sstore(a, b) + a := mload(5) + b := mload(a) + function f() -> x, y {} +} +// ==== +// step: ssaTransform +// ---- +// { +// let a_1 := mload(0) +// let a := a_1 +// let b_2 := mload(1) +// let b := b_2 +// let a_3, b_4 := f() +// a := a_3 +// b := b_4 +// sstore(a_3, b_4) +// let a_5 := mload(5) +// a := a_5 +// let b_6 := mload(a_5) +// b := b_6 +// function f() -> x, y +// { +// } +// } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/multi_decl.yul b/test/libyul/yulOptimizerTests/ssaTransform/multi_decl.yul new file mode 100644 index 000000000000..4f5949c8ec08 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaTransform/multi_decl.yul @@ -0,0 +1,25 @@ +{ + let x, y := f(1, 2) + x := mload(y) + y := mload(x) + let a, b := f(x, y) + sstore(a, b) + function f(t, v) -> w, z {} +} +// ==== +// step: ssaTransform +// ---- +// { +// let x_1, y_2 := f(1, 2) +// let x := x_1 +// let y := y_2 +// let x_3 := mload(y_2) +// x := x_3 +// let y_4 := mload(x_3) +// y := y_4 +// let a, b := f(x_3, y_4) +// sstore(a, b) +// function f(t, v) -> w, z +// { +// } +// } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/nested.yul b/test/libyul/yulOptimizerTests/ssaTransform/nested.yul index 49a76953774c..7f3af38c22e2 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/nested.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/nested.yul @@ -10,8 +10,9 @@ } a := add(b, a) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := 1 // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/notransform.yul b/test/libyul/yulOptimizerTests/ssaTransform/notransform.yul index 297905c60fc2..f42de9976228 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/notransform.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/notransform.yul @@ -6,8 +6,9 @@ mstore(c, 0) c := add(a, b) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a := 1 // let b := add(a, 2) diff --git a/test/libyul/yulOptimizerTests/ssaTransform/simple.yul b/test/libyul/yulOptimizerTests/ssaTransform/simple.yul index 6dbce7292104..d23d07d82e06 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/simple.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/simple.yul @@ -4,8 +4,9 @@ a := 3 a := 4 } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := 1 // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/switch.yul b/test/libyul/yulOptimizerTests/ssaTransform/switch.yul index bc9b55bbb051..404736923362 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/switch.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/switch.yul @@ -8,8 +8,9 @@ default { a := add(a, 8) } mstore(0, a) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := mload(0) // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/ssaTransform/used.yul b/test/libyul/yulOptimizerTests/ssaTransform/used.yul index ad686ca1158d..ef104512b0f4 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/used.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/used.yul @@ -14,8 +14,9 @@ a := 4 mstore(a, 0) } +// ==== +// step: ssaTransform // ---- -// ssaTransform // { // let a_1 := 1 // let a := a_1 diff --git a/test/libyul/yulOptimizerTests/stackCompressor/inlineInBlock.yul b/test/libyul/yulOptimizerTests/stackCompressor/inlineInBlock.yul index 4e594eee4a60..6c0ceef63a96 100644 --- a/test/libyul/yulOptimizerTests/stackCompressor/inlineInBlock.yul +++ b/test/libyul/yulOptimizerTests/stackCompressor/inlineInBlock.yul @@ -3,8 +3,9 @@ let y := calldataload(calldataload(9)) mstore(y, add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(y, 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1)) } +// ==== +// step: stackCompressor // ---- -// stackCompressor // { // mstore(calldataload(calldataload(9)), add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(calldataload(calldataload(9)), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1)) // } diff --git a/test/libyul/yulOptimizerTests/stackCompressor/inlineInFunction.yul b/test/libyul/yulOptimizerTests/stackCompressor/inlineInFunction.yul index fc89a8f77194..e74e788aaa57 100644 --- a/test/libyul/yulOptimizerTests/stackCompressor/inlineInFunction.yul +++ b/test/libyul/yulOptimizerTests/stackCompressor/inlineInFunction.yul @@ -5,8 +5,9 @@ mstore(y, add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(y, 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1)) } } +// ==== +// step: stackCompressor // ---- -// stackCompressor // { // let x := 8 // function f() diff --git a/test/libyul/yulOptimizerTests/stackCompressor/noInline.yul b/test/libyul/yulOptimizerTests/stackCompressor/noInline.yul index d357269349c3..e70bb9dc3c20 100644 --- a/test/libyul/yulOptimizerTests/stackCompressor/noInline.yul +++ b/test/libyul/yulOptimizerTests/stackCompressor/noInline.yul @@ -2,8 +2,9 @@ let x := 8 function f() { let y := 9 } } +// ==== +// step: stackCompressor // ---- -// stackCompressor // { // let x := 8 // function f() diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/bugfix_visit_after_change.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/bugfix_visit_after_change.yul index a68984b8bbb3..4f4d445ac4fe 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/bugfix_visit_after_change.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/bugfix_visit_after_change.yul @@ -6,8 +6,9 @@ x := 1 } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let x := 0 // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_movable_condition.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_movable_condition.yul index ee1975e71ae9..98adbb596d15 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_movable_condition.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_movable_condition.yul @@ -1,6 +1,7 @@ { let a := mload(0) if a {} } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let a := mload(0) // pop(a) diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_non_movable_condition.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_non_movable_condition.yul index 5977297be7ba..70257e03acc2 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_non_movable_condition.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_non_movable_condition.yul @@ -1,6 +1,7 @@ { if mload(0) {} } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // pop(mload(0)) // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/for_false_condition.sol b/test/libyul/yulOptimizerTests/structuralSimplifier/for_false_condition.sol index b881a0a30f9b..5c4faebf2740 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/for_false_condition.sol +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/for_false_condition.sol @@ -3,8 +3,9 @@ let b := a } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let a := 42 // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/if_false_condition.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/if_false_condition.yul index 0895b1bbd55f..59dbcaf2cd0f 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/if_false_condition.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/if_false_condition.yul @@ -1,5 +1,6 @@ { if 0 { mstore(0, 0) } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/if_multi_unassigned_condition.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/if_multi_unassigned_condition.yul index 0ece5dbdb14c..efd21db5f794 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/if_multi_unassigned_condition.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/if_multi_unassigned_condition.yul @@ -3,8 +3,9 @@ if x { mstore(0, 0) } if y { mstore(0, 0) } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let x, y // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/if_true_condition.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/if_true_condition.yul index ca9cba06d1d0..962eff9e2f5e 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/if_true_condition.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/if_true_condition.yul @@ -1,6 +1,7 @@ { if 1 { mstore(0, 0) } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // mstore(0, 0) // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/if_unassigned_condition.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/if_unassigned_condition.yul index a327a882970a..aefdb7869eb2 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/if_unassigned_condition.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/if_unassigned_condition.yul @@ -2,8 +2,9 @@ let x if x { mstore(0, 0) } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let x // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/nested.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/nested.yul index 169a84d1d35e..5fe6bdce4578 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/nested.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/nested.yul @@ -1,6 +1,7 @@ { if 1 { if 1 { for { mstore(0, 0) } 0 {} { mstore(2, 3) } if 0 { mstore(1, 2) } } } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // mstore(0, 0) // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline.yul index bca6d481ca0f..2476afc94e27 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline.yul @@ -4,8 +4,9 @@ case 0 { y := 8 } case 1 { y := 9 } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // { diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_match_default.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_match_default.yul index 5ffcf16a5a3b..edc9cb379bb1 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_match_default.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_match_default.yul @@ -5,8 +5,9 @@ case 1 { y := 9 } default { y := 10 } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // { diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match.yul index 2006b49c7e6e..04b473600414 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match.yul @@ -4,8 +4,9 @@ case 0 { y := 8 } case 1 { y := 9 } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match_mixed.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match_mixed.yul index 8d90ca1f08ee..9c9fd5eba844 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match_mixed.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_inline_no_match_mixed.yul @@ -5,8 +5,9 @@ case "" { y := 8 } case 1 { y := 9 } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // } diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_no_remove_empty_case.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_no_remove_empty_case.yul index f6e99cd4802f..a09a5510b763 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_no_remove_empty_case.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_no_remove_empty_case.yul @@ -5,8 +5,9 @@ case 1 { y := 9 } default { y := 100 } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // switch calldataload(0) diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_only_default.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_only_default.yul index 7ca815a70631..a9f3aea69042 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_only_default.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_only_default.yul @@ -1,8 +1,9 @@ { switch mload(0) default { mstore(1, 2) } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // pop(mload(0)) // { diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul index 14043b2d0d46..d8febde88dac 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul @@ -10,8 +10,9 @@ case 1 { } default { } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // pop(add(y, 4)) diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul index f1a09eb8aa56..9cbae83baaed 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul @@ -5,8 +5,9 @@ case 1 { y := 9 } case 2 { y := 10 } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // switch calldataload(0) diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul index d90a0e1731ae..5ffdcb1eee75 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul @@ -5,8 +5,9 @@ case 1 { y := 9 } default { } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // if eq(1, calldataload(0)) diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul index 158cd013e8f2..1c9f64a211d2 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul @@ -5,8 +5,9 @@ case 2 { y := 10 } default { } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // let y := 200 // switch calldataload(0) diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul index e015dc1f314d..97a4ab181acc 100644 --- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul +++ b/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul @@ -1,8 +1,9 @@ { switch calldataload(0) case 2 { mstore(0, 0) } } +// ==== +// step: structuralSimplifier // ---- -// structuralSimplifier // { // if eq(2, calldataload(0)) // { diff --git a/test/libyul/yulOptimizerTests/unusedPruner/functions.yul b/test/libyul/yulOptimizerTests/unusedPruner/functions.yul index ec9cdda837f1..44a15b152d47 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/functions.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/functions.yul @@ -2,7 +2,8 @@ function f() { let a := 1 } function g() { f() } } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // } diff --git a/test/libyul/yulOptimizerTests/unusedPruner/intermediate_assignment.yul b/test/libyul/yulOptimizerTests/unusedPruner/intermediate_assignment.yul index 4ed6dd2c50d5..1c1397061b42 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/intermediate_assignment.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/intermediate_assignment.yul @@ -3,8 +3,9 @@ a := 4 let b := 1 } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // let a := 1 // a := 4 diff --git a/test/libyul/yulOptimizerTests/unusedPruner/intermediate_multi_assignment.yul b/test/libyul/yulOptimizerTests/unusedPruner/intermediate_multi_assignment.yul index 94d101e9b106..744c58df636e 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/intermediate_multi_assignment.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/intermediate_multi_assignment.yul @@ -4,8 +4,9 @@ a := f() b := 1 } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // let a, b // function f() -> x diff --git a/test/libyul/yulOptimizerTests/unusedPruner/multi_assign.yul b/test/libyul/yulOptimizerTests/unusedPruner/multi_assign.yul index a14dc28cee57..35120111b52c 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/multi_assign.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/multi_assign.yul @@ -4,8 +4,9 @@ function f() -> x, y { } a, b := f() } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // let a // let b diff --git a/test/libyul/yulOptimizerTests/unusedPruner/multi_assignments.yul b/test/libyul/yulOptimizerTests/unusedPruner/multi_assignments.yul index fe94edb8f094..01e573199e1e 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/multi_assignments.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/multi_assignments.yul @@ -3,8 +3,9 @@ x := 1 y := 2 } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // let x, y // x := 1 diff --git a/test/libyul/yulOptimizerTests/unusedPruner/multi_declarations.yul b/test/libyul/yulOptimizerTests/unusedPruner/multi_declarations.yul index 3cf35007bafb..c7a8285ded6f 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/multi_declarations.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/multi_declarations.yul @@ -1,7 +1,8 @@ { let x, y } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // } diff --git a/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul b/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul index adabac87bbac..afa27ea8a440 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul @@ -2,8 +2,9 @@ function f() -> x, y { } let a, b := f() } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // function f() -> x, y // { diff --git a/test/libyul/yulOptimizerTests/unusedPruner/multi_partial_assignments.yul b/test/libyul/yulOptimizerTests/unusedPruner/multi_partial_assignments.yul index 5db0ade9a686..67d692f708f6 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/multi_partial_assignments.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/multi_partial_assignments.yul @@ -2,8 +2,9 @@ let x, y x := 1 } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // let x, y // x := 1 diff --git a/test/libyul/yulOptimizerTests/unusedPruner/pop.yul b/test/libyul/yulOptimizerTests/unusedPruner/pop.yul index 542070f9f400..76cb497b67d8 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/pop.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/pop.yul @@ -2,7 +2,8 @@ let a := 1 pop(a) } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // } diff --git a/test/libyul/yulOptimizerTests/unusedPruner/smoke.yul b/test/libyul/yulOptimizerTests/unusedPruner/smoke.yul index ca2ed9422d20..c29765b3d954 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/smoke.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/smoke.yul @@ -1,5 +1,6 @@ { } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // } diff --git a/test/libyul/yulOptimizerTests/unusedPruner/trivial.yul b/test/libyul/yulOptimizerTests/unusedPruner/trivial.yul index 9b4cf9fdb027..1297b26337ff 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/trivial.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/trivial.yul @@ -3,8 +3,9 @@ let b := 1 mstore(0, 1) } +// ==== +// step: unusedPruner // ---- -// unusedPruner // { // mstore(0, 1) // } diff --git a/test/libyul/yulOptimizerTests/varDeclInitializer/ambiguous.yul b/test/libyul/yulOptimizerTests/varDeclInitializer/ambiguous.yul index 5e2d60c2556f..054d0b53cfa5 100644 --- a/test/libyul/yulOptimizerTests/varDeclInitializer/ambiguous.yul +++ b/test/libyul/yulOptimizerTests/varDeclInitializer/ambiguous.yul @@ -11,8 +11,9 @@ let b := 2 let x, y := f() } +// ==== +// step: varDeclInitializer // ---- -// varDeclInitializer // { // function f() -> x, y // { diff --git a/test/libyul/yulOptimizerTests/varDeclInitializer/inside_func.yul b/test/libyul/yulOptimizerTests/varDeclInitializer/inside_func.yul index 16428d7e9dea..f9510e32f63a 100644 --- a/test/libyul/yulOptimizerTests/varDeclInitializer/inside_func.yul +++ b/test/libyul/yulOptimizerTests/varDeclInitializer/inside_func.yul @@ -8,8 +8,9 @@ let r r := 4 } +// ==== +// step: varDeclInitializer // ---- -// varDeclInitializer // { // function f() -> x, y // { diff --git a/test/libyul/yulOptimizerTests/varDeclInitializer/multi.yul b/test/libyul/yulOptimizerTests/varDeclInitializer/multi.yul index 02d731aff012..4911a6bd21c8 100644 --- a/test/libyul/yulOptimizerTests/varDeclInitializer/multi.yul +++ b/test/libyul/yulOptimizerTests/varDeclInitializer/multi.yul @@ -3,8 +3,9 @@ let a let b } +// ==== +// step: varDeclInitializer // ---- -// varDeclInitializer // { // let x := 0 // let y := 0 diff --git a/test/libyul/yulOptimizerTests/varDeclInitializer/multi_assign.yul b/test/libyul/yulOptimizerTests/varDeclInitializer/multi_assign.yul index 2e14fe707bdd..fac0eaa06bf9 100644 --- a/test/libyul/yulOptimizerTests/varDeclInitializer/multi_assign.yul +++ b/test/libyul/yulOptimizerTests/varDeclInitializer/multi_assign.yul @@ -7,8 +7,9 @@ let s := 3 let t } +// ==== +// step: varDeclInitializer // ---- -// varDeclInitializer // { // function f() -> x, y // { diff --git a/test/libyul/yulOptimizerTests/varDeclInitializer/simple.yul b/test/libyul/yulOptimizerTests/varDeclInitializer/simple.yul index 2a9bbe42c65b..819081fc197e 100644 --- a/test/libyul/yulOptimizerTests/varDeclInitializer/simple.yul +++ b/test/libyul/yulOptimizerTests/varDeclInitializer/simple.yul @@ -1,8 +1,9 @@ { let a } +// ==== +// step: varDeclInitializer // ---- -// varDeclInitializer // { // let a := 0 // } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/builtins.yul b/test/libyul/yulOptimizerTests/varNameCleaner/builtins.yul index f722037143ba..af7d62ccc128 100644 --- a/test/libyul/yulOptimizerTests/varNameCleaner/builtins.yul +++ b/test/libyul/yulOptimizerTests/varNameCleaner/builtins.yul @@ -1,8 +1,9 @@ { let datasize_256 := 1 } +// ==== +// step: varNameCleaner // ---- -// varNameCleaner // { // let datasize_1 := 1 // } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/function_names.yul b/test/libyul/yulOptimizerTests/varNameCleaner/function_names.yul index c336cd81340f..f4f2b76c80e9 100644 --- a/test/libyul/yulOptimizerTests/varNameCleaner/function_names.yul +++ b/test/libyul/yulOptimizerTests/varNameCleaner/function_names.yul @@ -3,8 +3,9 @@ function f() { let f_1 } let f_10 } +// ==== +// step: varNameCleaner // ---- -// varNameCleaner // { // let f_1 // function f() diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/function_parameters.yul b/test/libyul/yulOptimizerTests/varNameCleaner/function_parameters.yul index 510efddd0b6b..6f1d1817c6e2 100644 --- a/test/libyul/yulOptimizerTests/varNameCleaner/function_parameters.yul +++ b/test/libyul/yulOptimizerTests/varNameCleaner/function_parameters.yul @@ -8,8 +8,9 @@ } let f_10 } +// ==== +// step: varNameCleaner // ---- -// varNameCleaner // { // let f_1 // function f(x) -> x_1, y diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/function_scopes.yul b/test/libyul/yulOptimizerTests/varNameCleaner/function_scopes.yul index 8afd8e5e112b..866e7a3df5fe 100644 --- a/test/libyul/yulOptimizerTests/varNameCleaner/function_scopes.yul +++ b/test/libyul/yulOptimizerTests/varNameCleaner/function_scopes.yul @@ -2,8 +2,9 @@ function f() { let x_1 := 0 } function g() { let x_2 := 0 } } +// ==== +// step: varNameCleaner // ---- -// varNameCleaner // { // function f() // { diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/instructions.yul b/test/libyul/yulOptimizerTests/varNameCleaner/instructions.yul new file mode 100644 index 000000000000..7c5aca950f7b --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/instructions.yul @@ -0,0 +1,9 @@ +{ + let mul_256 := 1 +} +// ==== +// step: varNameCleaner +// ---- +// { +// let mul_1 := 1 +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/name_stripping.yul b/test/libyul/yulOptimizerTests/varNameCleaner/name_stripping.yul index f3e0b789d093..4104c9567799 100644 --- a/test/libyul/yulOptimizerTests/varNameCleaner/name_stripping.yul +++ b/test/libyul/yulOptimizerTests/varNameCleaner/name_stripping.yul @@ -4,8 +4,9 @@ let a_4312 := 0xdeadbeef let _42 := 21718 } +// ==== +// step: varNameCleaner // ---- -// varNameCleaner // { // let a := 1 // let a_1 := 2 diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling-inverse.yul b/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling-inverse.yul index 6818a88f3f9f..d96a7b0bec54 100644 --- a/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling-inverse.yul +++ b/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling-inverse.yul @@ -4,8 +4,9 @@ let x_2 := 3 let x_1 := 4 } +// ==== +// step: varNameCleaner // ---- -// varNameCleaner // { // let x := 1 // let x_1 := 2 diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling.yul b/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling.yul index 40898b78f3cb..e1132ad8b3fc 100644 --- a/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling.yul +++ b/test/libyul/yulOptimizerTests/varNameCleaner/reshuffling.yul @@ -3,8 +3,9 @@ let x_2 := 2 let x_3 := 3 } +// ==== +// step: varNameCleaner // ---- -// varNameCleaner // { // let x := 1 // let x_1 := 2 diff --git a/test/tools/IsolTestOptions.cpp b/test/tools/IsolTestOptions.cpp index dee4a01e195f..5b875393d99e 100644 --- a/test/tools/IsolTestOptions.cpp +++ b/test/tools/IsolTestOptions.cpp @@ -19,9 +19,14 @@ */ #include + +#include + #include -#include + #include +#include +#include namespace fs = boost::filesystem; namespace po = boost::program_options; @@ -32,7 +37,7 @@ namespace test { auto const description = R"(isoltest, tool for interactively managing test contracts. -Usage: isoltest [Options] --ipcpath ipcpath +Usage: isoltest [Options] Interactively validates test contracts. Allowed options)"; @@ -51,10 +56,10 @@ IsolTestOptions::IsolTestOptions(std::string* _editor): CommonOptions(description) { options.add_options() + ("editor", po::value(_editor)->default_value(editorPath()), "Path to editor for opening test files.") ("help", po::bool_switch(&showHelp), "Show this help screen.") - ("no-color", po::bool_switch(&noColor), "don't use colors") - ("editor", po::value(_editor)->default_value(editorPath()), "editor for opening test files"); - + ("no-color", po::bool_switch(&noColor), "Don't use colors.") + ("test,t", po::value(&testFilter)->default_value("*/*"), "Filters which test units to include."); } bool IsolTestOptions::parse(int _argc, char const* const* _argv) @@ -70,5 +75,16 @@ bool IsolTestOptions::parse(int _argc, char const* const* _argv) return res; } +void IsolTestOptions::validate() const +{ + static std::string filterString{"[a-zA-Z1-9_/*]*"}; + static std::regex filterExpression{filterString}; + assertThrow( + regex_match(testFilter, filterExpression), + ConfigException, + "Invalid test unit filter - can only contain '" + filterString + ": " + testFilter + ); +} + } } diff --git a/test/tools/IsolTestOptions.h b/test/tools/IsolTestOptions.h index 95bb5cc92f2d..e77e623f17d8 100644 --- a/test/tools/IsolTestOptions.h +++ b/test/tools/IsolTestOptions.h @@ -30,11 +30,13 @@ namespace test struct IsolTestOptions: CommonOptions { - bool noColor = false; bool showHelp = false; + bool noColor = false; + std::string testFilter = std::string{}; IsolTestOptions(std::string* _editor); bool parse(int _argc, char const* const* _argv) override; + void validate() const override; }; } } diff --git a/test/tools/afl_fuzzer.cpp b/test/tools/afl_fuzzer.cpp index d74be1efe0f2..2a8632899289 100644 --- a/test/tools/afl_fuzzer.cpp +++ b/test/tools/afl_fuzzer.cpp @@ -60,6 +60,10 @@ Allowed options)", "input-file", po::value(), "input file" + )( + "input-files", + po::value>()->multitoken(), + "input files" ) ( "without-optimizer", @@ -84,23 +88,56 @@ Allowed options)", return 1; } - string input; - if (arguments.count("input-file")) - input = readFileAsString(arguments["input-file"].as()); - else - input = readStandardInput(); - if (arguments.count("quiet")) quiet = true; if (arguments.count("help")) + { cout << options; - else if (arguments.count("const-opt")) - FuzzerUtil::testConstantOptimizer(input, quiet); - else if (arguments.count("standard-json")) - FuzzerUtil::testStandardCompiler(input, quiet); + return 0; + } + + vector inputs; + if (arguments.count("input-file")) + inputs.push_back(arguments["input-file"].as()); + else if (arguments.count("input-files")) + inputs = arguments["input-files"].as>(); else - FuzzerUtil::testCompiler(input, !arguments.count("without-optimizer"), quiet); + inputs.push_back(""); + + bool optimize = !arguments.count("without-optimizer"); + int retResult = 0; + + for (string const& inputFile: inputs) + { + string input; + if (inputFile.size() == 0) + input = readStandardInput(); + else + input = readFileAsString(inputFile); + + try + { + if (arguments.count("const-opt")) + FuzzerUtil::testConstantOptimizer(input, quiet); + else if (arguments.count("standard-json")) + FuzzerUtil::testStandardCompiler(input, quiet); + else + FuzzerUtil::testCompiler(input, optimize, quiet); + } + catch (...) + { + retResult = 1; + + if (inputFile.size() == 0) + throw; + + cerr << "Fuzzer " + << (optimize ? "" : "(without optimizer) ") + << "failed on " + << inputFile; + } + } - return 0; + return retResult; } diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index 0c031c9da0bf..0a193b4109fb 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -42,8 +42,9 @@ void FuzzerUtil::runCompiler(string const& _input, bool _quiet) Json::Value output; if (!jsonParseStrict(outputString, output)) { - cout << "Compiler produced invalid JSON output." << endl; - abort(); + string msg{"Compiler produced invalid JSON output."}; + cout << msg << endl; + throw std::runtime_error(std::move(msg)); } if (output.isMember("errors")) for (auto const& error: output["errors"]) @@ -54,8 +55,9 @@ void FuzzerUtil::runCompiler(string const& _input, bool _quiet) }); if (!invalid.empty()) { - cout << "Invalid error: \"" << error["type"].asString() << "\"" << endl; - abort(); + string msg = "Invalid error: \"" + error["type"].asString() + "\""; + cout << msg << endl; + throw std::runtime_error(std::move(msg)); } } } diff --git a/test/tools/fuzzer_common.h b/test/tools/fuzzer_common.h index ded0b6730907..edf196c1c81c 100644 --- a/test/tools/fuzzer_common.h +++ b/test/tools/fuzzer_common.h @@ -17,6 +17,10 @@ #include +/** + * Functions to be used for fuzz-testing of various components. + * They throw exceptions or error. + */ struct FuzzerUtil { static void runCompiler(std::string const& _input, bool _quiet); diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 1269f5e80938..f51b2300d602 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #if defined(_WIN32) #include @@ -45,6 +46,9 @@ using namespace std; namespace po = boost::program_options; namespace fs = boost::filesystem; +using TestCreator = TestCase::TestCaseCreator; +using TestOptions = dev::test::IsolTestOptions; + struct TestStats { int successCount = 0; @@ -60,17 +64,43 @@ struct TestStats } }; +class TestFilter +{ +public: + explicit TestFilter(string const& _filter): m_filter(_filter) + { + string filter{m_filter}; + + boost::replace_all(filter, "/", "\\/"); + boost::replace_all(filter, "*", ".*"); + + m_filterExpression = regex{"(" + filter + "(\\.sol|\\.yul))"}; + } + + bool matches(string const& _name) const + { + return regex_match(_name, m_filterExpression); + } + +private: + string m_filter; + regex m_filterExpression; +}; + class TestTool { public: TestTool( - TestCase::TestCaseCreator _testCaseCreator, - string const& _name, + TestCreator _testCaseCreator, + TestOptions const& _options, fs::path const& _path, - string const& _ipcPath, - bool _formatted, - langutil::EVMVersion _evmVersion - ): m_testCaseCreator(_testCaseCreator), m_name(_name), m_path(_path), m_ipcPath(_ipcPath), m_formatted(_formatted), m_evmVersion(_evmVersion) + string const& _name + ): + m_testCaseCreator(_testCaseCreator), + m_options(_options), + m_filter(TestFilter{_options.testFilter}), + m_path(_path), + m_name(_name) {} enum class Result @@ -84,12 +114,10 @@ class TestTool Result process(); static TestStats processPath( - TestCase::TestCaseCreator _testCaseCreator, + TestCreator _testCaseCreator, + TestOptions const& _options, fs::path const& _basepath, - fs::path const& _path, - string const& _ipcPath, - bool _formatted, - langutil::EVMVersion _evmVersion + fs::path const& _path ); static string editor; @@ -103,13 +131,14 @@ class TestTool Request handleResponse(bool _exception); - TestCase::TestCaseCreator m_testCaseCreator; - string const m_name; + TestCreator m_testCaseCreator; + TestOptions const& m_options; + TestFilter m_filter; fs::path const m_path; - string m_ipcPath; - bool const m_formatted = false; - langutil::EVMVersion const m_evmVersion; + string const m_name; + unique_ptr m_test; + static bool m_exitRequested; }; @@ -119,51 +148,58 @@ bool TestTool::m_exitRequested = false; TestTool::Result TestTool::process() { bool success; + bool formatted{!m_options.noColor}; std::stringstream outputMessages; - (AnsiColorized(cout, m_formatted, {BOLD}) << m_name << ": ").flush(); - try { - m_test = m_testCaseCreator(TestCase::Config{m_path.string(), m_ipcPath, m_evmVersion}); - if (m_test->supportedForEVMVersion(m_evmVersion)) - success = m_test->run(outputMessages, " ", m_formatted); - else + if (m_filter.matches(m_name)) { - AnsiColorized(cout, m_formatted, {BOLD, YELLOW}) << "NOT RUN" << endl; - return Result::Skipped; + (AnsiColorized(cout, formatted, {BOLD}) << m_name << ": ").flush(); + + m_test = m_testCaseCreator(TestCase::Config{m_path.string(), m_options.ipcPath.string(), m_options.evmVersion()}); + if (m_test->validateSettings(m_options.evmVersion())) + success = m_test->run(outputMessages, " ", formatted); + else + { + AnsiColorized(cout, formatted, {BOLD, YELLOW}) << "NOT RUN" << endl; + return Result::Skipped; + } } + else + return Result::Skipped; } catch(boost::exception const& _e) { - AnsiColorized(cout, m_formatted, {BOLD, RED}) << + AnsiColorized(cout, formatted, {BOLD, RED}) << "Exception during test: " << boost::diagnostic_information(_e) << endl; return Result::Exception; } catch (std::exception const& _e) { - AnsiColorized(cout, m_formatted, {BOLD, RED}) << + AnsiColorized(cout, formatted, {BOLD, RED}) << "Exception during test: " << _e.what() << endl; return Result::Exception; } catch (...) { - AnsiColorized(cout, m_formatted, {BOLD, RED}) << + AnsiColorized(cout, formatted, {BOLD, RED}) << "Unknown exception during test." << endl; return Result::Exception; } if (success) { - AnsiColorized(cout, m_formatted, {BOLD, GREEN}) << "OK" << endl; + AnsiColorized(cout, formatted, {BOLD, GREEN}) << "OK" << endl; return Result::Success; } else { - AnsiColorized(cout, m_formatted, {BOLD, RED}) << "FAIL" << endl; + AnsiColorized(cout, formatted, {BOLD, RED}) << "FAIL" << endl; - AnsiColorized(cout, m_formatted, {BOLD, CYAN}) << " Contract:" << endl; - m_test->printSource(cout, " ", m_formatted); + AnsiColorized(cout, formatted, {BOLD, CYAN}) << " Contract:" << endl; + m_test->printSource(cout, " ", formatted); + m_test->printUpdatedSettings(cout, " ", formatted); cout << endl << outputMessages.str() << endl; return Result::Failure; @@ -193,6 +229,7 @@ TestTool::Request TestTool::handleResponse(bool _exception) cout << endl; ofstream file(m_path.string(), ios::trunc); m_test->printSource(file); + m_test->printUpdatedSettings(file); file << "// ----" << endl; m_test->printUpdatedExpectations(file, "// "); return Request::Rerun; @@ -212,12 +249,10 @@ TestTool::Request TestTool::handleResponse(bool _exception) } TestStats TestTool::processPath( - TestCase::TestCaseCreator _testCaseCreator, + TestCreator _testCaseCreator, + TestOptions const& _options, fs::path const& _basepath, - fs::path const& _path, - string const& _ipcPath, - bool _formatted, - langutil::EVMVersion _evmVersion + fs::path const& _path ) { std::queue paths; @@ -249,7 +284,12 @@ TestStats TestTool::processPath( else { ++testCount; - TestTool testTool(_testCaseCreator, currentPath.string(), fullpath, _ipcPath, _formatted, _evmVersion); + TestTool testTool( + _testCaseCreator, + _options, + fullpath, + currentPath.string() + ); auto result = testTool.process(); switch(result) @@ -312,16 +352,15 @@ void setupTerminal() } boost::optional runTestSuite( - string const& _name, + TestCreator _testCaseCreator, + TestOptions const& _options, fs::path const& _basePath, fs::path const& _subdirectory, - string const& _ipcPath, - TestCase::TestCaseCreator _testCaseCreator, - bool _formatted, - langutil::EVMVersion _evmVersion + string const& _name ) { - fs::path testPath = _basePath / _subdirectory; + fs::path testPath{_basePath / _subdirectory}; + bool formatted{!_options.noColor}; if (!fs::exists(testPath) || !fs::is_directory(testPath)) { @@ -329,22 +368,29 @@ boost::optional runTestSuite( return {}; } - TestStats stats = TestTool::processPath(_testCaseCreator, _basePath, _subdirectory, _ipcPath, _formatted, _evmVersion); + TestStats stats = TestTool::processPath( + _testCaseCreator, + _options, + _basePath, + _subdirectory + ); - cout << endl << _name << " Test Summary: "; - AnsiColorized(cout, _formatted, {BOLD, stats ? GREEN : RED}) << - stats.successCount << - "/" << - stats.testCount; - cout << " tests successful"; - if (stats.skippedCount > 0) + if (stats.skippedCount != stats.testCount) { - cout << " ("; - AnsiColorized(cout, _formatted, {BOLD, YELLOW}) << stats.skippedCount; - cout<< " tests skipped)"; + cout << endl << _name << " Test Summary: "; + AnsiColorized(cout, formatted, {BOLD, stats ? GREEN : RED}) << + stats.successCount << + "/" << + stats.testCount; + cout << " tests successful"; + if (stats.skippedCount > 0) + { + cout << " ("; + AnsiColorized(cout, formatted, {BOLD, YELLOW}) << stats.skippedCount; + cout<< " tests skipped)"; + } + cout << "." << endl << endl; } - cout << "." << endl << endl; - return stats; } @@ -370,6 +416,7 @@ int main(int argc, char const *argv[]) } TestStats global_stats{0, 0}; + cout << "Running tests..." << endl << endl; // Actually run the tests. // Interactive tests are added in InteractiveTests.h @@ -381,7 +428,14 @@ int main(int argc, char const *argv[]) if (ts.smt && options.disableSMT) continue; - if (auto stats = runTestSuite(ts.title, options.testPath / ts.path, ts.subpath, options.ipcPath.string(), ts.testCaseCreator, !options.noColor, options.evmVersion())) + auto stats = runTestSuite( + ts.testCaseCreator, + options, + options.testPath / ts.path, + ts.subpath, + ts.title + ); + if (stats) global_stats += *stats; else return 1; diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 03d3facd9266..ff9f5e50d72f 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -16,17 +16,14 @@ */ #include - -#include -#include #include using namespace std; using namespace yul::test::yul_fuzzer; -std::string yul::test::yul_fuzzer::createHex(std::string const& _hexBytes) +string ProtoConverter::createHex(string const& _hexBytes) const { - std::string tmp{_hexBytes}; + string tmp{_hexBytes}; if (!tmp.empty()) { boost::range::remove_erase_if(tmp, [=](char c) -> bool { @@ -34,15 +31,15 @@ std::string yul::test::yul_fuzzer::createHex(std::string const& _hexBytes) }); tmp = tmp.substr(0, 64); } - // We need this awkward if case hex literals cannot be empty. + // We need this awkward if case because hex literals cannot be empty. if (tmp.empty()) tmp = "1"; return tmp; } -std::string yul::test::yul_fuzzer::createAlphaNum(std::string const& _strBytes) +string ProtoConverter::createAlphaNum(string const& _strBytes) const { - std::string tmp{_strBytes}; + string tmp{_strBytes}; if (!tmp.empty()) { boost::range::remove_erase_if(tmp, [=](char c) -> bool { @@ -53,318 +50,534 @@ std::string yul::test::yul_fuzzer::createAlphaNum(std::string const& _strBytes) return tmp; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Literal const& _x) +bool ProtoConverter::isCaseLiteralUnique(Literal const& _x) +{ + std::string tmp; + bool isUnique = false; + bool isEmptyString = false; + switch (_x.literal_oneof_case()) + { + case Literal::kIntval: + tmp = std::to_string(_x.intval()); + break; + case Literal::kHexval: + tmp = "0x" + createHex(_x.hexval()); + break; + case Literal::kStrval: + tmp = createAlphaNum(_x.strval()); + if (tmp.empty()) + { + isEmptyString = true; + tmp = std::to_string(0); + } + else + tmp = "\"" + tmp + "\""; + break; + case Literal::LITERAL_ONEOF_NOT_SET: + tmp = std::to_string(1); + break; + } + if (!_x.has_strval() || isEmptyString) + isUnique = m_switchLiteralSetPerScope.top().insert(dev::u256(tmp)).second; + else + isUnique = m_switchLiteralSetPerScope.top().insert( + dev::u256(dev::h256(tmp, dev::h256::FromBinary, dev::h256::AlignLeft))).second; + return isUnique; +} + +void ProtoConverter::visit(Literal const& _x) { switch (_x.literal_oneof_case()) { case Literal::kIntval: - _os << _x.intval(); + m_output << _x.intval(); break; case Literal::kHexval: - _os << "0x" << createHex(_x.hexval()); + m_output << "0x" << createHex(_x.hexval()); break; case Literal::kStrval: - _os << "\"" << createAlphaNum(_x.strval()) << "\""; + m_output << "\"" << createAlphaNum(_x.strval()) << "\""; break; case Literal::LITERAL_ONEOF_NOT_SET: - _os << "1"; + m_output << "1"; break; } - return _os; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarRef const& _x) +// Reference any index in [0, m_numLiveVars-1] or [0, m_numLiveVars) +void ProtoConverter::visit(VarRef const& _x) { - return _os << "x_" << (static_cast(_x.varnum()) % 10); + m_output << "x_" << (static_cast(_x.varnum()) % m_numLiveVars); } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Expression const& _x) +void ProtoConverter::visit(Expression const& _x) { switch (_x.expr_oneof_case()) { case Expression::kVarref: - _os << _x.varref(); + visit(_x.varref()); break; case Expression::kCons: - _os << _x.cons(); + visit(_x.cons()); break; case Expression::kBinop: - _os << _x.binop(); + visit(_x.binop()); break; case Expression::kUnop: - _os << _x.unop(); + visit(_x.unop()); + break; + case Expression::kTop: + visit(_x.top()); + break; + case Expression::kNop: + visit(_x.nop()); break; case Expression::EXPR_ONEOF_NOT_SET: - _os << "1"; + m_output << "1"; break; } - return _os; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, BinaryOp const& _x) +void ProtoConverter::visit(BinaryOp const& _x) { switch (_x.op()) { case BinaryOp::ADD: - _os << "add"; + m_output << "add"; break; case BinaryOp::SUB: - _os << "sub"; + m_output << "sub"; break; case BinaryOp::MUL: - _os << "mul"; + m_output << "mul"; break; case BinaryOp::DIV: - _os << "div"; + m_output << "div"; break; case BinaryOp::MOD: - _os << "mod"; + m_output << "mod"; break; case BinaryOp::XOR: - _os << "xor"; + m_output << "xor"; break; case BinaryOp::AND: - _os << "and"; + m_output << "and"; break; case BinaryOp::OR: - _os << "or"; + m_output << "or"; break; case BinaryOp::EQ: - _os << "eq"; + m_output << "eq"; break; case BinaryOp::LT: - _os << "lt"; + m_output << "lt"; break; case BinaryOp::GT: - _os << "gt"; + m_output << "gt"; break; case BinaryOp::SHR: - _os << "shr"; + m_output << "shr"; break; case BinaryOp::SHL: - _os << "shl"; + m_output << "shl"; break; case BinaryOp::SAR: - _os << "sar"; + m_output << "sar"; break; case BinaryOp::SDIV: - _os << "sdiv"; + m_output << "sdiv"; break; case BinaryOp::SMOD: - _os << "smod"; + m_output << "smod"; break; case BinaryOp::EXP: - _os << "exp"; + m_output << "exp"; break; case BinaryOp::SLT: - _os << "slt"; + m_output << "slt"; break; case BinaryOp::SGT: - _os << "sgt"; + m_output << "sgt"; break; case BinaryOp::BYTE: - _os << "byte"; + m_output << "byte"; break; case BinaryOp::SI: - _os << "signextend"; + m_output << "signextend"; break; case BinaryOp::KECCAK: - _os << "keccak256"; + m_output << "keccak256"; break; } - return _os << "(" << _x.left() << "," << _x.right() << ")"; + m_output << "("; + visit(_x.left()); + m_output << ","; + visit(_x.right()); + m_output << ")"; } -// New var numbering starts from x_10 until x_16 -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarDecl const& _x) +// New var numbering starts from x_10 +void ProtoConverter::visit(VarDecl const& _x) { - return _os << "let x_" << ((_x.id() % 7) + 10) << " := " << _x.expr() << "\n"; + m_output << "let x_" << m_numLiveVars << " := "; + visit(_x.expr()); + m_numVarsPerScope.top()++; + m_numLiveVars++; + m_output << "\n"; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, TypedVarDecl const& _x) +void ProtoConverter::visit(TypedVarDecl const& _x) { - _os << "let x_" << ((_x.id() % 7) + 10); + m_output << "let x_" << m_numLiveVars; switch (_x.type()) { case TypedVarDecl::BOOL: - _os << ": bool := " << _x.expr() << " : bool\n"; + m_output << ": bool := "; + visit(_x.expr()); + m_output << " : bool\n"; break; case TypedVarDecl::S8: - _os << ": s8 := " << _x.expr() << " : s8\n"; + m_output << ": s8 := "; + visit(_x.expr()); + m_output << " : s8\n"; break; case TypedVarDecl::S32: - _os << ": s32 := " << _x.expr() << " : s32\n"; + m_output << ": s32 := "; + visit(_x.expr()); + m_output << " : s32\n"; break; case TypedVarDecl::S64: - _os << ": s64 := " << _x.expr() << " : s64\n"; + m_output << ": s64 := "; + visit(_x.expr()); + m_output << " : s64\n"; break; case TypedVarDecl::S128: - _os << ": s128 := " << _x.expr() << " : s128\n"; + m_output << ": s128 := "; + visit(_x.expr()); + m_output << " : s128\n"; break; case TypedVarDecl::S256: - _os << ": s256 := " << _x.expr() << " : s256\n"; + m_output << ": s256 := "; + visit(_x.expr()); + m_output << " : s256\n"; break; case TypedVarDecl::U8: - _os << ": u8 := " << _x.expr() << " : u8\n"; + m_output << ": u8 := "; + visit(_x.expr()); + m_output << " : u8\n"; break; case TypedVarDecl::U32: - _os << ": u32 := " << _x.expr() << " : u32\n"; + m_output << ": u32 := "; + visit(_x.expr()); + m_output << " : u32\n"; break; case TypedVarDecl::U64: - _os << ": u64 := " << _x.expr() << " : u64\n"; + m_output << ": u64 := "; + visit(_x.expr()); + m_output << " : u64\n"; break; case TypedVarDecl::U128: - _os << ": u128 := " << _x.expr() << " : u128\n"; + m_output << ": u128 := "; + visit(_x.expr()); + m_output << " : u128\n"; break; case TypedVarDecl::U256: - _os << ": u256 := " << _x.expr() << " : u256\n"; + m_output << ": u256 := "; + visit(_x.expr()); + m_output << " : u256\n"; break; } - return _os; + m_numVarsPerScope.top()++; + m_numLiveVars++; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, UnaryOp const& _x) +void ProtoConverter::visit(UnaryOp const& _x) { switch (_x.op()) { case UnaryOp::NOT: - _os << "not"; + m_output << "not"; break; case UnaryOp::MLOAD: - _os << "mload"; + m_output << "mload"; break; case UnaryOp::SLOAD: - _os << "sload"; + m_output << "sload"; break; case UnaryOp::ISZERO: - _os << "iszero"; + m_output << "iszero"; break; } - return _os << "(" << _x.operand() << ")"; + m_output << "("; + visit(_x.operand()); + m_output << ")"; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, AssignmentStatement const& _x) +void ProtoConverter::visit(TernaryOp const& _x) { - return _os << _x.ref_id() << " := " << _x.expr() << "\n"; + switch (_x.op()) + { + case TernaryOp::ADDM: + m_output << "addmod"; + break; + case TernaryOp::MULM: + m_output << "mulmod"; + break; + } + m_output << "("; + visit(_x.arg1()); + m_output << ", "; + visit(_x.arg2()); + m_output << ", "; + visit(_x.arg3()); + m_output << ")"; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, IfStmt const& _x) +void ProtoConverter::visit(NullaryOp const& _x) { - return _os << - "if " << - _x.cond() << - " " << - _x.if_body(); + switch (_x.op()) + { + case NullaryOp::PC: + m_output << "pc()"; + break; + case NullaryOp::MSIZE: + m_output << "msize()"; + break; + case NullaryOp::GAS: + m_output << "gas()"; + break; + } } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, StoreFunc const& _x) +void ProtoConverter::visit(LogFunc const& _x) +{ + switch (_x.num_topics()) + { + case LogFunc::ZERO: + m_output << "log0"; + m_output << "("; + visit(_x.pos()); + m_output << ", "; + visit(_x.size()); + m_output << ")\n"; + break; + case LogFunc::ONE: + m_output << "log1"; + m_output << "("; + visit(_x.pos()); + m_output << ", "; + visit(_x.size()); + m_output << ", "; + visit(_x.t1()); + m_output << ")\n"; + break; + case LogFunc::TWO: + m_output << "log2"; + m_output << "("; + visit(_x.pos()); + m_output << ", "; + visit(_x.size()); + m_output << ", "; + visit(_x.t1()); + m_output << ", "; + visit(_x.t2()); + m_output << ")\n"; + break; + case LogFunc::THREE: + m_output << "log3"; + m_output << "("; + visit(_x.pos()); + m_output << ", "; + visit(_x.size()); + m_output << ", "; + visit(_x.t1()); + m_output << ", "; + visit(_x.t2()); + m_output << ", "; + visit(_x.t3()); + m_output << ")\n"; + break; + case LogFunc::FOUR: + m_output << "log4"; + m_output << "("; + visit(_x.pos()); + m_output << ", "; + visit(_x.size()); + m_output << ", "; + visit(_x.t1()); + m_output << ", "; + visit(_x.t2()); + m_output << ", "; + visit(_x.t3()); + m_output << ", "; + visit(_x.t4()); + m_output << ")\n"; + break; + } +} + +void ProtoConverter::visit(AssignmentStatement const& _x) +{ + visit(_x.ref_id()); + m_output << " := "; + visit(_x.expr()); + m_output << "\n"; +} + +void ProtoConverter::visit(IfStmt const& _x) +{ + m_output << "if "; + visit(_x.cond()); + m_output << " "; + visit(_x.if_body()); +} + +void ProtoConverter::visit(StoreFunc const& _x) { switch (_x.st()) { case StoreFunc::MSTORE: - _os << "mstore(" << _x.loc() << ", " << _x.val() << ")\n"; + m_output << "mstore("; break; case StoreFunc::SSTORE: - _os << "sstore(" << _x.loc() << ", " << _x.val() << ")\n"; + m_output << "sstore("; + break; + case StoreFunc::MSTORE8: + m_output << "mstore8("; break; } - return _os; + visit(_x.loc()); + m_output << ", "; + visit(_x.val()); + m_output << ")\n"; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, ForStmt const& _x) +void ProtoConverter::visit(ForStmt const& _x) { - _os << "for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } "; - return _os << _x.for_body(); + std::string loopVarName("i_" + std::to_string(m_numNestedForLoops++)); + m_output << "for { let " << loopVarName << " := 0 } " + << "lt(" << loopVarName << ", 0x60) " + << "{ " << loopVarName << " := add(" << loopVarName << ", 0x20) } "; + m_inForScope.push(true); + visit(_x.for_body()); + m_inForScope.pop(); + --m_numNestedForLoops; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, CaseStmt const& _x) +void ProtoConverter::visit(CaseStmt const& _x) { - _os << "case " << _x.case_lit() << " "; - return _os << _x.case_block(); + // Silently ignore duplicate case literals + if (isCaseLiteralUnique(_x.case_lit())) + { + m_output << "case "; + visit(_x.case_lit()); + m_output << " "; + visit(_x.case_block()); + } } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, SwitchStmt const& _x) +void ProtoConverter::visit(SwitchStmt const& _x) { if (_x.case_stmt_size() > 0 || _x.has_default_block()) { - _os << "switch " << _x.switch_expr() << "\n"; + std::set s; + m_switchLiteralSetPerScope.push(s); + m_output << "switch "; + visit(_x.switch_expr()); + m_output << "\n"; + for (auto const& caseStmt: _x.case_stmt()) - _os << caseStmt; + visit(caseStmt); + + m_switchLiteralSetPerScope.pop(); + if (_x.has_default_block()) - _os << "default " << _x.default_block(); + { + m_output << "default "; + visit(_x.default_block()); + } } - return _os; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Statement const& _x) +void ProtoConverter::visit(Statement const& _x) { switch (_x.stmt_oneof_case()) { case Statement::kDecl: - _os << _x.decl(); + visit(_x.decl()); break; case Statement::kAssignment: - _os << _x.assignment(); + visit(_x.assignment()); break; case Statement::kIfstmt: - _os << _x.ifstmt(); + visit(_x.ifstmt()); break; case Statement::kStorageFunc: - _os << _x.storage_func(); + visit(_x.storage_func()); break; case Statement::kBlockstmt: - _os << _x.blockstmt(); + visit(_x.blockstmt()); break; case Statement::kForstmt: - _os << _x.forstmt(); + visit(_x.forstmt()); break; case Statement::kSwitchstmt: - _os << _x.switchstmt(); + visit(_x.switchstmt()); + break; + case Statement::kBreakstmt: + if (m_inForScope.top()) + m_output << "break\n"; + break; + case Statement::kContstmt: + if (m_inForScope.top()) + m_output << "continue\n"; + break; + case Statement::kLogFunc: + visit(_x.log_func()); break; case Statement::STMT_ONEOF_NOT_SET: break; } - return _os; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Block const& _x) +void ProtoConverter::visit(Block const& _x) { if (_x.statements_size() > 0) { - _os << "{\n"; + m_numVarsPerScope.push(0); + m_output << "{\n"; for (auto const& st: _x.statements()) - _os << st; - _os << "}\n"; + visit(st); + m_output << "}\n"; + m_numLiveVars -= m_numVarsPerScope.top(); + m_numVarsPerScope.pop(); } else - _os << "{}\n"; - return _os; + m_output << "{}\n"; } -ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Function const& _x) +void ProtoConverter::visit(Function const& _x) { - _os << "{\n" - << "let a,b := foo(calldataload(0),calldataload(32),calldataload(64),calldataload(96),calldataload(128)," - << "calldataload(160),calldataload(192),calldataload(224))\n" - << "sstore(0, a)\n" - << "sstore(32, b)\n" - << "function foo(x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7) -> x_8, x_9\n" - << _x.statements() - << "}\n"; - return _os; + m_output << "{\n" + << "let a,b := foo(calldataload(0),calldataload(32),calldataload(64),calldataload(96),calldataload(128)," + << "calldataload(160),calldataload(192),calldataload(224))\n" + << "sstore(0, a)\n" + << "sstore(32, b)\n" + << "function foo(x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7) -> x_8, x_9\n"; + visit(_x.statements()); + m_output << "}\n"; } -string yul::test::yul_fuzzer::functionToString(Function const& _input) +string ProtoConverter::functionToString(Function const& _input) { - ostringstream os; - os << _input; - return os.str(); + visit(_input); + return m_output.str(); } -string yul::test::yul_fuzzer::protoToYul(const uint8_t* _data, size_t _size) +string ProtoConverter::protoToYul(const uint8_t* _data, size_t _size) { Function message; if (!message.ParsePartialFromArray(_data, _size)) return "#error invalid proto\n"; return functionToString(message); -} \ No newline at end of file +} diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 1c06370ccaf5..1e84133558cb 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -18,8 +18,14 @@ #include #include #include +#include +#include +#include +#include #include +#include +#include namespace yul { @@ -27,30 +33,56 @@ namespace test { namespace yul_fuzzer { -class Function; - -std::string functionToString(Function const& input); -std::string protoToYul(uint8_t const* data, size_t size); -std::string createHex(std::string const& _hexBytes); -std::string createAlphaNum(std::string const& _strBytes); -std::ostream& operator<<(std::ostream& _os, BinaryOp const& _x); -std::ostream& operator<<(std::ostream& _os, Block const& _x); -std::ostream& operator<<(std::ostream& _os, Literal const& _x); -std::ostream& operator<<(std::ostream& _os, VarRef const& _x); -std::ostream& operator<<(std::ostream& _os, Expression const& _x); -std::ostream& operator<<(std::ostream& _os, BinaryOp const& _x); -std::ostream& operator<<(std::ostream& _os, VarDecl const& _x); -std::ostream& operator<<(std::ostream& _os, TypedVarDecl const& _x); -std::ostream& operator<<(std::ostream& _os, UnaryOp const& _x); -std::ostream& operator<<(std::ostream& _os, AssignmentStatement const& _x); -std::ostream& operator<<(std::ostream& _os, IfStmt const& _x); -std::ostream& operator<<(std::ostream& _os, StoreFunc const& _x); -std::ostream& operator<<(std::ostream& _os, Statement const& _x); -std::ostream& operator<<(std::ostream& _os, Block const& _x); -std::ostream& operator<<(std::ostream& _os, Function const& _x); -std::ostream& operator<<(std::ostream& _os, ForStmt const& _x); -std::ostream& operator<<(std::ostream& _os, CaseStmt const& _x); -std::ostream& operator<<(std::ostream& _os, SwitchStmt const& _x); +class ProtoConverter +{ +public: + ProtoConverter() + { + // The hard-coded function template foo has 10 parameters that are already "live" + m_numLiveVars = 10; + m_numVarsPerScope.push(m_numLiveVars); + m_numNestedForLoops = 0; + m_inForScope.push(false); + } + ProtoConverter(ProtoConverter const&) = delete; + ProtoConverter(ProtoConverter&&) = delete; + std::string functionToString(Function const& _input); + std::string protoToYul(uint8_t const* _data, size_t _size); + +private: + void visit(BinaryOp const&); + void visit(Block const&); + void visit(Literal const&); + void visit(VarRef const&); + void visit(Expression const&); + void visit(VarDecl const&); + void visit(TypedVarDecl const&); + void visit(UnaryOp const&); + void visit(AssignmentStatement const&); + void visit(IfStmt const&); + void visit(StoreFunc const&); + void visit(Statement const&); + void visit(Function const&); + void visit(ForStmt const&); + void visit(CaseStmt const&); + void visit(SwitchStmt const&); + void visit(TernaryOp const&); + void visit(NullaryOp const&); + void visit(LogFunc const&); + template + void visit(google::protobuf::RepeatedPtrField const& _repeated_field); + + std::string createHex(std::string const& _hexBytes) const; + std::string createAlphaNum(std::string const& _strBytes) const; + bool isCaseLiteralUnique(Literal const&); + + std::ostringstream m_output; + std::stack m_numVarsPerScope; + int32_t m_numLiveVars; + int32_t m_numNestedForLoops; + std::stack m_inForScope; + std::stack> m_switchLiteralSetPerScope; +}; } } } diff --git a/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp index b3b11426a1f4..fe9f9609ef8a 100644 --- a/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp @@ -28,7 +28,11 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) return 0; string input(reinterpret_cast(_data), _size); - AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + langutil::EVMVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::full() + ); if (!stack.parseAndAnalyze("source", input)) return 0; diff --git a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp index 6aadbfa4fbbb..5f62e4c97e07 100644 --- a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp @@ -55,11 +55,18 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) })) return 0; - AssemblyStack stack(EVMVersion::petersburg(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + langutil::EVMVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::full() + ); try { - if (!stack.parseAndAnalyze("source", input) || !stack.parserResult()->code || - !stack.parserResult()->analysisInfo) + if ( + !stack.parseAndAnalyze("source", input) || + !stack.parserResult()->code || + !stack.parserResult()->analysisInfo + ) return 0; } catch (Exception const&) @@ -69,9 +76,33 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) ostringstream os1; ostringstream os2; - yulFuzzerUtil::interpret(os1, stack.parserResult()->code); + try + { + yulFuzzerUtil::interpret( + os1, + stack.parserResult()->code + ); + } + catch (yul::test::StepLimitReached const&) + { + return 0; + } + catch (yul::test::InterpreterTerminatedGeneric const&) + { + } + stack.optimize(); - yulFuzzerUtil::interpret(os2, stack.parserResult()->code); + try + { + yulFuzzerUtil::interpret( + os2, + stack.parserResult()->code, + (yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 1.5) + ); + } + catch (yul::test::InterpreterTerminatedGeneric const&) + { + } bool isTraceEq = (os1.str() == os2.str()); yulAssert(isTraceEq, "Interpreted traces for optimized and unoptimized code differ."); diff --git a/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp index 05e70e8ab5f1..28e1984b24a2 100644 --- a/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp @@ -27,7 +27,11 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) return 0; string input(reinterpret_cast(_data), _size); - AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + langutil::EVMVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::full() + ); if (!stack.parseAndAnalyze("source", input)) return 0; diff --git a/test/tools/ossfuzz/yulFuzzerCommon.cpp b/test/tools/ossfuzz/yulFuzzerCommon.cpp index b5a8eba80161..1a69d99baf27 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.cpp +++ b/test/tools/ossfuzz/yulFuzzerCommon.cpp @@ -20,20 +20,21 @@ using namespace std; using namespace yul; using namespace yul::test::yul_fuzzer; -void yulFuzzerUtil::interpret(ostream& _os, shared_ptr _ast) +void yulFuzzerUtil::interpret( + ostream& _os, + shared_ptr _ast, + size_t _maxSteps, + size_t _maxTraceSize, + size_t _maxMemory +) { InterpreterState state; - state.maxTraceSize = 10000; + state.maxTraceSize = _maxTraceSize; + state.maxSteps = _maxSteps; + state.maxMemSize = _maxMemory; Interpreter interpreter(state); - try - { - interpreter(*_ast); - } - catch (InterpreterTerminated const&) - { - } - + interpreter(*_ast); _os << "Trace:" << endl; for (auto const& line: interpreter.trace()) _os << " " << line << endl; -} \ No newline at end of file +} diff --git a/test/tools/ossfuzz/yulFuzzerCommon.h b/test/tools/ossfuzz/yulFuzzerCommon.h index 26b472b9c806..b7db9494cc21 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.h +++ b/test/tools/ossfuzz/yulFuzzerCommon.h @@ -24,7 +24,16 @@ namespace yul_fuzzer { struct yulFuzzerUtil { - static void interpret(std::ostream& _os, std::shared_ptr _ast); + static void interpret( + std::ostream& _os, + std::shared_ptr _ast, + size_t _maxSteps = maxSteps, + size_t _maxTraceSize = maxTraceSize, + size_t _maxMemory = maxMemory + ); + static size_t constexpr maxSteps = 100; + static size_t constexpr maxTraceSize = 75; + static size_t constexpr maxMemory = 0x200; }; } } diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index bd8468d9be7a..ff020b42db18 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -18,8 +18,7 @@ syntax = "proto2"; message VarDecl { - required int32 id = 1; - required Expression expr = 2; + required Expression expr = 1; } message TypedVarDecl { @@ -112,22 +111,62 @@ message UnaryOp { required Expression operand = 2; } +message TernaryOp { + enum TOp { + ADDM = 0; + MULM = 1; + } + required TOp op = 1; + required Expression arg1 = 2; + required Expression arg2 = 3; + required Expression arg3 = 4; +} + +message NullaryOp { + enum NOp { + PC = 1; + MSIZE = 2; + GAS = 3; + } + required NOp op = 1; +} + message StoreFunc { enum Storage { MSTORE = 0; SSTORE = 1; + MSTORE8 = 2; } required Expression loc = 1; required Expression val = 2; required Storage st = 3; } +message LogFunc { + enum NumTopics { + ZERO = 0; + ONE = 1; + TWO = 2; + THREE = 3; + FOUR = 4; + } + required Expression pos = 1; + required Expression size = 2; + required NumTopics num_topics = 3; + required Expression t1 = 4; + required Expression t2 = 5; + required Expression t3 = 6; + required Expression t4 = 7; +} + message Expression { oneof expr_oneof { VarRef varref = 1; Literal cons = 2; BinaryOp binop = 3; UnaryOp unop = 4; + TernaryOp top = 5; + NullaryOp nop = 6; } } @@ -142,7 +181,7 @@ message IfStmt { } message ForStmt { - required Block for_body = 2; + required Block for_body = 1; } message CaseStmt { @@ -156,6 +195,9 @@ message SwitchStmt { optional Block default_block = 3; } +message BreakStmt {} +message ContinueStmt {} + message Statement { oneof stmt_oneof { VarDecl decl = 1; @@ -165,6 +207,9 @@ message Statement { Block blockstmt = 5; ForStmt forstmt = 6; SwitchStmt switchstmt = 7; + BreakStmt breakstmt = 8; + ContinueStmt contstmt = 9; + LogFunc log_func = 10; } } diff --git a/test/tools/ossfuzz/yulProtoFuzzer.cpp b/test/tools/ossfuzz/yulProtoFuzzer.cpp index c4416a61eacd..9f81ea01b21f 100644 --- a/test/tools/ossfuzz/yulProtoFuzzer.cpp +++ b/test/tools/ossfuzz/yulProtoFuzzer.cpp @@ -30,9 +30,10 @@ using namespace yul; using namespace yul::test::yul_fuzzer; using namespace std; -DEFINE_BINARY_PROTO_FUZZER(Function const& _input) +DEFINE_PROTO_FUZZER(Function const& _input) { - string yul_source = functionToString(_input); + ProtoConverter converter; + string yul_source = converter.functionToString(_input); if (yul_source.size() > 600) return; @@ -45,7 +46,11 @@ DEFINE_BINARY_PROTO_FUZZER(Function const& _input) } // AssemblyStack entry point - AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + langutil::EVMVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::full() + ); // Parse protobuf mutated YUL code if (!stack.parseAndAnalyze("source", yul_source)) @@ -58,4 +63,4 @@ DEFINE_BINARY_PROTO_FUZZER(Function const& _input) // Optimize stack.optimize(); -} \ No newline at end of file +} diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index 72e320ef57ae..793effba3b31 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -36,9 +36,10 @@ using namespace langutil; using namespace dev; using namespace yul::test; -DEFINE_BINARY_PROTO_FUZZER(Function const& _input) +DEFINE_PROTO_FUZZER(Function const& _input) { - string yul_source = functionToString(_input); + ProtoConverter converter; + string yul_source = converter.functionToString(_input); if (yul_source.size() > 600) return; @@ -51,7 +52,11 @@ DEFINE_BINARY_PROTO_FUZZER(Function const& _input) } // AssemblyStack entry point - AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + langutil::EVMVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::full() + ); try { @@ -67,11 +72,34 @@ DEFINE_BINARY_PROTO_FUZZER(Function const& _input) ostringstream os1; ostringstream os2; - yulFuzzerUtil::interpret(os1, stack.parserResult()->code); + try + { + yulFuzzerUtil::interpret( + os1, + stack.parserResult()->code + ); + } + catch (yul::test::StepLimitReached const&) + { + return; + } + catch (yul::test::InterpreterTerminatedGeneric const&) + { + } + stack.optimize(); - yulFuzzerUtil::interpret(os2, stack.parserResult()->code); + try + { + yulFuzzerUtil::interpret(os2, + stack.parserResult()->code, + (yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 1.5) + ); + } + catch (yul::test::InterpreterTerminatedGeneric const&) + { + } bool isTraceEq = (os1.str() == os2.str()); yulAssert(isTraceEq, "Interpreted traces for optimized and unoptimized code differ."); return; -} \ No newline at end of file +} diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 05bc3684b6ab..0d407eff62dc 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -92,20 +92,21 @@ void copyZeroExtended( using u512 = boost::multiprecision::number>; u256 EVMInstructionInterpreter::eval( - solidity::Instruction _instruction, + dev::eth::Instruction _instruction, vector const& _arguments ) { - using dev::solidity::Instruction; + using namespace dev::eth; + using dev::eth::Instruction; - auto info = solidity::instructionInfo(_instruction); + auto info = instructionInfo(_instruction); yulAssert(size_t(info.args) == _arguments.size(), ""); auto const& arg = _arguments; switch (_instruction) { case Instruction::STOP: - throw InterpreterTerminated(); + throw ExplicitlyTerminated(); // --------------- arithmetic --------------- case Instruction::ADD: return arg[0] + arg[1]; @@ -341,18 +342,18 @@ u256 EVMInstructionInterpreter::eval( if (logMemoryRead(arg[0], arg[1])) data = bytesConstRef(m_state.memory.data() + size_t(arg[0]), size_t(arg[1])).toBytes(); logTrace(_instruction, arg, data); - throw InterpreterTerminated(); + throw ExplicitlyTerminated(); } case Instruction::REVERT: logMemoryRead(arg[0], arg[1]); logTrace(_instruction, arg); - throw InterpreterTerminated(); + throw ExplicitlyTerminated(); case Instruction::INVALID: logTrace(_instruction); - throw InterpreterTerminated(); + throw ExplicitlyTerminated(); case Instruction::SELFDESTRUCT: logTrace(_instruction, arg); - throw InterpreterTerminated(); + throw ExplicitlyTerminated(); case Instruction::POP: break; // --------------- invalid in strict assembly --------------- @@ -455,17 +456,13 @@ bool EVMInstructionInterpreter::logMemoryWrite(u256 const& _offset, u256 const& bool EVMInstructionInterpreter::logMemory(bool _write, u256 const& _offset, u256 const& _size, bytes const& _data) { - /// Memory size limit. Anything beyond this will still work, but it has - /// deterministic yet not necessarily consistent behaviour. - size_t constexpr maxMemSize = 0x20000000; - logTrace(_write ? "MSTORE_AT_SIZE" : "MLOAD_FROM_SIZE", {_offset, _size}, _data); if (((_offset + _size) >= _offset) && ((_offset + _size + 0x1f) >= (_offset + _size))) { u256 newSize = (_offset + _size + 0x1f) & ~u256(0x1f); m_state.msize = max(m_state.msize, newSize); - if (newSize < maxMemSize) + if (newSize < m_state.maxMemSize) { if (m_state.memory.size() < newSize) m_state.memory.resize(size_t(newSize)); @@ -478,9 +475,9 @@ bool EVMInstructionInterpreter::logMemory(bool _write, u256 const& _offset, u256 return false; } -void EVMInstructionInterpreter::logTrace(solidity::Instruction _instruction, std::vector const& _arguments, bytes const& _data) +void EVMInstructionInterpreter::logTrace(dev::eth::Instruction _instruction, std::vector const& _arguments, bytes const& _data) { - logTrace(solidity::instructionInfo(_instruction).name, _arguments, _data); + logTrace(dev::eth::instructionInfo(_instruction).name, _arguments, _data); } void EVMInstructionInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments, bytes const& _data) @@ -495,6 +492,6 @@ void EVMInstructionInterpreter::logTrace(std::string const& _pseudoInstruction, if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize) { m_state.trace.emplace_back("Trace size limit reached."); - throw InterpreterTerminated(); + throw TraceLimitReached(); } } diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.h b/test/tools/yulInterpreter/EVMInstructionInterpreter.h index 1ce6dbd9a67e..bd943b6342a0 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.h +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.h @@ -28,7 +28,7 @@ namespace dev { -namespace solidity +namespace eth { enum class Instruction: uint8_t; } @@ -66,7 +66,7 @@ class EVMInstructionInterpreter explicit EVMInstructionInterpreter(InterpreterState& _state): m_state(_state) {} - dev::u256 eval(dev::solidity::Instruction _instruction, std::vector const& _arguments); + dev::u256 eval(dev::eth::Instruction _instruction, std::vector const& _arguments); private: /// Record a memory read in the trace. Also updates m_state.msize @@ -78,7 +78,7 @@ class EVMInstructionInterpreter bool logMemory(bool _write, dev::u256 const& _offset, dev::u256 const& _size = 32, dev::bytes const& _data = {}); - void logTrace(dev::solidity::Instruction _instruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); + void logTrace(dev::eth::Instruction _instruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); /// Appends a log to the trace representing an instruction or similar operation by string, /// with arguments and auxiliary data (if nonempty). void logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index e9b4dbd94450..f3ce44e4d0b7 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -128,6 +128,12 @@ void Interpreter::operator()(Continue const&) void Interpreter::operator()(Block const& _block) { + m_state.numSteps++; + if (m_state.maxSteps > 0 && m_state.numSteps >= m_state.maxSteps) + { + m_state.trace.emplace_back("Interpreter execution step limit reached."); + throw StepLimitReached(); + } openScope(); // Register functions. for (auto const& statement: _block.statements) diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 06cfe939dd34..7d6eb08a3468 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -35,7 +35,19 @@ namespace yul namespace test { -class InterpreterTerminated: dev::Exception +class InterpreterTerminatedGeneric: public dev::Exception +{ +}; + +class ExplicitlyTerminated: public InterpreterTerminatedGeneric +{ +}; + +class StepLimitReached: public InterpreterTerminatedGeneric +{ +}; + +class TraceLimitReached: public InterpreterTerminatedGeneric { }; @@ -72,6 +84,11 @@ struct InterpreterState std::vector trace; /// This is actually an input parameter that more or less limits the runtime. size_t maxTraceSize = 0; + /// Memory size limit. Anything beyond this will still work, but it has + /// deterministic yet not necessarily consistent behaviour. + size_t maxMemSize = 0x200; + size_t maxSteps = 0; + size_t numSteps = 0; LoopState loopState = LoopState::Default; }; diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 14ac0e950a31..776759ebe794 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -79,10 +80,7 @@ class YulOpti SourceReferenceFormatter formatter(cout); for (auto const& error: m_errors) - formatter.printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + formatter.printErrorInformation(*error); } bool parse(string const& _input) @@ -132,7 +130,7 @@ class YulOpti cout << " (e)xpr inline/(i)nline/(s)implify/varname c(l)eaner/(u)nusedprune/ss(a) transform/" << endl; cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-pre-rewriter/" << endl; cout << " s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/? " << endl; - cout << " stack com(p)ressor? " << endl; + cout << " stack com(p)ressor/(D)ead code eliminator/? " << endl; cout.flush(); int option = readStandardInputChar(); cout << ' ' << char(option) << endl; @@ -182,6 +180,9 @@ class YulOpti case 'u': UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); break; + case 'D': + DeadCodeEliminator{}(*m_ast); + break; case 'a': SSATransform::run(*m_ast, *m_nameDispenser); break; diff --git a/test/tools/yulrun.cpp b/test/tools/yulrun.cpp index 2eae312a58bd..c1461d61e063 100644 --- a/test/tools/yulrun.cpp +++ b/test/tools/yulrun.cpp @@ -55,15 +55,16 @@ namespace void printErrors(ErrorList const& _errors) { for (auto const& error: _errors) - SourceReferenceFormatter(cout).printExceptionInformation( - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error" - ); + SourceReferenceFormatter(cout).printErrorInformation(*error); } pair, shared_ptr> parse(string const& _source) { - AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + AssemblyStack stack( + langutil::EVMVersion(), + AssemblyStack::Language::StrictAssembly, + solidity::OptimiserSettings::none() + ); if (stack.parseAndAnalyze("--INPUT--", _source)) { yulAssert(stack.errors().empty(), "Parsed successfully but had errors."); @@ -86,12 +87,13 @@ void interpret(string const& _source) InterpreterState state; state.maxTraceSize = 10000; + state.maxMemSize = 0x20000000; Interpreter interpreter(state); try { interpreter(*ast); } - catch (InterpreterTerminated const&) + catch (InterpreterTerminatedGeneric const&) { }