diff --git a/.circleci/config.yml b/.circleci/config.yml index 58a4c2742..1b50f7583 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -214,6 +214,16 @@ defaults: command: ./test/lsp.py ./build/solc/solc - gitter_notify_failure_unless_pr + - steps_build: &steps_build + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - store_artifacts: *artifact_solidity_upgrade + - store_artifacts: *artifact_yul_phaser + - persist_to_workspace: *artifacts_executables + - gitter_notify_failure_unless_pr + - steps_soltest_all: &steps_soltest_all steps: - checkout @@ -234,6 +244,14 @@ defaults: - store_artifacts: *artifacts_test_results - gitter_notify_failure_unless_pr + - steps_install_dependencies_osx: &steps_install_dependencies_osx + steps: + - restore_cache: + keys: + - dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }} + - attach_workspace: + at: . + # -------------------------------------------------------------------------- # Base Image Templates @@ -565,7 +583,7 @@ defaults: project: uniswap binary_type: native nodejs_version: '16' - - job_native_test_prb_math: &job_native_test_prb_math + - job_native_test_ext_prb_math: &job_native_test_ext_prb_math <<: *workflow_ubuntu2004_static name: t_native_test_ext_prb_math project: prb-math @@ -586,6 +604,15 @@ defaults: nodejs_version: '14' resource_class: medium + - job_b_ubu_asan_clang: &job_b_ubu_asan_clang + <<: *workflow_trigger_on_tags + name: b_ubu_asan_clang + cmake_options: -DSANITIZE=address + - job_b_ubu_ubsan_clang: &job_b_ubu_ubsan_clang + <<: *workflow_trigger_on_tags + name: b_ubu_ubsan_clang + cmake_options: -DSANITIZE=address + # ----------------------------------------------------------------------------------------------- jobs: @@ -731,14 +758,7 @@ jobs: # this runs 2x faster on xlarge but takes 4x more resources (compared to medium). # Enough other jobs depend on it that it's worth it though. <<: *base_ubuntu2004_xlarge - steps: - - checkout - - run: *run_build - - store_artifacts: *artifacts_solc - - store_artifacts: *artifact_solidity_upgrade - - store_artifacts: *artifact_yul_phaser - - persist_to_workspace: *artifacts_executables - - gitter_notify_failure_unless_pr + <<: *steps_build # x64 ASAN build, for testing for memory related bugs b_ubu_asan: &b_ubu_asan @@ -748,12 +768,7 @@ jobs: CMAKE_OPTIONS: -DSANITIZE=address MAKEFLAGS: -j 3 CMAKE_BUILD_TYPE: Release - steps: - - checkout - - run: *run_build - - store_artifacts: *artifacts_solc - - persist_to_workspace: *artifacts_executables - - gitter_notify_failure_unless_pr + <<: *steps_build b_ubu_clang: &b_ubu_clang <<: *base_ubuntu2004_clang_large @@ -762,42 +777,21 @@ jobs: CC: clang CXX: clang++ MAKEFLAGS: -j 10 - steps: - - checkout - - run: *run_build - - store_artifacts: *artifacts_solc - - persist_to_workspace: *artifacts_executables - - gitter_notify_failure_unless_pr + <<: *steps_build - b_ubu_asan_clang: &b_ubu_asan_clang + b_ubu_san_clang: # This runs a bit faster on large and xlarge but on nightly efficiency matters more. + parameters: + cmake_options: + type: string <<: *base_ubuntu2004_clang environment: + TERM: xterm CC: clang CXX: clang++ - CMAKE_OPTIONS: -DSANITIZE=address MAKEFLAGS: -j 3 - steps: - - checkout - - run: *run_build - - store_artifacts: *artifacts_solc - - persist_to_workspace: *artifacts_executables - - gitter_notify_failure_unless_pr - - b_ubu_ubsan_clang: &b_ubu_ubsan_clang - # This runs a bit faster on large and xlarge but on nightly efficiency matters more. - <<: *base_ubuntu2004_clang - environment: - CC: clang - CXX: clang++ - CMAKE_OPTIONS: -DSANITIZE=undefined - MAKEFLAGS: -j 3 - steps: - - checkout - - run: *run_build - - store_artifacts: *artifacts_solc - - persist_to_workspace: *artifacts_executables - - gitter_notify_failure_unless_pr + CMAKE_OPTIONS: << parameters.cmake_options >> + <<: *steps_build b_ubu_release: &b_ubu_release <<: *b_ubu @@ -949,7 +943,7 @@ jobs: - build/test/tools/solfuzzer - gitter_notify_failure_unless_pr - t_osx_soltest: + t_osx_soltest: &t_osx_soltest <<: *base_osx environment: EVM: << pipeline.parameters.evm-version >> @@ -957,11 +951,9 @@ jobs: TERM: xterm steps: - checkout - - restore_cache: - keys: - - dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }} - - attach_workspace: - at: . + - when: + condition: true + <<: *steps_install_dependencies_osx - run: *run_soltest - store_test_results: *store_test_results - store_artifacts: *artifacts_test_results @@ -971,11 +963,9 @@ jobs: <<: *base_osx steps: - checkout - - restore_cache: - keys: - - dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }} - - attach_workspace: - at: . + - when: + condition: true + <<: *steps_install_dependencies_osx - run: *run_cmdline_tests - store_artifacts: *artifacts_test_results - gitter_notify_failure_unless_pr @@ -992,10 +982,10 @@ jobs: command: | scripts/ci/build_emscripten.sh - store_artifacts: - path: emscripten_build/libsolc/soljson.js + path: upload/soljson.js destination: soljson.js - run: mkdir -p workspace - - run: cp emscripten_build/libsolc/soljson.js workspace/soljson.js + - run: cp upload/soljson.js workspace/soljson.js - run: scripts/get_version.sh > workspace/version.txt - persist_to_workspace: root: workspace @@ -1108,6 +1098,7 @@ jobs: parallelism: 20 environment: EVM: << pipeline.parameters.evm-version >> + SOLTEST_FLAGS: --no-smt <<: *steps_soltest t_ubu_ubsan_clang_cli: @@ -1215,8 +1206,34 @@ jobs: name: External <> tests (native) command: | test/externalTests/<>.sh native /tmp/workspace/solc/solc + - store_artifacts: + path: reports/externalTests/ + # persist_to_workspace fails if the directory does not exist and the test script will create + # it only if it actually has benchmark results. + - run: mkdir -p reports/externalTests/ + - persist_to_workspace: + root: . + paths: + - reports/externalTests/ - gitter_notify_failure_unless_pr + c_ext_benchmarks: + <<: *base_node_small + steps: + - checkout + - attach_workspace: + at: . + - run: + name: Combine benchmark reports + command: cat reports/externalTests/benchmark-*.json | scripts/externalTests/merge_benchmarks.sh > reports/externalTests/all-benchmarks.json + - run: + name: Summarize reports + command: cat reports/externalTests/all-benchmarks.json | scripts/externalTests/summarize_benchmarks.sh > reports/externalTests/summarized-benchmarks.json + - store_artifacts: + path: reports/externalTests/all-benchmarks.json + - store_artifacts: + path: reports/externalTests/summarized-benchmarks.json + b_win: &b_win <<: *base_win_powershell_large steps: @@ -1464,9 +1481,27 @@ workflows: - t_ems_ext: *job_native_test_ext_pool_together - t_ems_ext: *job_native_test_ext_perpetual_pools - t_ems_ext: *job_native_test_ext_uniswap - - t_ems_ext: *job_native_test_prb_math + - t_ems_ext: *job_native_test_ext_prb_math - t_ems_ext: *job_native_test_ext_elementfi + - c_ext_benchmarks: + <<: *workflow_trigger_on_tags + requires: + - t_ems_compile_ext_colony + - t_native_compile_ext_gnosis + - t_native_test_ext_gnosis_v2 + - t_native_test_ext_zeppelin + - t_native_test_ext_ens + - t_native_test_ext_trident + - t_native_test_ext_euler + - t_native_test_ext_yield_liquidator + - t_native_test_ext_bleeps + - t_native_test_ext_pool_together + - t_native_test_ext_perpetual_pools + - t_native_test_ext_uniswap + - t_native_test_ext_prb_math + - t_native_test_ext_elementfi + # Windows build and tests - b_win: *workflow_trigger_on_tags - b_win_release: *workflow_trigger_on_tags @@ -1519,13 +1554,13 @@ workflows: # ASan build and tests - b_ubu_asan: *workflow_trigger_on_tags - - b_ubu_asan_clang: *workflow_trigger_on_tags + - b_ubu_san_clang: *job_b_ubu_asan_clang - t_ubu_asan_soltest: *workflow_ubuntu2004_asan - t_ubu_asan_clang_soltest: *workflow_ubuntu2004_asan_clang - t_ubu_asan_cli: *workflow_ubuntu2004_asan # UBSan build and tests - - b_ubu_ubsan_clang: *workflow_trigger_on_tags + - b_ubu_san_clang: *job_b_ubu_ubsan_clang - t_ubu_ubsan_clang_soltest: *workflow_ubuntu2004_ubsan_clang - t_ubu_ubsan_clang_cli: *workflow_ubuntu2004_ubsan_clang diff --git a/CMakeLists.txt b/CMakeLists.txt index 99d765980..fc7320c72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.8.12") +set(PROJECT_VERSION "0.8.13") # OSX target needed in order to support std::visit set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) diff --git a/Changelog.md b/Changelog.md index d897c363f..ec8724502 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,30 +9,46 @@ Breaking changes: * Commandline Interface: Assembler mode no longer enables all outputs by default. -### 0.8.12 (unreleased) +### 0.8.13 (unreleased) Language Features: - * General: Support ``ContractName.functionName`` for ``abi.encodeCall``, in addition to external function pointers. - * General: Add equality-comparison operators for external function types. Compiler Features: + + +Bugfixes: + + + +### 0.8.12 (2022-02-16) + +Language Features: + * General: Add equality-comparison operators for external function types. + * General: Support ``ContractName.functionName`` for ``abi.encodeCall``, in addition to external function pointers. + + +Compiler Features: + * Commandline Interface: Event and error signatures are also returned when using ``--hashes``. * Yul Optimizer: Remove ``mstore`` and ``sstore`` operations if the slot already contains the same value. * Yul: Emit immutable references for pure yul code when requested. Bugfixes: * Antlr Grammar: Allow builtin names in ``yulPath`` to support ``.address`` in function pointers. - * Code Generator: Fix ICE when accessing the members of external functions occupying more than two stack slots. - * Code Generator: Fix ICE when doing an explicit conversion from ``string calldata`` to ``bytes``. + * Code Generator: Fix internal error when accessing the members of external functions occupying more than two stack slots. + * Code Generator: Fix internal error when doing an explicit conversion from ``string calldata`` to ``bytes``. * Control Flow Graph: Perform proper virtual lookup for modifiers for uninitialized variable and unreachable code analysis. + * General: ``string.concat`` now properly takes strings as arguments and returns ``string memory``. It was accidentally introduced as a copy of ``bytes.concat`` before. * Immutables: Fix wrong error when the constructor of a base contract uses ``return`` and the derived contract contains immutable variables. + * Inheritance: Consider functions in all ancestors during override analysis. * IR Generator: Add missing cleanup during the conversion of fixed bytes types to smaller fixed bytes types. * IR Generator: Add missing cleanup for indexed event arguments of value type. * IR Generator: Fix internal error when copying reference types in calldata and storage to struct or array members in memory. * IR Generator: Fix IR syntax error when copying storage arrays of structs containing functions. - * Natspec: Fix ICE when overriding a struct getter with a Natspec-documented return value and the name in the struct is different. - * TypeChecker: Fix ICE when a constant variable declaration forward references a struct. + * Natspec: Fix internal error when overriding a struct getter with a Natspec-documented return value and the name in the struct is different. + * Type Checker: Fix internal error when a constant variable declaration forward references a struct. + * Yul EVM Code Transform: Improved stack shuffling in corner cases. Solc-Js: @@ -40,6 +56,10 @@ Solc-Js: * The code has been ported to TypeScript. +Build System: + * Emscripten builds store the embedded WebAssembly binary in LZ4 compressed format and transparently decompress on loading. + + ### 0.8.11 (2021-12-20) Language Features: diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 25c32f889..ffa825446 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -142,8 +142,6 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s WASM=1") # Set webassembly build to synchronous loading. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s WASM_ASYNC_COMPILATION=0") - # Output a single js file with the wasm binary embedded as base64 string. - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s SINGLE_FILE=1") # Allow new functions to be added to the wasm module via addFunction. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_TABLE_GROWTH=1") # Disable warnings about not being pure asm.js due to memory growth. @@ -153,8 +151,11 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # The major alternative compiler to GCC/Clang is Microsoft's Visual C++ compiler, only available on Windows. elseif (DEFINED MSVC) + # Remove NDEBUG from RELWITHDEBINFO (to enable asserts) + # CMAKE_CXX_FLAGS_RELWITHDEBINFO for GCC/Clang does not include NDEBUG + string(REPLACE "/DNDEBUG" " " CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - add_compile_options(/MP) # enable parallel compilation + add_compile_options(/MP) # enable parallel compilation add_compile_options(/EHsc) # specify Exception Handling Model in msvc add_compile_options(/WX) # enable warnings-as-errors add_compile_options(/wd4068) # disable unknown pragma warning (4068) diff --git a/cmake/templates/license.h.in b/cmake/templates/license.h.in index 6cdd2bb8a..2d7f2691a 100644 --- a/cmake/templates/license.h.in +++ b/cmake/templates/license.h.in @@ -137,6 +137,36 @@ evmc: See the License for the specific language governing permissions and limitations under the License. +mini-lz4: + The file scripts/ci/mini-lz4.js is derived from the emscripten adaptation of + node-lz4 and licensed under the following terms: + + Copyright (c) 2012 Pierre Curto + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +base64: + The file scripts/ci/base64DecToArr.js is derived from a code example + in the MDN Web Docs, which permits use under CC0 terms: + + Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ + All other code is licensed under GPL version 3: diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 608ba3dfd..0aae3f1ac 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -1552,6 +1552,10 @@ "bugs": [], "released": "2021-12-20" }, + "0.8.12": { + "bugs": [], + "released": "2022-02-16" + }, "0.8.2": { "bugs": [ "SignedImmutables", diff --git a/docs/cheatsheet.rst b/docs/cheatsheet.rst index cd9ba5c14..a7581aae5 100644 --- a/docs/cheatsheet.rst +++ b/docs/cheatsheet.rst @@ -86,6 +86,8 @@ Global Variables to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)`` - ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of arguments to one byte array` +- ``string.concat(...) returns (string memory)``: :ref:`Concatenates variable number of + arguments to one string array` - ``block.basefee`` (``uint``): current block's base fee (`EIP-3198 `_ and `EIP-1559 `_) - ``block.chainid`` (``uint``): current chain id - ``block.coinbase`` (``address payable``): current block miner's address diff --git a/docs/contributing.rst b/docs/contributing.rst index 2895744ef..ba5a0a0f8 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -94,6 +94,13 @@ dependencies (`evmone `_, On macOS some of the testing scripts expect GNU coreutils to be installed. This can be easiest accomplished using Homebrew: ``brew install coreutils``. +On Windows systems make sure that you have a privilege to create symlinks, +otherwise several tests may fail. +Administrators should have that privilege, but you may also +`grant it to other users `_ +or +`enable Developer Mode `_. + Running the Tests ----------------- diff --git a/docs/examples/voting.rst b/docs/examples/voting.rst index 84191118d..0e8901c3d 100644 --- a/docs/examples/voting.rst +++ b/docs/examples/voting.rst @@ -130,9 +130,12 @@ of votes. // Since `sender` is a reference, this // modifies `voters[msg.sender].voted` + Voter storage delegate_ = voters[to]; + + // Voters cannot delegate to wallets that cannot vote. + require(delegate_.weight >= 1); sender.voted = true; sender.delegate = to; - Voter storage delegate_ = voters[to]; if (delegate_.voted) { // If the delegate already voted, // directly add to the number of votes diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index f2ed85237..9d6e666d2 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -24,7 +24,7 @@ actual release. They are not meant for production use. 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](https://semver.org/#spec-item-4). +We currently use a 0.x version number `to indicate this fast pace of change `_. Remix ===== diff --git a/docs/internals/layout_in_storage.rst b/docs/internals/layout_in_storage.rst index 9afe97ddb..4682d640c 100644 --- a/docs/internals/layout_in_storage.rst +++ b/docs/internals/layout_in_storage.rst @@ -56,7 +56,8 @@ as individual values. 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 of the language and due to its critical nature should be considered very carefully before - being executed. + being executed. In the event of such a breaking change, we would want to release a + compatibility mode in which the compiler would generate bytecode supporting the old layout. Mappings and Dynamic Arrays diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index aab0faf7c..fa2fa34c1 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -222,7 +222,7 @@ than the maximum value of ``uint`` (``2**256 - 1``). This is also true for the s :ref:`Errors ` allow you to provide more information to the caller about why a condition or operation failed. Errors are used together with the -:ref:`revert statement `. The revert statement unconditionally +:ref:`revert statement `. The ``revert`` statement unconditionally aborts and reverts all changes similar to the ``require`` function, but it also allows you to provide the name of an error and additional data which will be supplied to the caller (and eventually to the front-end application or block explorer) so that diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index b3a27bf1e..921211248 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -27,6 +27,9 @@ it does include the supplied string in the :ref:`bytecode metadata `. If you do not want to specify a license or if the source code is not open-source, please use the special value ``UNLICENSED``. +Note that ``UNLICENSED`` (no usage allowed, not present in SPDX license list) +is different from ``UNLICENSE`` (grants all rights to everyone). +Solidity follows `the npm recommendation `_. Supplying this comment of course does not free you from other obligations related to licensing like having to mention diff --git a/docs/resources.rst b/docs/resources.rst index a3b8d17e0..c70b35fbf 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -82,6 +82,9 @@ Editor Integrations * `Visual Studio Code extension `_ Solidity plugin for Microsoft Visual Studio Code that includes syntax highlighting and the Solidity compiler. + * `Solidity Visual Auditor extension `_ + Adds security centric syntax and semantic highlighting to Visual Studio Code. + Solidity Tools ============== diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 1bbe6cc74..a3d3f69a5 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -8,7 +8,7 @@ Style Guide Introduction ************ -This guide is intended to provide coding conventions for writing solidity code. +This guide is intended to provide coding conventions for writing Solidity code. This guide should be thought of as an evolving document that will change over time as useful conventions are found and old conventions are rendered obsolete. @@ -20,7 +20,7 @@ taken from python's `pep8 style guide `_. The goal of this guide is *not* to be the right way or the best way to write -solidity code. The goal of this guide is *consistency*. A quote from python's +Solidity code. The goal of this guide is *consistency*. A quote from python's `pep8 `_ captures this concept well. @@ -28,7 +28,7 @@ captures this concept well. A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important. - But most importantly: **know when to be inconsistent** -- sometimes the style guide just doesn't apply. When in doubt, use your best judgement. Look at other examples and decide what looks best. And don't hesitate to ask! + But most importantly: **know when to be inconsistent** -- sometimes the style guide just doesn't apply. When in doubt, use your best judgment. Look at other examples and decide what looks best. And don't hesitate to ask! *********** @@ -51,7 +51,7 @@ Mixing tabs and spaces should be avoided. Blank Lines =========== -Surround top level declarations in solidity source with two blank lines. +Surround top level declarations in Solidity source with two blank lines. Yes: @@ -680,7 +680,7 @@ No: } For long function declarations, it is recommended to drop each argument onto -it's own line at the same indentation level as the function body. The closing +its own line at the same indentation level as the function body. The closing parenthesis and opening bracket should be placed on their own line as well at the same indentation level as the function declaration. @@ -933,7 +933,7 @@ Permissible: function shortFunction() public { doSomething(); } These guidelines for function declarations are intended to improve readability. -Authors should use their best judgement as this guide does not try to cover all +Authors should use their best judgment as this guide does not try to cover all possible permutations for function declarations. Mappings @@ -1023,7 +1023,7 @@ No: * Operators with a higher priority than others can exclude surrounding whitespace in order to denote precedence. This is meant to allow for - improved readability for complex statement. You should always use the same + improved readability for complex statements. You should always use the same amount of whitespace on either side of an operator: Yes: diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst index 88e23a47e..014c49935 100644 --- a/docs/types/reference-types.rst +++ b/docs/types/reference-types.rst @@ -150,7 +150,7 @@ length or index access. Solidity does not have string manipulation functions, but there are third-party string libraries. You can also compare two strings by their keccak256-hash using ``keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))`` and -concatenate two strings using ``bytes.concat(bytes(s1), bytes(s2))``. +concatenate two strings using ``string.concat(s1, s2)``. You should use ``bytes`` over ``bytes1[]`` because it is cheaper, since using ``bytes1[]`` in ``memory`` adds 31 padding bytes between the elements. Note that in ``storage``, the @@ -165,31 +165,40 @@ always use one of the value types ``bytes1`` to ``bytes32`` because they are muc that you are accessing the low-level bytes of the UTF-8 representation, and not the individual characters. -.. index:: ! bytes-concat +.. index:: ! bytes-concat, ! string-concat .. _bytes-concat: +.. _string-concat: -``bytes.concat`` function -^^^^^^^^^^^^^^^^^^^^^^^^^ +The functions ``bytes.concat`` and ``string.concat`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You can concatenate a variable number of ``bytes`` or ``bytes1 ... bytes32`` using ``bytes.concat``. +You can concatenate an arbitrary number of ``string`` values using ``string.concat``. +The function returns a single ``string memory`` array that contains the contents of the arguments without padding. +If you want to use parameters of other types that are not implicitly convertible to ``string``, you need to convert them to ``string`` first. + +Analogously, the ``bytes.concat`` function can concatenate an arbitrary number of ``bytes`` or ``bytes1 ... bytes32`` values. The function returns a single ``bytes memory`` array that contains the contents of the arguments without padding. -If you want to use string parameters or other types, you need to convert them to ``bytes`` or ``bytes1``/.../``bytes32`` first. +If you want to use string parameters or other types that are not implicitly convertible to ``bytes``, you need to convert them to ``bytes`` or ``bytes1``/.../``bytes32`` first. + .. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 - pragma solidity ^0.8.4; + pragma solidity ^0.8.12; contract C { - bytes s = "Storage"; - function f(bytes calldata c, string memory m, bytes16 b) public view { - bytes memory a = bytes.concat(s, c, c[:2], "Literal", bytes(m), b); - assert((s.length + c.length + 2 + 7 + bytes(m).length + 16) == a.length); + string s = "Storage"; + function f(bytes calldata bc, string memory sm, bytes16 b) public view { + string memory concat_string = string.concat(s, string(bc), "Literal", sm); + assert((bytes(s).length + bc.length + 7 + bytes(sm).length) == bytes(concat_string).length); + + bytes memory concat_bytes = bytes.concat(bytes(s), bc, bc[:2], "Literal", bytes(sm), b); + assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) == concat_bytes.length); } } -If you call ``bytes.concat`` without arguments it will return an empty ``bytes`` array. +If you call ``string.concat`` or ``bytes.concat`` without arguments they return an empty array. .. index:: ! array;allocating, new diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 70125636c..7d172eb07 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -154,6 +154,14 @@ Members of bytes - ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of bytes and bytes1, ..., bytes32 arguments to one byte array` +.. index:: string members + +Members of string +----------------- + +- ``string.concat(...) returns (string memory)``: :ref:`Concatenates variable number of string arguments to one string array` + + .. index:: assert, revert, require Error Handling diff --git a/libsolidity/analysis/OverrideChecker.cpp b/libsolidity/analysis/OverrideChecker.cpp index 6f44f92ac..2f83748fb 100644 --- a/libsolidity/analysis/OverrideChecker.cpp +++ b/libsolidity/analysis/OverrideChecker.cpp @@ -897,10 +897,11 @@ OverrideChecker::OverrideProxyBySignatureMultiSet const& OverrideChecker::inheri if (var->isPublic()) functionsInBase.emplace(OverrideProxy{var}); - for (OverrideProxy const& func: inheritedFunctions(*base)) - functionsInBase.insert(func); - result += functionsInBase; + + for (OverrideProxy const& func: inheritedFunctions(*base)) + if (!functionsInBase.count(func)) + result.insert(func); } m_inheritedFunctions[&_contract] = result; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 1bd78ddc9..0bfa54f6e 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2207,14 +2207,42 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa } } + +void TypeChecker::typeCheckStringConcatFunction( + FunctionCall const& _functionCall, + FunctionType const* _functionType +) +{ + solAssert(_functionType); + solAssert(_functionType->kind() == FunctionType::Kind::StringConcat); + solAssert(_functionCall.names().empty()); + + typeCheckFunctionGeneralChecks(_functionCall, _functionType); + + for (shared_ptr const& argument: _functionCall.arguments()) + { + Type const* argumentType = type(*argument); + bool notConvertibleToString = !argumentType->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()); + + if (notConvertibleToString) + m_errorReporter.typeError( + 9977_error, + argument->location(), + "Invalid type for argument in the string.concat function call. " + "string type is required, but " + + argumentType->identifier() + " provided." + ); + } +} + void TypeChecker::typeCheckBytesConcatFunction( FunctionCall const& _functionCall, FunctionType const* _functionType ) { - solAssert(_functionType, ""); - solAssert(_functionType->kind() == FunctionType::Kind::BytesConcat, ""); - solAssert(_functionCall.names().empty(), ""); + solAssert(_functionType); + solAssert(_functionType->kind() == FunctionType::Kind::BytesConcat); + solAssert(_functionCall.names().empty()); typeCheckFunctionGeneralChecks(_functionCall, _functionType); @@ -2651,6 +2679,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = functionType->returnParameterTypes(); break; } + case FunctionType::Kind::StringConcat: + { + typeCheckStringConcatFunction(_functionCall, functionType); + returnTypes = functionType->returnParameterTypes(); + break; + } case FunctionType::Kind::Wrap: case FunctionType::Kind::Unwrap: { diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 7586f9d15..969ee4394 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -113,6 +113,12 @@ private: /// Performs checks specific to the ABI encode functions of type ABIEncodeCall void typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall); + /// Performs general checks and checks specific to string concat function call + void typeCheckStringConcatFunction( + FunctionCall const& _functionCall, + FunctionType const* _functionType + ); + /// Performs general checks and checks specific to bytes concat function call void typeCheckBytesConcatFunction( FunctionCall const& _functionCall, diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index f2b7369b7..2a7609de8 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -192,7 +192,7 @@ FunctionDefinition const* ContractDefinition::receiveFunction() const return nullptr; } -vector const& ContractDefinition::interfaceEvents() const +vector const& ContractDefinition::definedInterfaceEvents() const { return m_interfaceEvents.init([&]{ set eventsSeen; @@ -213,11 +213,20 @@ vector const& ContractDefinition::interfaceEvents() cons interfaceEvents.push_back(e); } } - return interfaceEvents; }); } +vector const ContractDefinition::usedInterfaceEvents() const +{ + solAssert(annotation().creationCallGraph.set(), ""); + + return convertContainer>( + (*annotation().creationCallGraph)->emittedEvents + + (*annotation().deployedCallGraph)->emittedEvents + ); +} + vector ContractDefinition::interfaceErrors(bool _requireCallGraph) const { set result; @@ -227,10 +236,9 @@ vector ContractDefinition::interfaceErrors(bool _require if (_requireCallGraph) solAssert(annotation().creationCallGraph.set(), ""); if (annotation().creationCallGraph.set()) - { - result += (*annotation().creationCallGraph)->usedErrors; - result += (*annotation().deployedCallGraph)->usedErrors; - } + result += + (*annotation().creationCallGraph)->usedErrors + + (*annotation().deployedCallGraph)->usedErrors; return convertContainer>(move(result)); } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index b7d5db805..c35c69c9d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -519,7 +519,8 @@ public: return ranges::subrange(b, e) | ranges::views::values; } std::vector events() const { return filteredNodes(m_subNodes); } - std::vector const& interfaceEvents() const; + std::vector const& definedInterfaceEvents() const; + std::vector const usedInterfaceEvents() const; /// @returns all errors defined in this contract or any base contract /// and all errors referenced during execution. /// @param _requireCallGraph if false, do not fail if the call graph has not been computed yet. diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3bbb99ebd..17b3c9fea 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2932,6 +2932,7 @@ string FunctionType::richIdentifier() const case Kind::ArrayPush: id += "arraypush"; break; case Kind::ArrayPop: id += "arraypop"; break; case Kind::BytesConcat: id += "bytesconcat"; break; + case Kind::StringConcat: id += "stringconcat"; break; case Kind::ObjectCreation: id += "objectcreation"; break; case Kind::Assert: id += "assert"; break; case Kind::Require: id += "require"; break; @@ -3821,15 +3822,14 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons ) members.emplace_back("concat", TypeProvider::function( TypePointers{}, - TypePointers{TypeProvider::bytesMemory()}, + TypePointers{arrayType->isString() ? TypeProvider::stringMemory() : TypeProvider::bytesMemory()}, strings{}, - strings{string()}, - FunctionType::Kind::BytesConcat, + strings{string{}}, + arrayType->isString() ? FunctionType::Kind::StringConcat : FunctionType::Kind::BytesConcat, StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() )); - return members; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c4aaf38ca..9b030f65a 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1228,6 +1228,7 @@ public: ArrayPush, ///< .push() to a dynamically sized array in storage ArrayPop, ///< .pop() from a dynamically sized array in storage BytesConcat, ///< .concat() on bytes (type type) + StringConcat, ///< .concat() on string (type type) ObjectCreation, ///< array creation using new Assert, ///< assert() Require, ///< require() diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index da3022251..e4ad4cf7e 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1101,6 +1101,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) ArrayUtils(m_context).popStorageArrayElement(*arrayType); break; } + case FunctionType::Kind::StringConcat: case FunctionType::Kind::BytesConcat: { _functionCall.expression().accept(*this); @@ -1121,8 +1122,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) else { solAssert(!dynamic_cast(argument->annotation().type), ""); - solAssert(argument->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()), ""); - targetTypes.emplace_back(TypeProvider::bytesMemory()); + if (function.kind() == FunctionType::Kind::StringConcat) + { + solAssert(argument->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), ""); + targetTypes.emplace_back(TypeProvider::stringMemory()); + } + else if (function.kind() == FunctionType::Kind::BytesConcat) + { + solAssert(argument->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()), ""); + targetTypes.emplace_back(TypeProvider::bytesMemory()); + } } } utils().fetchFreeMemoryPointer(); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 9b50d68cd..23f41efd2 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -2475,18 +2475,26 @@ string YulUtilFunctions::copyArrayFromStorageToMemoryFunction(ArrayType const& _ }); } -string YulUtilFunctions::bytesConcatFunction(vector const& _argumentTypes) +string YulUtilFunctions::bytesOrStringConcatFunction( + vector const& _argumentTypes, + FunctionType::Kind _functionTypeKind +) { - string functionName = "bytes_concat"; + solAssert(_functionTypeKind == FunctionType::Kind::BytesConcat || _functionTypeKind == FunctionType::Kind::StringConcat); + std::string functionName = (_functionTypeKind == FunctionType::Kind::StringConcat) ? "string_concat" : "bytes_concat"; size_t totalParams = 0; vector targetTypes; + for (Type const* argumentType: _argumentTypes) { - solAssert( - argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) || - argumentType->isImplicitlyConvertibleTo(*TypeProvider::fixedBytes(32)), - "" - ); + if (_functionTypeKind == FunctionType::Kind::StringConcat) + solAssert(argumentType->isImplicitlyConvertibleTo(*TypeProvider::stringMemory())); + else if (_functionTypeKind == FunctionType::Kind::BytesConcat) + solAssert( + argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) || + argumentType->isImplicitlyConvertibleTo(*TypeProvider::fixedBytes(32)) + ); + if (argumentType->category() == Type::Category::FixedBytes) targetTypes.emplace_back(argumentType); else if ( @@ -2496,15 +2504,16 @@ string YulUtilFunctions::bytesConcatFunction(vector const& _argumen targetTypes.emplace_back(TypeProvider::fixedBytes(static_cast(literalType->value().size()))); else { - solAssert(!dynamic_cast(argumentType), ""); - solAssert(argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()), ""); - targetTypes.emplace_back(TypeProvider::bytesMemory()); + solAssert(!dynamic_cast(argumentType)); + targetTypes.emplace_back( + _functionTypeKind == FunctionType::Kind::StringConcat ? + TypeProvider::stringMemory() : + TypeProvider::bytesMemory() + ); } - totalParams += argumentType->sizeOnStack(); functionName += "_" + argumentType->identifier(); } - return m_functionCollector.createFunction(functionName, [&]() { Whiskers templ(R"( function () -> outPtr { diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 52d672b1f..4d6e7efda 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -312,9 +312,13 @@ public: /// of the storage array into it. std::string copyArrayFromStorageToMemoryFunction(ArrayType const& _from, ArrayType const& _to); - /// @returns the name of a function that does concatenation of variadic number of bytes - /// or fixed bytes - std::string bytesConcatFunction(std::vector const& _argumentTypes); + /// @returns the name of a function that does concatenation of variadic number of + /// bytes if @a functionTypeKind is FunctionType::Kind::BytesConcat, + /// or of strings, if @a functionTypeKind is FunctionType::Kind::StringConcat. + std::string bytesOrStringConcatFunction( + std::vector const& _argumentTypes, + FunctionType::Kind _functionTypeKind + ); /// @returns the name of a function that performs index access for mappings. /// @param _mappingType the type of the mapping diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 065b351f6..9fb73f021 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1389,6 +1389,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } break; } + case FunctionType::Kind::StringConcat: case FunctionType::Kind::BytesConcat: { TypePointers argumentTypes; @@ -1399,11 +1400,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) argumentVars += IRVariable(*argument).stackSlots(); } define(IRVariable(_functionCall)) << - m_utils.bytesConcatFunction(argumentTypes) << + m_utils.bytesOrStringConcatFunction(argumentTypes, functionType->kind()) << "(" << joinHumanReadable(argumentVars) << ")\n"; - break; } case FunctionType::Kind::MetaType: diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 9c7a645b6..eab1801c6 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -101,7 +101,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); abi.emplace(std::move(method)); } - for (auto const& it: _contractDef.interfaceEvents()) + for (auto const& it: _contractDef.definedInterfaceEvents()) { Json::Value event{Json::objectValue}; event["type"] = "event"; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 5292d5329..7a8bc4ddb 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -75,11 +75,14 @@ #include #include #include +#include #include #include +#include + #include #include #include @@ -1013,15 +1016,34 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const return _contract.devDocumentation.init([&]{ return Natspec::devDocumentation(*_contract.contract); }); } -Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const +Json::Value CompilerStack::interfaceSymbols(string const& _contractName) const { if (m_stackState < AnalysisPerformed) solThrow(CompilerError, "Analysis was not successful."); - Json::Value methodIdentifiers(Json::objectValue); + Json::Value interfaceSymbols(Json::objectValue); + // Always have a methods object + interfaceSymbols["methods"] = Json::objectValue; + for (auto const& it: contractDefinition(_contractName).interfaceFunctions()) - methodIdentifiers[it.second->externalSignature()] = it.first.hex(); - return methodIdentifiers; + interfaceSymbols["methods"][it.second->externalSignature()] = it.first.hex(); + for (ErrorDefinition const* error: contractDefinition(_contractName).interfaceErrors()) + { + string signature = error->functionType(true)->externalSignature(); + interfaceSymbols["errors"][signature] = toHex(toCompactBigEndian(selectorFromSignature32(signature), 4)); + } + + for (EventDefinition const* event: ranges::concat_view( + contractDefinition(_contractName).definedInterfaceEvents(), + contractDefinition(_contractName).usedInterfaceEvents() + )) + if (!event->isAnonymous()) + { + string signature = event->functionType(true)->externalSignature(); + interfaceSymbols["events"][signature] = toHex(u256(h256::Arith(keccak256(signature)))); + } + + return interfaceSymbols; } bytes CompilerStack::cborMetadata(string const& _contractName, bool _forIR) const diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 3609662e0..c1f15a480 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -327,8 +327,8 @@ public: /// Prerequisite: Successful call to parse or compile. Json::Value const& natspecDev(std::string const& _contractName) const; - /// @returns a JSON representing a map of method identifiers (hashes) to function names. - Json::Value methodIdentifiers(std::string const& _contractName) const; + /// @returns a JSON object with the three members ``methods``, ``events``, ``errors``. Each is a map, mapping identifiers (hashes) to function names. + Json::Value interfaceSymbols(std::string const& _contractName) const; /// @returns the Contract Metadata matching the pipeline selected using the viaIR setting. std::string const& metadata(std::string const& _contractName) const { return metadata(contract(_contractName)); } diff --git a/libsolidity/interface/FileReader.cpp b/libsolidity/interface/FileReader.cpp index 47f4d2b5a..e581c4a56 100644 --- a/libsolidity/interface/FileReader.cpp +++ b/libsolidity/interface/FileReader.cpp @@ -116,7 +116,6 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so for (auto const& prefix: prefixes) { boost::filesystem::path canonicalPath = normalizeCLIPathForVFS(prefix / strippedSourceUnitName, SymlinkResolution::Enabled); - if (boost::filesystem::exists(canonicalPath)) candidates.push_back(std::move(canonicalPath)); } @@ -124,7 +123,12 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so auto pathToQuotedString = [](boost::filesystem::path const& _path){ return "\"" + _path.string() + "\""; }; if (candidates.empty()) - return ReadCallback::Result{false, "File not found."}; + return ReadCallback::Result{ + false, + "File not found. Searched the following locations: " + + joinHumanReadable(prefixes | ranges::views::transform(pathToQuotedString), ", ") + + "." + }; if (candidates.size() >= 2) return ReadCallback::Result{ @@ -135,11 +139,13 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so "." }; - FileSystemPathSet extraAllowedPaths = {m_basePath.empty() ? "." : m_basePath}; - extraAllowedPaths += m_includePaths; + FileSystemPathSet allowedPaths = + m_allowedDirectories + + decltype(allowedPaths){m_basePath.empty() ? "." : m_basePath} + + m_includePaths; bool isAllowed = false; - for (boost::filesystem::path const& allowedDir: m_allowedDirectories + extraAllowedPaths) + for (boost::filesystem::path const& allowedDir: allowedPaths) if (isPathPrefix(normalizeCLIPathForVFS(allowedDir, SymlinkResolution::Enabled), candidates[0])) { isAllowed = true; @@ -147,7 +153,12 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so } if (!isAllowed) - return ReadCallback::Result{false, "File outside of allowed directories."}; + return ReadCallback::Result{ + false, + "File outside of allowed directories. The following are allowed: " + + joinHumanReadable(allowedPaths | ranges::views::transform(pathToQuotedString), ", ") + + "." + }; if (!boost::filesystem::is_regular_file(candidates[0])) return ReadCallback::Result{false, "Not a valid file."}; @@ -269,7 +280,8 @@ boost::filesystem::path FileReader::normalizeCLIPathForVFS( if (!isUNCPath(normalizedPath)) { boost::filesystem::path workingDirRootPath = canonicalWorkDir.root_path(); - if (normalizedRootPath == workingDirRootPath) + // Ignore drive letter case on Windows (C:\ <=> c:\). + if (boost::filesystem::equivalent(normalizedRootPath, workingDirRootPath)) normalizedRootPath = "/"; } diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index 533cbaa3d..449690493 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -78,7 +78,7 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) doc["methods"][it.second->externalSignature()]["notice"] = value; } - for (auto const& event: _contractDef.interfaceEvents()) + for (auto const& event: _contractDef.definedInterfaceEvents()) { string value = extractDoc(event->annotation().docTags, "notice"); if (!value.empty()) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 5ccf2d447..75876d935 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -1298,7 +1298,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental)) evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName); if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental)) - evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName); + evmData["methodIdentifiers"] = compilerStack.interfaceSymbols(contractName)["methods"]; if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental)) evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); diff --git a/libyul/backends/evm/StackHelpers.h b/libyul/backends/evm/StackHelpers.h index 38d0ddf4b..a0a942860 100644 --- a/libyul/backends/evm/StackHelpers.h +++ b/libyul/backends/evm/StackHelpers.h @@ -262,6 +262,11 @@ private: if (ops.sourceMultiplicity(ops.sourceSize() - 1 - swapDepth) < 0) { ops.swap(swapDepth); + if (ops.targetIsArbitrary(sourceTop)) + // Usually we keep a slot that is to-be-removed, if the current top is arbitrary. + // However, since we are in a stack-too-deep situation, pop it immediately + // to compress the stack (we can always push back junk in the end). + ops.pop(); return true; } // Otherwise we rely on stack compression or stack-to-memory. @@ -321,14 +326,44 @@ private: yulAssert(ops.sourceMultiplicity(i) == 0 && (ops.targetIsArbitrary(i) || ops.targetMultiplicity(i) == 0), ""); yulAssert(ops.isCompatible(sourceTop, sourceTop), ""); + auto swappableOffsets = ranges::views::iota(size > 17 ? size - 17 : 0u, size); + // If we find a lower slot that is out of position, but also compatible with the top, swap that up. + for (size_t offset: swappableOffsets) + if (!ops.isCompatible(offset, offset) && ops.isCompatible(sourceTop, offset)) + { + ops.swap(size - offset - 1); + return true; + } + // Swap up any reachable slot that is still out of position. + for (size_t offset: swappableOffsets) + if (!ops.isCompatible(offset, offset) && !ops.sourceIsSame(offset, sourceTop)) + { + ops.swap(size - offset - 1); + return true; + } + // We are in a stack-too-deep situation and try to reduce the stack size. + // If the current top is merely kept since the target slot is arbitrary, pop it. + if (ops.targetIsArbitrary(sourceTop) && ops.sourceMultiplicity(sourceTop) <= 0) + { + ops.pop(); + return true; + } + // If any reachable slot is merely kept, since the target slot is arbitrary, swap it up and pop it. + for (size_t offset: swappableOffsets) + if (ops.targetIsArbitrary(offset) && ops.sourceMultiplicity(offset) <= 0) + { + ops.swap(size - offset - 1); + ops.pop(); + return true; + } + // We cannot avoid a stack-too-deep error. Repeat the above without restricting to reachable slots. for (size_t offset: ranges::views::iota(0u, size)) if (!ops.isCompatible(offset, offset) && ops.isCompatible(sourceTop, offset)) { ops.swap(size - offset - 1); return true; } - // Swap up any slot that is still out of position. for (size_t offset: ranges::views::iota(0u, size)) if (!ops.isCompatible(offset, offset) && !ops.sourceIsSame(offset, sourceTop)) { diff --git a/scripts/ci/base64DecToArr.js b/scripts/ci/base64DecToArr.js new file mode 100644 index 000000000..e1f4729bc --- /dev/null +++ b/scripts/ci/base64DecToArr.js @@ -0,0 +1,46 @@ +function base64DecToArr (sBase64) { +/*\ +|*| +|*| Base64 / binary data / UTF-8 strings utilities +|*| +|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding +|*| +\*/ + +/* Array of bytes to Base64 string decoding */ + +function b64ToUint6 (nChr) { + + return nChr > 64 && nChr < 91 ? + nChr - 65 + : nChr > 96 && nChr < 123 ? + nChr - 71 + : nChr > 47 && nChr < 58 ? + nChr + 4 + : nChr === 43 ? + 62 + : nChr === 47 ? + 63 + : + 0; + +} + + var + nInLen = sBase64.length, + nOutLen = nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); + + for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= b64ToUint6(sBase64.charCodeAt(nInIdx)) << 6 * (3 - nMod4); + if (nMod4 === 3 || nInLen - nInIdx === 1) { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { + taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; + } + nUint24 = 0; + + } + } + + return taBytes; +} diff --git a/scripts/ci/build_emscripten.sh b/scripts/ci/build_emscripten.sh index 7eac2486f..55d2288c3 100755 --- a/scripts/ci/build_emscripten.sh +++ b/scripts/ci/build_emscripten.sh @@ -40,6 +40,8 @@ else BUILD_DIR="$1" fi +apt-get update && apt-get install lz4 + WORKSPACE=/root/project cd $WORKSPACE @@ -71,8 +73,8 @@ make soljson cd .. mkdir -p upload -cp "$BUILD_DIR/libsolc/soljson.js" upload/ -cp "$BUILD_DIR/libsolc/soljson.js" ./ +scripts/ci/pack_soljson.sh "$BUILD_DIR/libsolc/soljson.js" "$BUILD_DIR/libsolc/soljson.wasm" upload/soljson.js +cp upload/soljson.js ./ OUTPUT_SIZE=$(ls -la soljson.js) diff --git a/scripts/ci/mini-lz4.js b/scripts/ci/mini-lz4.js new file mode 100644 index 000000000..767647003 --- /dev/null +++ b/scripts/ci/mini-lz4.js @@ -0,0 +1,116 @@ +function uncompress(source, uncompressedSize) { +/* +based off https://github.com/emscripten-core/emscripten/blob/main/third_party/mini-lz4.js +The license only applies to the body of this function (``uncompress``). +==== +MiniLZ4: Minimal LZ4 block decoding and encoding. + +based off of node-lz4, https://github.com/pierrec/node-lz4 + +==== +Copyright (c) 2012 Pierre Curto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +==== + +changes have the same license +*/ +/** + * Decode a block. Assumptions: input contains all sequences of a + * chunk, output is large enough to receive the decoded data. + * If the output buffer is too small, an error will be thrown. + * If the returned value is negative, an error occurred at the returned offset. + * + * @param {ArrayBufferView} input input data + * @param {ArrayBufferView} output output data + * @param {number=} sIdx + * @param {number=} eIdx + * @return {number} number of decoded bytes + * @private + */ +function uncompressBlock (input, output, sIdx, eIdx) { + sIdx = sIdx || 0 + eIdx = eIdx || (input.length - sIdx) + // Process each sequence in the incoming data + for (var i = sIdx, n = eIdx, j = 0; i < n;) { + var token = input[i++] + + // Literals + var literals_length = (token >> 4) + if (literals_length > 0) { + // length of literals + var l = literals_length + 240 + while (l === 255) { + l = input[i++] + literals_length += l + } + + // Copy the literals + var end = i + literals_length + while (i < end) output[j++] = input[i++] + + // End of buffer? + if (i === n) return j + } + + // Match copy + // 2 bytes offset (little endian) + var offset = input[i++] | (input[i++] << 8) + + // XXX 0 is an invalid offset value + if (offset === 0) return j + if (offset > j) return -(i-2) + + // length of match copy + var match_length = (token & 0xf) + var l = match_length + 240 + while (l === 255) { + l = input[i++] + match_length += l + } + // Copy the match + var pos = j - offset // position of the match copy in the current output + var end = j + match_length + 4 // minmatch = 4 + while (j < end) output[j++] = output[pos++] + } + + return j +} +var result = new ArrayBuffer(uncompressedSize); +var sourceIndex = 0; +var destIndex = 0; +var blockSize; +while((blockSize = (source[sourceIndex] | (source[sourceIndex + 1] << 8) | (source[sourceIndex + 2] << 16) | (source[sourceIndex + 3] << 24))) > 0) +{ + sourceIndex += 4; + if (blockSize & 0x80000000) + { + blockSize &= 0x7FFFFFFFF; + for (var i = 0; i < blockSize; i++) { + result[destIndex++] = source[sourceIndex++]; + } + } + else + { + destIndex += uncompressBlock(source, new Uint8Array(result, destIndex, uncompressedSize - destIndex), sourceIndex, sourceIndex + blockSize); + sourceIndex += blockSize; + } +} +return new Uint8Array(result, 0, uncompressedSize); +} diff --git a/scripts/ci/pack_soljson.sh b/scripts/ci/pack_soljson.sh new file mode 100755 index 000000000..75ef9e09b --- /dev/null +++ b/scripts/ci/pack_soljson.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(realpath "$(dirname "$0")")" +soljson_js="$1" +soljson_wasm="$2" +soljson_wasm_size=$(wc -c "${soljson_wasm}" | cut -d ' ' -f 1) +output="$3" + +(( $# == 3 )) || { >&2 echo "Usage: $0 soljson.js soljson.wasm packed_soljson.js"; exit 1; } + +# If this changes in an emscripten update, it's probably nothing to worry about, +# but we should double-check when it happens and adjust the tail command below. +[[ $(head -c 5 "${soljson_js}") == "null;" ]] || { >&2 echo 'Expected soljson.js to start with "null;"'; exit 1; } + +echo "Packing $soljson_js and $soljson_wasm to $output." +( + echo -n 'var Module = Module || {}; Module["wasmBinary"] = ' + echo -n '(function(source, uncompressedSize) {' + # Note that base64DecToArr assumes no trailing equals signs. + cpp "${script_dir}/base64DecToArr.js" | grep -v "^#.*" + # Note that mini-lz4.js assumes no file header and no frame crc checksums. + cpp "${script_dir}/mini-lz4.js" | grep -v "^#.*" + echo 'return uncompress(base64DecToArr(source), uncompressedSize);})(' + echo -n '"' + # We fix lz4 format settings, remove the 8 bytes file header and remove the trailing equals signs of the base64 encoding. + lz4c --no-frame-crc --best --favor-decSpeed "${soljson_wasm}" - | tail -c +8 | base64 -w 0 | sed 's/[^A-Za-z0-9\+\/]//g' + echo '",' + echo -n "${soljson_wasm_size});" + # Remove "null;" from the js wrapper. + tail -c +6 "${soljson_js}" +) > "$output" + +echo "Testing $output." +echo "process.stdout.write(require('$(realpath "${output}")').wasmBinary)" | node | cmp "${soljson_wasm}" && echo "Binaries match." +# Allow the wasm binary to be garbage collected after compilation. +echo 'Module["wasmBinary"] = undefined;' >> "${output}" diff --git a/scripts/externalTests/merge_benchmarks.sh b/scripts/externalTests/merge_benchmarks.sh new file mode 100755 index 000000000..3e2d7318c --- /dev/null +++ b/scripts/externalTests/merge_benchmarks.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +# ------------------------------------------------------------------------------ +# Reads multiple individual benchmark reports produced by scripts from +# test/externalTests/ from standard input and creates a combined report. +# +# Usage: +#