diff --git a/.circleci/config.yml b/.circleci/config.yml index 611675a26..0813fe576 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,20 +9,20 @@ version: 2.1 parameters: ubuntu-2004-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-9 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:3d8a912e8e78e98cd217955d06d98608ad60adc67728d4c3a569991235fa1abb" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-10 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:e61939751ff9777307857361f712b581bfc8a8aaae75fab7b50febc764710587" ubuntu-2004-clang-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-9 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:a1ba002cae17279d1396a898b04e4e9c45602ad881295db3e2f484a7e24f6f43" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-10 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:0de8c68f084120b2a165406e3a0c2aab58b32f5b7182c2322245245f1d243b8d" ubuntu-1604-clang-ossfuzz-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-14 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:f353823cce2f6cd2f9f1459d86cd76fdfc551a0261d87626615ea6c1d8f90587" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-15 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:87f1a57586eec194a6217ab624efc69d3d9af2f7ac8ca36497ad57488c2f08ae" emscripten-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:emscripten-8 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:842d6074e0e7e5355c89122c1cafc1fdb59696596750e7d56e5f35c0d883ad59" + # solbuildpackpusher/solidity-buildpack-deps:emscripten-9 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24" evm-version: type: string default: london @@ -53,8 +53,11 @@ commands: workflow_info=$(curl --silent "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}") || true workflow_name=$(echo "$workflow_info" | grep -E '"\s*name"\s*:\s*".*"' | cut -d \" -f 4 || echo "$CIRCLE_WORKFLOW_ID") - [[ "<< parameters.event >>" == "failure" ]] && message=" ❌ [${workflow_name}] Job **${CIRCLE_JOB}** failed on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details." - [[ "<< parameters.event >>" == "success" ]] && message=" ✅ [${workflow_name}] Job **${CIRCLE_JOB}** succeeded on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details." + [[ $CIRCLE_NODE_TOTAL == 1 ]] && job="**${CIRCLE_JOB}**" + [[ $CIRCLE_NODE_TOTAL != 1 ]] && job="**${CIRCLE_JOB}** (run $((CIRCLE_NODE_INDEX + 1))/${CIRCLE_NODE_TOTAL})" + + [[ "<< parameters.event >>" == "failure" ]] && message=" ❌ [${workflow_name}] Job ${job} failed on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details." + [[ "<< parameters.event >>" == "success" ]] && message=" ✅ [${workflow_name}] Job ${job} succeeded on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details." curl "https://api.gitter.im/v1/rooms/${GITTER_NOTIFY_ROOM_ID}/chatMessages" \ --request POST \ @@ -355,7 +358,15 @@ defaults: xcode: "13.2.0" environment: TERM: xterm - MAKEFLAGS: -j 5 + MAKEFLAGS: -j5 + + - base_osx_large: &base_osx_large + macos: + xcode: "13.2.0" + resource_class: large + environment: + TERM: xterm + MAKEFLAGS: -j10 - base_ems_large: &base_ems_large docker: @@ -464,6 +475,98 @@ defaults: requires: - b_win_release + # -------------------------------------------------------------------------- + # Parameterized Job Templates + + # Separate compile-only runs of those external tests where a full run takes much longer. + - job_ems_compile_ext_colony: &job_ems_compile_ext_colony + <<: *workflow_emscripten + name: t_ems_compile_ext_colony + project: colony + binary_type: solcjs + compile_only: 1 + nodejs_version: '14' + - job_native_compile_ext_gnosis: &job_native_compile_ext_gnosis + <<: *workflow_ubuntu2004_static + name: t_native_compile_ext_gnosis + project: gnosis + binary_type: native + compile_only: 1 + nodejs_version: '14' + + - job_native_test_ext_gnosis: &job_native_test_ext_gnosis + <<: *workflow_emscripten + name: t_native_test_ext_gnosis + project: gnosis + binary_type: native + # NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1"). + nodejs_version: '12' + - job_native_test_ext_gnosis_v2: &job_native_test_ext_gnosis_v2 + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_gnosis_v2 + project: gnosis-v2 + binary_type: native + # NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1"). + nodejs_version: '12' + - job_native_test_ext_zeppelin: &job_native_test_ext_zeppelin + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_zeppelin + project: zeppelin + binary_type: native + # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" + nodejs_version: '16' + resource_class: large + - job_native_test_ext_ens: &job_native_test_ext_ens + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_ens + project: ens + binary_type: native + # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" + nodejs_version: '16' + - job_native_test_ext_trident: &job_native_test_ext_trident + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_trident + project: trident + binary_type: native + # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" + nodejs_version: '16' + - job_native_test_ext_euler: &job_native_test_ext_euler + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_euler + project: euler + binary_type: native + # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" + nodejs_version: '16' + - job_native_test_ext_yield_liquidator: &job_native_test_ext_yield_liquidator + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_yield_liquidator + project: yield-liquidator + binary_type: native + # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" + nodejs_version: '16' + - job_native_test_ext_bleeps: &job_native_test_ext_bleeps + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_bleeps + project: bleeps + binary_type: native + # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" + nodejs_version: '16' + resource_class: medium + - job_native_test_ext_pool_together: &job_native_test_ext_pool_together + <<: *workflow_ubuntu2004_static + name: t_native_test_ext_pool_together + project: pool-together + binary_type: native + # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" + nodejs_version: '16' + - job_ems_test_ext_colony: &job_ems_test_ext_colony + <<: *workflow_emscripten + name: t_ems_test_ext_colony + project: colony + binary_type: solcjs + nodejs_version: '14' + resource_class: medium + # ----------------------------------------------------------------------------------------------- jobs: @@ -791,11 +894,11 @@ jobs: - gitter_notify_failure_unless_pr b_osx: - <<: *base_osx + <<: *base_osx_large environment: TERM: xterm CMAKE_BUILD_TYPE: Release - MAKEFLAGS: -j 5 + MAKEFLAGS: -j10 steps: - checkout - restore_cache: @@ -897,7 +1000,7 @@ jobs: t_ubu_soltest_all: &t_ubu_soltest_all <<: *base_ubuntu2004 - parallelism: 15 # 7 EVM versions, each with/without optimization + 1 ABIv1/@nooptions run + parallelism: 50 <<: *steps_soltest_all t_ubu_lsp: &t_ubu_lsp @@ -906,6 +1009,7 @@ jobs: t_archlinux_soltest: &t_archlinux_soltest <<: *base_archlinux + parallelism: 20 environment: EVM: << pipeline.parameters.evm-version >> OPTIMIZE: 0 @@ -924,6 +1028,7 @@ jobs: t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul <<: *base_ubuntu2004 + parallelism: 20 environment: EVM: << pipeline.parameters.evm-version >> SOLTEST_FLAGS: --enforce-via-yul @@ -933,6 +1038,7 @@ jobs: t_ubu_clang_soltest: &t_ubu_clang_soltest <<: *base_ubuntu2004_clang + parallelism: 20 environment: EVM: << pipeline.parameters.evm-version >> OPTIMIZE: 0 @@ -960,6 +1066,7 @@ jobs: t_ubu_asan_soltest: <<: *base_ubuntu2004 + parallelism: 20 environment: EVM: << pipeline.parameters.evm-version >> OPTIMIZE: 0 @@ -969,6 +1076,7 @@ jobs: t_ubu_asan_clang_soltest: <<: *base_ubuntu2004_clang + parallelism: 20 environment: EVM: << pipeline.parameters.evm-version >> OPTIMIZE: 0 @@ -978,6 +1086,7 @@ jobs: t_ubu_ubsan_clang_soltest: <<: *base_ubuntu2004_clang + parallelism: 20 environment: EVM: << pipeline.parameters.evm-version >> <<: *steps_soltest @@ -1318,53 +1427,20 @@ workflows: - t_ems_solcjs: *workflow_emscripten - t_ems_ext_hardhat: *workflow_emscripten - # Separate compile-only runs of those external tests where a full run takes much longer. - - t_ems_ext: - <<: *workflow_emscripten - name: t_ems_compile_ext_colony - project: colony - binary_type: solcjs - compile_only: 1 - nodejs_version: '14' - - t_ems_ext: - <<: *workflow_ubuntu2004_static - name: t_native_compile_ext_gnosis - project: gnosis - binary_type: native - compile_only: 1 - nodejs_version: '14' + - t_ems_ext: *job_ems_compile_ext_colony + - t_ems_ext: *job_native_compile_ext_gnosis # FIXME: Gnosis tests are pretty flaky right now. They often fail on CircleCI due to random ProviderError # and there are also other less frequent problems. See https://github.com/gnosis/safe-contracts/issues/216. - #- t_ems_ext: - # <<: *workflow_emscripten - # name: t_native_test_ext_gnosis - # project: gnosis - # binary_type: native - # # NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1"). - # nodejs_version: '12' - - t_ems_ext: - <<: *workflow_ubuntu2004_static - name: t_native_test_ext_gnosis_v2 - project: gnosis-v2 - binary_type: native - # NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1"). - nodejs_version: '12' - - t_ems_ext: - <<: *workflow_ubuntu2004_static - name: t_native_test_ext_zeppelin - project: zeppelin - binary_type: native - # NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported" - nodejs_version: '16' - resource_class: large - - t_ems_ext: - <<: *workflow_ubuntu2004_static - name: t_native_test_ext_ens - project: ens - binary_type: native - # NOTE: One of the dependencies (fsevents) fails to build its native extension on node.js 12+. - nodejs_version: '10' + #-t_ems_ext: *job_native_test_ext_gnosis + - t_ems_ext: *job_native_test_ext_gnosis_v2 + - t_ems_ext: *job_native_test_ext_zeppelin + - t_ems_ext: *job_native_test_ext_ens + - t_ems_ext: *job_native_test_ext_trident + - t_ems_ext: *job_native_test_ext_euler + - t_ems_ext: *job_native_test_ext_yield_liquidator + - t_ems_ext: *job_native_test_ext_bleeps + - t_ems_ext: *job_native_test_ext_pool_together # Windows build and tests - b_win: *workflow_trigger_on_tags @@ -1374,18 +1450,23 @@ workflows: # Bytecode comparison: - b_bytecode_ubu: + <<: *workflow_trigger_on_tags requires: - b_ubu - b_bytecode_win: + <<: *workflow_trigger_on_tags requires: - b_win - b_bytecode_osx: + <<: *workflow_trigger_on_tags requires: - b_osx - b_bytecode_ems: + <<: *workflow_trigger_on_tags requires: - b_ems - t_bytecode_compare: + <<: *workflow_trigger_on_tags requires: - b_bytecode_ubu - b_bytecode_win @@ -1425,10 +1506,4 @@ workflows: # Emscripten build and tests that take more than 15 minutes to execute - b_ems: *workflow_trigger_on_tags - - t_ems_ext: - <<: *workflow_emscripten - name: t_ems_test_ext_colony - project: colony - binary_type: solcjs - nodejs_version: '14' - resource_class: medium + - t_ems_ext: *job_ems_test_ext_colony diff --git a/.circleci/osx_install_dependencies.sh b/.circleci/osx_install_dependencies.sh index 36d336774..cd0637eac 100755 --- a/.circleci/osx_install_dependencies.sh +++ b/.circleci/osx_install_dependencies.sh @@ -61,11 +61,11 @@ then ./scripts/install_obsolete_jsoncpp_1_7_4.sh # z3 - z3_version="4.8.13" + z3_version="4.8.14" z3_dir="z3-${z3_version}-x64-osx-10.16" z3_package="${z3_dir}.zip" wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}" - validate_checksum "$z3_package" 191b26be2b617b2dffffce139d77abcd7e584859efbc10a58d01a1d7830697a4 + validate_checksum "$z3_package" 1341671670e0c4e72da80815128a68975ee90816d50ceaf6bd820f06babe2cfd unzip "$z3_package" rm "$z3_package" cp "${z3_dir}/bin/libz3.a" /usr/local/lib diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh index 1814b8728..30bd1274c 100755 --- a/.circleci/soltest.sh +++ b/.circleci/soltest.sh @@ -50,19 +50,48 @@ mkdir -p test_results ulimit -s 16384 get_logfile_basename() { + local run="$1" local filename="${EVM}" test "${OPTIMIZE}" = "1" && filename="${filename}_opt" test "${ABI_ENCODER_V1}" = "1" && filename="${filename}_abiv1" + filename="${filename}_${run}" echo -ne "${filename}" } -BOOST_TEST_ARGS=("--color_output=no" "--show_progress=yes" "--logger=JUNIT,error,test_results/$(get_logfile_basename).xml" "${BOOST_TEST_ARGS[@]}") -SOLTEST_ARGS=("--evm-version=$EVM" "${SOLTEST_FLAGS[@]}") +[ -z "$CIRCLE_NODE_TOTAL" ] || [ "$CIRCLE_NODE_TOTAL" = 0 ] && CIRCLE_NODE_TOTAL=1 +[ -z "$CIRCLE_NODE_INDEX" ] && CIRCLE_NODE_INDEX=0 +[ -z "$INDEX_SHIFT" ] && INDEX_SHIFT=0 -test "${OPTIMIZE}" = "1" && SOLTEST_ARGS+=(--optimize) -test "${ABI_ENCODER_V1}" = "1" && SOLTEST_ARGS+=(--abiencoderv1) +# Multiply by a prime number to get better spread, just in case +# long-running test cases are next to each other. +CIRCLE_NODE_INDEX=$(((CIRCLE_NODE_INDEX + 23 * INDEX_SHIFT) % CIRCLE_NODE_TOTAL)) -echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS[*]} -- ${SOLTEST_ARGS[*]}" +CPUs=3 +PIDs=() +for run in $(seq 0 $((CPUs - 1))) +do + BOOST_TEST_ARGS_RUN=( + "--color_output=no" + "--show_progress=yes" + "--logger=JUNIT,error,test_results/$(get_logfile_basename "$run").xml" + "${BOOST_TEST_ARGS[@]}" + ) + SOLTEST_ARGS=("--evm-version=$EVM" "${SOLTEST_FLAGS[@]}") -"${REPODIR}/build/test/soltest" "${BOOST_TEST_ARGS[@]}" -- "${SOLTEST_ARGS[@]}" + test "${OPTIMIZE}" = "1" && SOLTEST_ARGS+=(--optimize) + test "${ABI_ENCODER_V1}" = "1" && SOLTEST_ARGS+=(--abiencoderv1) + + BATCH_ARGS=("--batches" "$((CPUs * CIRCLE_NODE_TOTAL))" "--selected-batch" "$((CPUs * CIRCLE_NODE_INDEX + run))") + + echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS_RUN[*]} -- ${SOLTEST_ARGS[*]}" + + "${REPODIR}/build/test/soltest" -l test_suite "${BOOST_TEST_ARGS_RUN[@]}" -- "${SOLTEST_ARGS[@]}" "${BATCH_ARGS[@]}" & + PIDs+=($!) +done + +# wait for individual processes to get their exit status +for pid in ${PIDs[*]} +do + wait "$pid" +done diff --git a/.circleci/soltest_all.sh b/.circleci/soltest_all.sh index 5bd5ed9f1..3e29d1d2e 100755 --- a/.circleci/soltest_all.sh +++ b/.circleci/soltest_all.sh @@ -31,30 +31,21 @@ REPODIR="$(realpath "$(dirname "$0")"/..)" # shellcheck source=scripts/common.sh source "${REPODIR}/scripts/common.sh" -# NOTE: If you add/remove values, remember to update `parallelism` setting in CircleCI config. EVM_VALUES=(homestead byzantium constantinople petersburg istanbul berlin london) DEFAULT_EVM=london [[ " ${EVM_VALUES[*]} " =~ $DEFAULT_EVM ]] OPTIMIZE_VALUES=(0 1) -STEPS=$(( 1 + ${#EVM_VALUES[@]} * ${#OPTIMIZE_VALUES[@]} )) - -RUN_STEPS=$(circleci_select_steps "$(seq "$STEPS")") -printTask "Running steps $RUN_STEPS..." - -STEP=1 - # Run for ABI encoder v1, without SMTChecker tests. -if circleci_step_selected "$RUN_STEPS" "$STEP" -then - EVM="${DEFAULT_EVM}" \ - OPTIMIZE=1 \ - ABI_ENCODER_V1=1 \ - BOOST_TEST_ARGS="-t !smtCheckerTests" \ - "${REPODIR}/.circleci/soltest.sh" -fi -((++STEP)) +EVM="${DEFAULT_EVM}" \ +OPTIMIZE=1 \ +ABI_ENCODER_V1=1 \ +BOOST_TEST_ARGS="-t !smtCheckerTests" \ +"${REPODIR}/.circleci/soltest.sh" +# We shift the batch index so that long-running tests +# do not always run in the same executor for all EVM versions +INDEX_SHIFT=0 for OPTIMIZE in "${OPTIMIZE_VALUES[@]}" do for EVM in "${EVM_VALUES[@]}" @@ -68,16 +59,13 @@ do DISABLE_SMTCHECKER="" [ "${OPTIMIZE}" != "0" ] && DISABLE_SMTCHECKER="-t !smtCheckerTests" - if circleci_step_selected "$RUN_STEPS" "$STEP" - then - EVM="$EVM" \ - OPTIMIZE="$OPTIMIZE" \ - SOLTEST_FLAGS="$SOLTEST_FLAGS $ENFORCE_GAS_ARGS $EWASM_ARGS" \ - BOOST_TEST_ARGS="-t !@nooptions $DISABLE_SMTCHECKER" \ - "${REPODIR}/.circleci/soltest.sh" - fi - ((++STEP)) - done -done + EVM="$EVM" \ + OPTIMIZE="$OPTIMIZE" \ + SOLTEST_FLAGS="$SOLTEST_FLAGS $ENFORCE_GAS_ARGS $EWASM_ARGS" \ + BOOST_TEST_ARGS="-t !@nooptions $DISABLE_SMTCHECKER" \ + INDEX_SHIFT="$INDEX_SHIFT" \ + "${REPODIR}/.circleci/soltest.sh" -((STEP == STEPS + 1)) || assertFail "Step counter not properly adjusted!" + INDEX_SHIFT=$((INDEX_SHIFT + 1)) + done +done \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f3a03d94e..99d765980 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens include(EthOptions) configure_project(TESTS) -set(LATEST_Z3_VERSION "4.8.13") +set(LATEST_Z3_VERSION "4.8.14") set(MINIMUM_Z3_VERSION "4.8.0") find_package(Z3) if (${Z3_FOUND}) diff --git a/Changelog.md b/Changelog.md index 43ad0875e..c19b39206 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,10 +10,24 @@ Breaking changes: ### 0.8.12 (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: + * Yul Optimizer: Remove ``mstore`` and ``sstore`` operations if the slot already contains the same value. 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``. + * Control Flow Graph: Perform proper virtual lookup for modifiers for uninitialized variable and unreachable code analysis. + * Immutables: Fix wrong error when the constructor of a base contract uses ``return`` and the parent contract contains immutable variables. + * 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. Solc-Js: @@ -2161,7 +2175,7 @@ Features: * Internal: Inline assembly usable by the code generator. * Commandline interface: Using ``-`` as filename allows reading from stdin. * Interface JSON: Fallback function is now part of the ABI. - * Interface: Version string now *semver* compatible. + * Interface: Version string now *SemVer* compatible. * Code generator: Do not provide "new account gas" if we know the called account exists. Bugfixes: diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md index eaa859fe7..7bfea128a 100644 --- a/ReleaseChecklist.md +++ b/ReleaseChecklist.md @@ -54,7 +54,8 @@ - [ ] Run ``./scripts/docker_deploy_manual.sh v$VERSION``). ### PPA - - [ ] Change ``scripts/release_ppa.sh`` to match your key's email and key id. + - [ ] Make sure the ``ethereum/cpp-build-deps`` PPA repository contains libz3-static-dev builds for all current versions of ubuntu. If not run ``scripts/deps-ppa/static-z3.sh`` (after changing email address and key id and adding the missing ubuntu version) and wait for the builds to succeed before continuing. + - [ ] Change ``scripts/release_ppa.sh`` to match your key's email and key id; double-check that ``DISTRIBUTIONS`` contains the most recent versions. - [ ] Run ``scripts/release_ppa.sh v$VERSION`` to create the PPA release (you need the relevant openssl key). - [ ] Wait for the ``~ethereum/ubuntu/ethereum-static`` PPA build to be finished and published for *all platforms*. SERIOUSLY: DO NOT PROCEED EARLIER!!! *After* the static builds are *published*, copy the static package to the ``~ethereum/ubuntu/ethereum`` PPA for the destination series ``Trusty``, ``Xenial`` and ``Bionic`` while selecting ``Copy existing binaries``. diff --git a/docs/conf.py b/docs/conf.py index 5fa2e6ab5..10aa406f2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -63,7 +63,7 @@ master_doc = 'index' # General information about the project. project = 'Solidity' -copyright = '2016-2021, Ethereum' +project_copyright = '2016-2021, Ethereum' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -73,7 +73,7 @@ copyright = '2016-2021, Ethereum' with open('../CMakeLists.txt', 'r', encoding='utf8') as f: version = re.search('PROJECT_VERSION "([^"]+)"', f.read()).group(1) # The full version, including alpha/beta/rc tags. -if os.path.isfile('../prerelease.txt') != True or os.path.getsize('../prerelease.txt') == 0: +if not os.path.isfile('../prerelease.txt') or os.path.getsize('../prerelease.txt') == 0: release = version else: # This is a prerelease version diff --git a/docs/contracts/libraries.rst b/docs/contracts/libraries.rst index 4f0f03fec..76bedba49 100644 --- a/docs/contracts/libraries.rst +++ b/docs/contracts/libraries.rst @@ -34,7 +34,8 @@ contracts (using qualified access like ``L.f()``). Of course, calls to internal functions use the internal calling convention, which means that all internal types can be passed and types :ref:`stored in memory ` will be passed by reference and not copied. -To realize this in the EVM, code of internal library functions +To realize this in the EVM, the code of internal library functions +that are called from a contract and all functions called from therein will at compile time be included in the calling contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``. diff --git a/docs/contributing.rst b/docs/contributing.rst index 4844eaae3..2895744ef 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -33,7 +33,7 @@ the team and contributors are working on, you can join our public team calls: - Mondays at 3pm CET/CEST. - Wednesdays at 2pm CET/CEST. -Both calls take place on `Jitsi `_. +Both calls take place on `Jitsi `_. How to Report Issues ==================== diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 7881a22ec..0ce7d21e3 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -699,7 +699,7 @@ safest action is to revert all changes and make the whole transaction (or at least call) without effect. In both cases, the caller can react on such failures using ``try``/``catch``, but -the changes in the caller will always be reverted. +the changes in the callee will always be reverted. .. note:: @@ -895,6 +895,6 @@ in scope in the block that follows. The error might have happened deeper down in the call chain and the called contract just forwarded it. Also, it could be due to an out-of-gas situation and not a deliberate error condition: - The caller always retains 63/64th of the gas in a call and thus + The caller always retains at least 1/64th of the gas in a call and thus even if the called contract goes out of gas, the caller still has some gas left. diff --git a/docs/examples/blind-auction.rst b/docs/examples/blind-auction.rst index ad898a9b1..b0a86f248 100644 --- a/docs/examples/blind-auction.rst +++ b/docs/examples/blind-auction.rst @@ -121,6 +121,9 @@ to receive their money - contracts cannot activate themselves. // before `send` returns. pendingReturns[msg.sender] = 0; + // msg.sender is not of type `address payable` and must be + // explicitly converted using `payable(msg.sender)` in order + // use the member function `send()`. if (!payable(msg.sender).send(amount)) { // No need to call throw here, just reset the amount owing pendingReturns[msg.sender] = amount; diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst index 3383e6195..c95d7c5e4 100644 --- a/docs/examples/micropayment.rst +++ b/docs/examples/micropayment.rst @@ -37,7 +37,7 @@ Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline. In this tutorial, we will sign messages in the browser using `web3.js `_ and -`MetaMask `_, using the method described in `EIP-762 `_, +`MetaMask `_, using the method described in `EIP-712 `_, as it provides a number of other security benefits. .. code-block:: javascript diff --git a/docs/grammar/SolidityParser.g4 b/docs/grammar/SolidityParser.g4 index f7630c59e..110773fed 100644 --- a/docs/grammar/SolidityParser.g4 +++ b/docs/grammar/SolidityParser.g4 @@ -564,7 +564,7 @@ yulFunctionDefinition: * While only identifiers without dots can be declared within inline assembly, * paths containing dots can refer to declarations outside the inline assembly block. */ -yulPath: YulIdentifier (YulPeriod YulIdentifier)*; +yulPath: YulIdentifier (YulPeriod (YulIdentifier | YulEVMBuiltin))*; /** * A call to a function with return values can only occur as right-hand side of an assignment or * a variable declaration. diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index dbfa7a4c5..438c1afa9 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -118,8 +118,17 @@ The nightly version can be installed using these commands: sudo apt-get update sudo apt-get install solc -We are also releasing a `snap package `_, which is -installable in all the `supported Linux distros `_. To +Furthermore, some Linux distributions provide their own packages. These packages are not directly +maintained by us, but usually kept up-to-date by the respective package maintainers. + +For example, Arch Linux has packages for the latest development version: + +.. code-block:: bash + + pacman -S solidity + +There is also a `snap package `_, however, it is **currently unmaintained**. +It is installable in all the `supported Linux distros `_. To install the latest stable version of solc: .. code-block:: bash @@ -139,18 +148,6 @@ with the most recent changes, please use the following: but it comes with limitations, like accessing only the files in your ``/home`` and ``/media`` directories. For more information, go to `Demystifying Snap Confinement `_. -Arch Linux also has packages, albeit limited to the latest development version: - -.. code-block:: bash - - pacman -S solidity - -Gentoo Linux has an `Ethereum overlay `_ that contains a Solidity package. -After the overlay is setup, ``solc`` can be installed in x86_64 architectures by: - -.. code-block:: bash - - emerge dev-lang/solidity macOS Packages ============== @@ -537,8 +534,8 @@ The Solidity version string contains four parts: If there are local modifications, the commit will be postfixed with ``.mod``. -These parts are combined as required by Semver, where the Solidity pre-release tag equals to the Semver pre-release -and the Solidity commit and platform combined make up the Semver build metadata. +These parts are combined as required by SemVer, where the Solidity pre-release tag equals to the SemVer pre-release +and the Solidity commit and platform combined make up the SemVer build metadata. A release example: ``0.4.8+commit.60cc1668.Emscripten.clang``. @@ -549,7 +546,7 @@ Important Information About Versioning After a release is made, the patch version level is bumped, because we assume that only patch level changes follow. When changes are merged, the version should be bumped according -to semver and the severity of the change. Finally, a release is always made with the version +to SemVer and the severity of the change. Finally, a release is always made with the version of the current nightly build, but without the ``prerelease`` specifier. Example: diff --git a/docs/internals/optimizer.rst b/docs/internals/optimizer.rst index 19bca791e..a7eff7346 100644 --- a/docs/internals/optimizer.rst +++ b/docs/internals/optimizer.rst @@ -290,6 +290,7 @@ on the individual steps and their sequence below. - :ref:`conditional-unsimplifier`. - :ref:`control-flow-simplifier`. - :ref:`dead-code-eliminator`. +- :ref:`equal-store-eliminator`. - :ref:`equivalent-function-combiner`. - :ref:`expression-joiner`. - :ref:`expression-simplifier`. @@ -938,6 +939,22 @@ we require ForLoopInitRewriter to run before this step. Prerequisite: ForLoopInitRewriter, Function Hoister, Function Grouper +.. _equal-store-eliminator: + +EqualStoreEliminator +^^^^^^^^^^^^^^^^^^^^ + +This steps removes ``mstore(k, v)`` and ``sstore(k, v)`` calls if +there was a previous call to ``mstore(k, v)`` / ``sstore(k, v)``, +no other store in between and the values of ``k`` and ``v`` did not change. + +This simple step is effective if run after the SSA transform and the +Common Subexpression Eliminator, because SSA will make sure that the variables +will not change and the Common Subexpression Eliminator re-uses exactly the same +variable if the value is known to be the same. + +Prerequisites: Disambiguator, ForLoopInitRewriter + .. _unused-pruner: UnusedPruner diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index cd031bb01..b3a27bf1e 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -13,7 +13,7 @@ and :ref:`constant variable` definitions. SPDX License Identifier ======================= -Trust in smart contract can be better established if their source code +Trust in smart contracts can be better established if their source code is available. Since making source code available always touches on legal problems with regards to copyright, the Solidity compiler encourages the use of machine-readable `SPDX license identifiers `_. diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index c026d73f2..18f3bf827 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -84,23 +84,24 @@ The example below uses ``_allowances`` to record the amount someone else is allo } function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { + require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough."); + _allowances[sender][msg.sender] -= amount; _transfer(sender, recipient, amount); - approve(sender, msg.sender, amount); return true; } - function approve(address owner, address spender, uint256 amount) public returns (bool) { - require(owner != address(0), "ERC20: approve from the zero address"); + function approve(address spender, uint256 amount) public returns (bool) { require(spender != address(0), "ERC20: approve to the zero address"); - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); + _allowances[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); return true; } function _transfer(address sender, address recipient, uint256 amount) internal { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); + require(_balances[sender] >= amount, "ERC20: Not enough funds."); _balances[sender] -= amount; _balances[recipient] += amount; diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst index cc6fe2d8a..126440321 100644 --- a/docs/types/reference-types.rst +++ b/docs/types/reference-types.rst @@ -153,7 +153,8 @@ third-party string libraries. You can also compare two strings by their keccak25 concatenate two strings using ``bytes.concat(bytes(s1), bytes(s2))``. You should use ``bytes`` over ``bytes1[]`` because it is cheaper, -since ``bytes1[]`` adds 31 padding bytes between the elements. As a general rule, +since using ``bytes1[]`` in ``memory`` adds 31 padding bytes between the elements. Note that in ``storage``, the +padding is absent due to tight packing, see :ref:`bytes and string `. As a general rule, use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 49e35d9ea..215a18cee 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -329,6 +329,10 @@ on ``call``. regardless of whether state is read from or written to, as this can have many pitfalls. Also, access to gas might change in the future. +* ``code`` and ``codehash`` + +You can query the deployed code for any smart contract. Use ``code`` to get the EVM bytecode as a string, which might be empty. Use ``codehash`` get the Keccak-256 hash of that code. + .. note:: All contracts can be converted to ``address`` type, so it is possible to query the balance of the current contract using ``address(this).balance``. diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index ff8e2a29a..3de1ecca5 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -25,19 +25,21 @@ using namespace solidity::langutil; using namespace solidity::frontend; using namespace std; -ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow): +ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow, ContractDefinition const* _contract): m_nodeContainer(_nodeContainer), m_currentNode(_functionFlow.entry), m_returnNode(_functionFlow.exit), m_revertNode(_functionFlow.revert), - m_transactionReturnNode(_functionFlow.transactionReturn) + m_transactionReturnNode(_functionFlow.transactionReturn), + m_contract(_contract) { } unique_ptr ControlFlowBuilder::createFunctionFlow( CFG::NodeContainer& _nodeContainer, - FunctionDefinition const& _function + FunctionDefinition const& _function, + ContractDefinition const* _contract ) { auto functionFlow = make_unique(); @@ -45,7 +47,7 @@ unique_ptr ControlFlowBuilder::createFunctionFlow( functionFlow->exit = _nodeContainer.newNode(); functionFlow->revert = _nodeContainer.newNode(); functionFlow->transactionReturn = _nodeContainer.newNode(); - ControlFlowBuilder builder(_nodeContainer, *functionFlow); + ControlFlowBuilder builder(_nodeContainer, *functionFlow, _contract); builder.appendControlFlow(_function); return functionFlow; @@ -297,7 +299,8 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); - m_currentNode->functionCalls.emplace_back(&_functionCall); + solAssert(!m_currentNode->functionCall); + m_currentNode->functionCall = &_functionCall; auto nextNode = newLabel(); @@ -321,8 +324,20 @@ bool ControlFlowBuilder::visit(ModifierInvocation const& _modifierInvocation) auto modifierDefinition = dynamic_cast( _modifierInvocation.name().annotation().referencedDeclaration ); - if (!modifierDefinition) return false; - if (!modifierDefinition->isImplemented()) return false; + + if (!modifierDefinition) + return false; + + VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup; + + if (requiredLookup == VirtualLookup::Virtual) + modifierDefinition = &modifierDefinition->resolveVirtual(*m_contract); + else + solAssert(requiredLookup == VirtualLookup::Static); + + if (!modifierDefinition->isImplemented()) + return false; + solAssert(!!m_returnNode, ""); m_placeholderEntry = newLabel(); @@ -355,8 +370,8 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition) } - for (auto const& modifier: _functionDefinition.modifiers()) - appendControlFlow(*modifier); + for (auto const& modifierInvocation: _functionDefinition.modifiers()) + appendControlFlow(*modifierInvocation); appendControlFlow(_functionDefinition.body()); diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h index 9cab99dd4..a150262f1 100644 --- a/libsolidity/analysis/ControlFlowBuilder.h +++ b/libsolidity/analysis/ControlFlowBuilder.h @@ -37,13 +37,15 @@ class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker public: static std::unique_ptr createFunctionFlow( CFG::NodeContainer& _nodeContainer, - FunctionDefinition const& _function + FunctionDefinition const& _function, + ContractDefinition const* _contract = nullptr ); private: explicit ControlFlowBuilder( CFG::NodeContainer& _nodeContainer, - FunctionFlow const& _functionFlow + FunctionFlow const& _functionFlow, + ContractDefinition const* _contract = nullptr ); // Visits for constructing the control flow. @@ -158,6 +160,8 @@ private: CFGNode* m_revertNode = nullptr; CFGNode* m_transactionReturnNode = nullptr; + ContractDefinition const* m_contract = nullptr; + /// The current jump destination of break Statements. CFGNode* m_breakJump = nullptr; /// The current jump destination of continue Statements. diff --git a/libsolidity/analysis/ControlFlowGraph.cpp b/libsolidity/analysis/ControlFlowGraph.cpp index f4afe0149..ca36b421c 100644 --- a/libsolidity/analysis/ControlFlowGraph.cpp +++ b/libsolidity/analysis/ControlFlowGraph.cpp @@ -44,7 +44,7 @@ bool CFG::visit(ContractDefinition const& _contract) for (FunctionDefinition const* function: contract->definedFunctions()) if (function->isImplemented()) m_functionControlFlow[{&_contract, function}] = - ControlFlowBuilder::createFunctionFlow(m_nodeContainer, *function); + ControlFlowBuilder::createFunctionFlow(m_nodeContainer, *function, &_contract); return true; } diff --git a/libsolidity/analysis/ControlFlowGraph.h b/libsolidity/analysis/ControlFlowGraph.h index 732548664..7383783fd 100644 --- a/libsolidity/analysis/ControlFlowGraph.h +++ b/libsolidity/analysis/ControlFlowGraph.h @@ -98,8 +98,8 @@ struct CFGNode std::vector entries; /// Exit nodes. All CFG nodes to which control flow may continue after this node. std::vector exits; - /// Function calls done by this node - std::vector functionCalls; + /// Function call done by this node + FunctionCall const* functionCall = nullptr; /// Variable occurrences in the node. std::vector variableOccurrences; diff --git a/libsolidity/analysis/ControlFlowRevertPruner.cpp b/libsolidity/analysis/ControlFlowRevertPruner.cpp index 6a8018355..9a44be57a 100644 --- a/libsolidity/analysis/ControlFlowRevertPruner.cpp +++ b/libsolidity/analysis/ControlFlowRevertPruner.cpp @@ -81,27 +81,27 @@ void ControlFlowRevertPruner::findRevertStates() if (_node == functionFlow.exit) foundExit = true; - for (auto const* functionCall: _node->functionCalls) + if (auto const* functionCall = _node->functionCall) { auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.contract); - if (resolvedFunction == nullptr || !resolvedFunction->isImplemented()) - continue; - - CFG::FunctionContractTuple calledFunctionTuple{ - findScopeContract(*resolvedFunction, item.contract), - resolvedFunction - }; - switch (m_functions.at(calledFunctionTuple)) + if (resolvedFunction && resolvedFunction->isImplemented()) { - case RevertState::Unknown: - wakeUp[calledFunctionTuple].insert(item); - foundUnknown = true; - return; - case RevertState::AllPathsRevert: - return; - case RevertState::HasNonRevertingPath: - break; + CFG::FunctionContractTuple calledFunctionTuple{ + findScopeContract(*resolvedFunction, item.contract), + resolvedFunction + }; + switch (m_functions.at(calledFunctionTuple)) + { + case RevertState::Unknown: + wakeUp[calledFunctionTuple].insert(item); + foundUnknown = true; + return; + case RevertState::AllPathsRevert: + return; + case RevertState::HasNonRevertingPath: + break; + } } } @@ -135,31 +135,29 @@ void ControlFlowRevertPruner::modifyFunctionFlows() FunctionFlow const& functionFlow = m_cfg.functionFlow(*item.first.function, item.first.contract); solidity::util::BreadthFirstSearch{{functionFlow.entry}}.run( [&](CFGNode* _node, auto&& _addChild) { - for (auto const* functionCall: _node->functionCalls) + if (auto const* functionCall = _node->functionCall) { auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.first.contract); - if (resolvedFunction == nullptr || !resolvedFunction->isImplemented()) - continue; + if (resolvedFunction && resolvedFunction->isImplemented()) + switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction})) + { + case RevertState::Unknown: + [[fallthrough]]; + case RevertState::AllPathsRevert: + // If the revert states of the functions do not + // change anymore, we treat all "unknown" states as + // "reverting", since they can only be caused by + // recursion. + for (CFGNode * node: _node->exits) + ranges::remove(node->entries, _node); - switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction})) - { - case RevertState::Unknown: - [[fallthrough]]; - case RevertState::AllPathsRevert: - // If the revert states of the functions do not - // change anymore, we treat all "unknown" states as - // "reverting", since they can only be caused by - // recursion. - for (CFGNode * node: _node->exits) - ranges::remove(node->entries, _node); - - _node->exits = {functionFlow.revert}; - functionFlow.revert->entries.push_back(_node); - return; - default: - break; - } + _node->exits = {functionFlow.revert}; + functionFlow.revert->entries.push_back(_node); + return; + default: + break; + } } for (CFGNode* exit: _node->exits) diff --git a/libsolidity/analysis/DeclarationTypeChecker.cpp b/libsolidity/analysis/DeclarationTypeChecker.cpp index 076155366..7a4aa9942 100644 --- a/libsolidity/analysis/DeclarationTypeChecker.cpp +++ b/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -437,16 +437,14 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable) type = TypeProvider::withLocation(ref, typeLoc, isPointer); } - if ( - _variable.isConstant() && - !dynamic_cast(type) && - type->containsNestedMapping() - ) - m_errorReporter.fatalDeclarationError( - 3530_error, - _variable.location(), - "The type contains a (nested) mapping and therefore cannot be a constant." - ); + if (_variable.isConstant() && !type->isValueType()) + { + bool allowed = false; + if (auto arrayType = dynamic_cast(type)) + allowed = arrayType->isByteArray(); + if (!allowed) + m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Only constants of value type and byte array type are implemented."); + } _variable.annotation().type = type; } diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 018c10f58..5cff34f2a 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -37,7 +38,7 @@ using namespace solidity::frontend; namespace { -void copyMissingTags(set const& _baseFunctions, StructurallyDocumentedAnnotation& _target, CallableDeclaration const* _declaration = nullptr) +void copyMissingTags(set const& _baseFunctions, StructurallyDocumentedAnnotation& _target, FunctionType const* _functionType = nullptr) { // Only copy if there is exactly one direct base function. if (_baseFunctions.size() != 1) @@ -45,12 +46,6 @@ void copyMissingTags(set const& _baseFunctions, Stru CallableDeclaration const& baseFunction = **_baseFunctions.begin(); - auto hasReturnParameter = [](CallableDeclaration const& declaration, size_t _n) - { - return declaration.returnParameterList() && - declaration.returnParameters().size() > _n; - }; - auto& sourceDoc = dynamic_cast(baseFunction.annotation()); for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();) @@ -70,21 +65,22 @@ void copyMissingTags(set const& _baseFunctions, Stru DocTag content = it->second; // Update the parameter name for @return tags - if (_declaration && tag == "return") + if (_functionType && tag == "return") { size_t docParaNameEndPos = content.content.find_first_of(" \t"); string const docParameterName = content.content.substr(0, docParaNameEndPos); if ( - hasReturnParameter(*_declaration, n) && - docParameterName != _declaration->returnParameters().at(n)->name() + _functionType->returnParameterNames().size() > n && + docParameterName != _functionType->returnParameterNames().at(n) ) { bool baseHasNoName = - hasReturnParameter(baseFunction, n) && + baseFunction.returnParameterList() && + baseFunction.returnParameters().size() > n && baseFunction.returnParameters().at(n)->name().empty(); - string paramName = _declaration->returnParameters().at(n)->name(); + string paramName = _functionType->returnParameterNames().at(n); content.content = (paramName.empty() ? "" : std::move(paramName) + " ") + ( string::npos == docParaNameEndPos || baseHasNoName ? @@ -127,7 +123,7 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) bool DocStringAnalyser::visit(FunctionDefinition const& _function) { if (!_function.isConstructor()) - handleCallable(_function, _function, _function.annotation()); + handleCallable(_function, _function, _function.annotation(), TypeProvider::function(_function)); return true; } @@ -136,10 +132,12 @@ bool DocStringAnalyser::visit(VariableDeclaration const& _variable) if (!_variable.isStateVariable() && !_variable.isFileLevelVariable()) return false; + auto const* getterType = TypeProvider::function(_variable); + if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation())) - copyMissingTags({baseFunction}, _variable.annotation()); + copyMissingTags({baseFunction}, _variable.annotation(), getterType); else if (_variable.annotation().docTags.empty()) - copyMissingTags(_variable.annotation().baseFunctions, _variable.annotation()); + copyMissingTags(_variable.annotation().baseFunctions, _variable.annotation(), getterType); return false; } @@ -168,17 +166,18 @@ bool DocStringAnalyser::visit(ErrorDefinition const& _error) void DocStringAnalyser::handleCallable( CallableDeclaration const& _callable, StructurallyDocumented const& _node, - StructurallyDocumentedAnnotation& _annotation + StructurallyDocumentedAnnotation& _annotation, + FunctionType const* _functionType ) { if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation)) - copyMissingTags({baseFunction}, _annotation, &_callable); + copyMissingTags({baseFunction}, _annotation, _functionType); else if ( _annotation.docTags.empty() && _callable.annotation().baseFunctions.size() == 1 && parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin()) ) - copyMissingTags(_callable.annotation().baseFunctions, _annotation, &_callable); + copyMissingTags(_callable.annotation().baseFunctions, _annotation, _functionType); } CallableDeclaration const* DocStringAnalyser::resolveInheritDoc( diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h index 1fa076359..d9cd6f09d 100644 --- a/libsolidity/analysis/DocStringAnalyser.h +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -54,7 +54,8 @@ private: void handleCallable( CallableDeclaration const& _callable, StructurallyDocumented const& _node, - StructurallyDocumentedAnnotation& _annotation + StructurallyDocumentedAnnotation& _annotation, + FunctionType const* _functionType = nullptr ); langutil::ErrorReporter& m_errorReporter; diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 17d5b8c3e..58ebea7b1 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -73,24 +73,24 @@ inline vector> constructMagicVariable 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("addmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, StateMutability::Pure)), + magicVarDecl("assert", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Assert, 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("blockhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, StateMutability::View)), + magicVarDecl("ecrecover", TypeProvider::function(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, StateMutability::Pure)), + magicVarDecl("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, StateMutability::View)), + magicVarDecl("keccak256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, StateMutability::Pure)), magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)), - magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), + magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, 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("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, StateMutability::Pure)), + magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, StateMutability::Pure)), + magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, 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("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, StateMutability::Pure)), + magicVarDecl("sha3", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, StateMutability::Pure)), magicVarDecl("suicide", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), magicVarDecl("tx", TypeProvider::magic(MagicType::Kind::Transaction)), // Accepts a MagicType that can be any contract type or an Integer type and returns a @@ -99,8 +99,8 @@ inline vector> constructMagicVariable strings{}, strings{}, FunctionType::Kind::MetaType, - true, - StateMutability::Pure + StateMutability::Pure, + FunctionType::Options::withArbitraryParameters() )), }; } diff --git a/libsolidity/analysis/ImmutableValidator.cpp b/libsolidity/analysis/ImmutableValidator.cpp index e28236fb6..485aa3fa2 100644 --- a/libsolidity/analysis/ImmutableValidator.cpp +++ b/libsolidity/analysis/ImmutableValidator.cpp @@ -29,7 +29,7 @@ void ImmutableValidator::analyze() { m_inCreationContext = true; - auto linearizedContracts = m_currentContract.annotation().linearizedBaseContracts | ranges::views::reverse; + auto linearizedContracts = m_mostDerivedContract.annotation().linearizedBaseContracts | ranges::views::reverse; for (ContractDefinition const* contract: linearizedContracts) for (VariableDeclaration const* stateVar: contract->stateVariables()) @@ -62,7 +62,7 @@ void ImmutableValidator::analyze() visitCallableIfNew(*modDef); } - checkAllVariablesInitialized(m_currentContract.location()); + checkAllVariablesInitialized(m_mostDerivedContract.location()); } bool ImmutableValidator::visit(Assignment const& _assignment) @@ -137,7 +137,7 @@ void ImmutableValidator::endVisit(IdentifierPath const& _identifierPath) if (auto const callableDef = dynamic_cast(_identifierPath.annotation().referencedDeclaration)) visitCallableIfNew( *_identifierPath.annotation().requiredLookup == VirtualLookup::Virtual ? - callableDef->resolveVirtual(m_currentContract) : + callableDef->resolveVirtual(m_mostDerivedContract) : *callableDef ); @@ -147,7 +147,7 @@ void ImmutableValidator::endVisit(IdentifierPath const& _identifierPath) void ImmutableValidator::endVisit(Identifier const& _identifier) { if (auto const callableDef = dynamic_cast(_identifier.annotation().referencedDeclaration)) - visitCallableIfNew(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual ? callableDef->resolveVirtual(m_currentContract) : *callableDef); + visitCallableIfNew(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual ? callableDef->resolveVirtual(m_mostDerivedContract) : *callableDef); if (auto const varDecl = dynamic_cast(_identifier.annotation().referencedDeclaration)) analyseVariableReference(*varDecl, _identifier); } @@ -160,15 +160,18 @@ void ImmutableValidator::endVisit(Return const& _return) bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDeclaration) { - FunctionDefinition const* prevConstructor = m_currentConstructor; - m_currentConstructor = nullptr; + ScopedSaveAndRestore constructorGuard{m_currentConstructor, {}}; + ScopedSaveAndRestore constructorContractGuard{m_currentConstructorContract, {}}; if (FunctionDefinition const* funcDef = dynamic_cast(&_callableDeclaration)) { ASTNode::listAccept(funcDef->modifiers(), *this); if (funcDef->isConstructor()) + { + m_currentConstructorContract = funcDef->annotation().contract; m_currentConstructor = funcDef; + } if (funcDef->isImplemented()) funcDef->body().accept(*this); @@ -177,8 +180,6 @@ bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDec if (modDef->isImplemented()) modDef->body().accept(*this); - m_currentConstructor = prevConstructor; - return false; } @@ -253,7 +254,8 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::SourceLocation const& _location) { - for (ContractDefinition const* contract: m_currentContract.annotation().linearizedBaseContracts) + for (ContractDefinition const* contract: m_mostDerivedContract.annotation().linearizedBaseContracts | ranges::views::reverse) + { for (VariableDeclaration const* varDecl: contract->stateVariables()) if (varDecl->immutable()) if (!util::contains(m_initializedStateVariables, varDecl)) @@ -263,6 +265,11 @@ void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::Source solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()), "Construction control flow ends without initializing all immutable state variables." ); + + // Don't check further than the current c'tors contract + if (contract == m_currentConstructorContract) + break; + } } void ImmutableValidator::visitCallableIfNew(Declaration const& _declaration) diff --git a/libsolidity/analysis/ImmutableValidator.h b/libsolidity/analysis/ImmutableValidator.h index e6d837efe..452e91d7c 100644 --- a/libsolidity/analysis/ImmutableValidator.h +++ b/libsolidity/analysis/ImmutableValidator.h @@ -42,7 +42,7 @@ class ImmutableValidator: private ASTConstVisitor public: ImmutableValidator(langutil::ErrorReporter& _errorReporter, ContractDefinition const& _contractDefinition): - m_currentContract(_contractDefinition), + m_mostDerivedContract(_contractDefinition), m_errorReporter(_errorReporter) { } @@ -66,7 +66,7 @@ private: void visitCallableIfNew(Declaration const& _declaration); - ContractDefinition const& m_currentContract; + ContractDefinition const& m_mostDerivedContract; CallableDeclarationSet m_visitedCallables; @@ -74,6 +74,7 @@ private: langutil::ErrorReporter& m_errorReporter; FunctionDefinition const* m_currentConstructor = nullptr; + ContractDefinition const* m_currentConstructorContract = nullptr; bool m_inLoop = false; bool m_inBranch = false; bool m_inCreationContext = true; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 601a07cc2..3e587551f 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -530,15 +530,6 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) } if (_variable.isConstant()) { - if (!varType->isValueType()) - { - bool allowed = false; - if (auto arrayType = dynamic_cast(varType)) - allowed = arrayType->isByteArray(); - if (!allowed) - m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Constants of non-value type not yet implemented."); - } - if (!_variable.value()) m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable."); else if (!*_variable.value()->annotation().isPure) @@ -2122,16 +2113,35 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa return; } - if (functionPointerType->kind() != FunctionType::Kind::External) + if ( + functionPointerType->kind() != FunctionType::Kind::External && + functionPointerType->kind() != FunctionType::Kind::Declaration + ) { - string msg = "Function must be \"public\" or \"external\"."; + string msg = "Expected regular external function type, or external view on public function."; + if (functionPointerType->kind() == FunctionType::Kind::Internal) + msg += " Provided internal function."; + else if (functionPointerType->kind() == FunctionType::Kind::DelegateCall) + msg += " Cannot use library functions for abi.encodeCall."; + else if (functionPointerType->kind() == FunctionType::Kind::Creation) + msg += " Provided creation function."; + else + msg += " Cannot use special function."; SecondarySourceLocation ssl{}; if (functionPointerType->hasDeclaration()) { ssl.append("Function is declared here:", functionPointerType->declaration().location()); - if (functionPointerType->declaration().scope() == m_currentContract) + if ( + functionPointerType->declaration().visibility() == Visibility::Public && + functionPointerType->declaration().scope() == m_currentContract + ) msg += " Did you forget to prefix \"this.\"?"; + else if (contains( + m_currentContract->annotation().linearizedBaseContracts, + functionPointerType->declaration().scope() + ) && functionPointerType->declaration().scope() != m_currentContract) + msg += " Functions from base contracts have to be external."; } m_errorReporter.typeError(3509_error, arguments[0]->location(), ssl, msg); @@ -2866,7 +2876,6 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) strings(1, ""), strings(1, ""), FunctionType::Kind::ObjectCreation, - false, StateMutability::Pure ); _newExpression.annotation().isPure = true; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index c36aee5b3..f2b7369b7 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -511,23 +511,20 @@ ModifierDefinition const& ModifierDefinition::resolveVirtual( ContractDefinition const* _searchStart ) const { + // Super is not possible with modifiers solAssert(_searchStart == nullptr, "Used super in connection with modifiers."); - // If we are not doing super-lookup and the modifier is not virtual, we can stop here. - if (_searchStart == nullptr && !virtualSemantics()) + // The modifier is not virtual, we can stop here. + if (!virtualSemantics()) return *this; solAssert(!dynamic_cast(*scope()).isLibrary(), ""); for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts) - { - if (_searchStart != nullptr && c != _searchStart) - continue; - _searchStart = nullptr; for (ModifierDefinition const* modifier: c->functionModifiers()) if (modifier->name() == name()) return *modifier; - } + solAssert(false, "Virtual modifier " + name() + " not found."); return *this; // not reached } diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 6183493b4..97d230d2e 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -445,13 +445,18 @@ FunctionType const* TypeProvider::function( strings const& _parameterTypes, strings const& _returnParameterTypes, FunctionType::Kind _kind, - bool _arbitraryParameters, - StateMutability _stateMutability + StateMutability _stateMutability, + FunctionType::Options _options ) { + // Can only use this constructor for "arbitraryParameters". + solAssert(!_options.valueSet && !_options.gasSet && !_options.saltSet && !_options.bound); return createAndGet( - _parameterTypes, _returnParameterTypes, - _kind, _arbitraryParameters, _stateMutability + _parameterTypes, + _returnParameterTypes, + _kind, + _stateMutability, + std::move(_options) ); } @@ -461,13 +466,9 @@ FunctionType const* TypeProvider::function( strings _parameterNames, strings _returnParameterNames, FunctionType::Kind _kind, - bool _arbitraryParameters, StateMutability _stateMutability, Declaration const* _declaration, - bool _gasSet, - bool _valueSet, - bool _bound, - bool _saltSet + FunctionType::Options _options ) { return createAndGet( @@ -476,13 +477,9 @@ FunctionType const* TypeProvider::function( _parameterNames, _returnParameterNames, _kind, - _arbitraryParameters, _stateMutability, _declaration, - _gasSet, - _valueSet, - _bound, - _saltSet + std::move(_options) ); } diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index 78b8378ca..b089ff1ef 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -149,8 +149,8 @@ public: strings const& _parameterTypes, strings const& _returnParameterTypes, FunctionType::Kind _kind = FunctionType::Kind::Internal, - bool _arbitraryParameters = false, - StateMutability _stateMutability = StateMutability::NonPayable + StateMutability _stateMutability = StateMutability::NonPayable, + FunctionType::Options _options = {} ); /// @returns a highly customized FunctionType, use with care. @@ -160,13 +160,9 @@ public: 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, - bool _saltSet = false + FunctionType::Options _options = {} ); /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3bad6d24e..1c9448317 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -470,15 +470,15 @@ MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const {"balance", TypeProvider::uint256()}, {"code", TypeProvider::array(DataLocation::Memory)}, {"codehash", TypeProvider::fixedBytes(32)}, - {"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)} + {"call", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, StateMutability::Payable)}, + {"callcode", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, StateMutability::Payable)}, + {"delegatecall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, StateMutability::NonPayable)}, + {"staticcall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, StateMutability::View)} }; if (m_stateMutability == StateMutability::Payable) { - 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)}); + members.emplace_back(MemberList::Member{"send", TypeProvider::function(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send, StateMutability::NonPayable)}); + members.emplace_back(MemberList::Member{"transfer", TypeProvider::function(strings{"uint"}, strings(), FunctionType::Kind::Transfer, StateMutability::NonPayable)}); } return members; } @@ -2852,7 +2852,6 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c parameterNames, strings{""}, Kind::Creation, - false, stateMutability ); } @@ -2946,11 +2945,11 @@ string FunctionType::richIdentifier() const } id += "_" + stateMutabilityToString(m_stateMutability); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); - if (m_gasSet) + if (gasSet()) id += "gas"; - if (m_valueSet) + if (valueSet()) id += "value"; - if (m_saltSet) + if (saltSet()) id += "salt"; if (bound()) id += "bound_to" + identifierList(selfType()); @@ -3023,8 +3022,18 @@ TypeResult FunctionType::binaryOperatorResult(Token _operator, Type const* _othe if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) return nullptr; FunctionType const& other = dynamic_cast(*_other); - if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1) + if (kind() == Kind::Internal && sizeOnStack() == 1 && other.kind() == Kind::Internal && other.sizeOnStack() == 1) return commonType(this, _other); + else if ( + kind() == Kind::External && + sizeOnStack() == 2 && + !bound() && + other.kind() == Kind::External && + other.sizeOnStack() == 2 && + !other.bound() + ) + return commonType(this, _other); + return nullptr; } @@ -3098,11 +3107,11 @@ bool FunctionType::nameable() const { return (m_kind == Kind::Internal || m_kind == Kind::External) && - !m_bound && - !m_arbitraryParameters && - !m_gasSet && - !m_valueSet && - !m_saltSet; + !bound() && + !takesArbitraryParameters() && + !gasSet() && + !valueSet() && + !saltSet(); } vector> FunctionType::makeStackItems() const @@ -3144,11 +3153,11 @@ vector> FunctionType::makeStackItems() const break; } - if (m_gasSet) + if (gasSet()) slots.emplace_back("gas", TypeProvider::uint256()); - if (m_valueSet) + if (valueSet()) slots.emplace_back("value", TypeProvider::uint256()); - if (m_saltSet) + if (saltSet()) slots.emplace_back("salt", TypeProvider::fixedBytes(32)); if (bound()) slots.emplace_back("self", m_parameterTypes.front()); @@ -3180,13 +3189,13 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const if (variable && retParamTypes.get().empty()) return FunctionTypePointer(); + solAssert(!takesArbitraryParameters()); return TypeProvider::function( paramTypes, retParamTypes, m_parameterNames, m_returnParameterNames, m_kind, - m_arbitraryParameters, m_stateMutability, m_declaration ); @@ -3241,12 +3250,9 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const strings(1, ""), strings(1, ""), Kind::SetValue, - false, StateMutability::Pure, nullptr, - m_gasSet, - m_valueSet, - m_saltSet + Options::fromFunctionType(*this) ) ); } @@ -3259,12 +3265,9 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const strings(1, ""), strings(1, ""), Kind::SetGas, - false, StateMutability::Pure, nullptr, - m_gasSet, - m_valueSet, - m_saltSet + Options::fromFunctionType(*this) ) ); return members; @@ -3292,7 +3295,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const Type const* FunctionType::encodingType() const { - if (m_gasSet || m_valueSet) + if (gasSet() || valueSet()) return nullptr; // Only external functions can be encoded, internal functions cannot leave code boundaries. if (m_kind == Kind::External) @@ -3311,7 +3314,7 @@ TypeResult FunctionType::interfaceType(bool /*_inLibrary*/) const Type const* FunctionType::mobileType() const { - if (m_valueSet || m_gasSet || m_saltSet || m_bound) + if (valueSet() || gasSet() || saltSet() || bound()) return nullptr; // return function without parameter names @@ -3321,13 +3324,9 @@ Type const* FunctionType::mobileType() const strings(m_parameterTypes.size()), strings(m_returnParameterNames.size()), m_kind, - m_arbitraryParameters, m_stateMutability, m_declaration, - m_gasSet, - m_valueSet, - m_bound, - m_saltSet + Options::fromFunctionType(*this) ); } @@ -3413,7 +3412,7 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con return false; //@todo this is ugly, but cannot be prevented right now - if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet || m_saltSet != _other.m_saltSet) + if (gasSet() != _other.gasSet() || valueSet() != _other.valueSet() || saltSet() != _other.saltSet()) return false; if (bound() != _other.bound()) @@ -3524,41 +3523,39 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) Type const* FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const { solAssert(m_kind != Kind::Declaration, ""); + Options options = Options::fromFunctionType(*this); + if (_setGas) options.gasSet = true; + if (_setValue) options.valueSet = true; + if (_setSalt) options.saltSet = true; return TypeProvider::function( m_parameterTypes, m_returnParameterTypes, m_parameterNames, m_returnParameterNames, m_kind, - m_arbitraryParameters, m_stateMutability, m_declaration, - m_gasSet || _setGas, - m_valueSet || _setValue, - m_saltSet || _setSalt, - m_bound + options ); } FunctionTypePointer FunctionType::asBoundFunction() const { solAssert(!m_parameterTypes.empty(), ""); - solAssert(!m_gasSet, ""); - solAssert(!m_valueSet, ""); - solAssert(!m_saltSet, ""); + solAssert(!gasSet(), ""); + solAssert(!valueSet(), ""); + solAssert(!saltSet(), ""); + Options options = Options::fromFunctionType(*this); + options.bound = true; return TypeProvider::function( m_parameterTypes, m_returnParameterTypes, m_parameterNames, m_returnParameterNames, m_kind, - m_arbitraryParameters, m_stateMutability, m_declaration, - m_gasSet, - m_valueSet, - m_saltSet, - true + options ); } @@ -3596,13 +3593,9 @@ FunctionTypePointer FunctionType::asExternallyCallableFunction(bool _inLibrary) m_parameterNames, m_returnParameterNames, kind, - m_arbitraryParameters, m_stateMutability, m_declaration, - m_gasSet, - m_valueSet, - m_saltSet, - m_bound + Options::fromFunctionType(*this) ); } @@ -3807,7 +3800,6 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons strings{string{}}, strings{string{}}, FunctionType::Kind::Wrap, - false, /*_arbitraryParameters */ StateMutability::Pure ) ); @@ -3819,7 +3811,6 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons strings{string{}}, strings{string{}}, FunctionType::Kind::Unwrap, - false, /* _arbitraryParameters */ StateMutability::Pure ) ); @@ -3834,8 +3825,9 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons strings{}, strings{string()}, FunctionType::Kind::BytesConcat, - /* _arbitraryParameters */ true, - StateMutability::Pure + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() )); return members; @@ -3958,7 +3950,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const return MemberList::MemberMap({ {"coinbase", TypeProvider::payableAddress()}, {"timestamp", TypeProvider::uint256()}, - {"blockhash", TypeProvider::function(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, + {"blockhash", TypeProvider::function(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, StateMutability::View)}, {"difficulty", TypeProvider::uint256()}, {"number", TypeProvider::uint256()}, {"gaslimit", TypeProvider::uint256()}, @@ -3986,8 +3978,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const strings{}, strings{1, ""}, FunctionType::Kind::ABIEncode, - true, - StateMutability::Pure + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() )}, {"encodePacked", TypeProvider::function( TypePointers{}, @@ -3995,8 +3988,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const strings{}, strings{1, ""}, FunctionType::Kind::ABIEncodePacked, - true, - StateMutability::Pure + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() )}, {"encodeWithSelector", TypeProvider::function( TypePointers{TypeProvider::fixedBytes(4)}, @@ -4004,8 +3998,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const strings{1, ""}, strings{1, ""}, FunctionType::Kind::ABIEncodeWithSelector, - true, - StateMutability::Pure + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() )}, {"encodeCall", TypeProvider::function( TypePointers{}, @@ -4013,8 +4008,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const strings{}, strings{1, ""}, FunctionType::Kind::ABIEncodeCall, - true, - StateMutability::Pure + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() )}, {"encodeWithSignature", TypeProvider::function( TypePointers{TypeProvider::array(DataLocation::Memory, true)}, @@ -4022,8 +4018,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const strings{1, ""}, strings{1, ""}, FunctionType::Kind::ABIEncodeWithSignature, - true, - StateMutability::Pure + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() )}, {"decode", TypeProvider::function( TypePointers(), @@ -4031,8 +4028,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const strings{}, strings{}, FunctionType::Kind::ABIDecode, - true, - StateMutability::Pure + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() )} }); case Kind::MetaType: diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 8b3826ca0..f15da58ab 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1110,11 +1110,7 @@ public: u256 storageSize() const override { return underlyingType().storageSize(); } unsigned storageBytes() const override { return underlyingType().storageBytes(); } - bool isValueType() const override - { - solAssert(underlyingType().isValueType(), ""); - return true; - } + bool isValueType() const override { return true; } bool nameable() const override { solAssert(underlyingType().nameable(), ""); @@ -1247,6 +1243,38 @@ public: /// Cannot be called. Declaration, }; + struct Options + { + /// true iff the function takes an arbitrary number of arguments of arbitrary types + bool arbitraryParameters = false; + /// true iff the gas value to be used is on the stack + bool gasSet = false; + /// true iff the value to be sent is on the stack + bool valueSet = false; + /// iff the salt value (for create2) to be used is on the stack + bool saltSet = false; + /// true iff the function is called as arg1.fun(arg2, ..., argn). + /// This is achieved through the "using for" directive. + bool bound = false; + + static Options withArbitraryParameters() + { + Options result; + result.arbitraryParameters = true; + return result; + } + static Options fromFunctionType(FunctionType const& _type) + { + Options result; + result.arbitraryParameters = _type.takesArbitraryParameters(); + result.gasSet = _type.gasSet(); + result.valueSet = _type.valueSet(); + result.saltSet = _type.saltSet(); + result.bound = _type.bound(); + return result; + } + }; + /// Creates the type of a function. /// @arg _kind must be Kind::Internal, Kind::External or Kind::Declaration. @@ -1263,18 +1291,21 @@ public: strings const& _parameterTypes, strings const& _returnParameterTypes, Kind _kind, - bool _arbitraryParameters = false, - StateMutability _stateMutability = StateMutability::NonPayable + StateMutability _stateMutability = StateMutability::NonPayable, + Options _options = Options{false, false, false, false, false} ): FunctionType( parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes), strings(_parameterTypes.size(), ""), strings(_returnParameterTypes.size(), ""), _kind, - _arbitraryParameters, - _stateMutability + _stateMutability, + nullptr, + std::move(_options) ) { + // In this constructor, only the "arbitrary Parameters" option should be used. + solAssert(!bound() && !gasSet() && !valueSet() && !saltSet()); } /// Detailed constructor, use with care. @@ -1284,13 +1315,9 @@ public: strings _parameterNames = strings(), strings _returnParameterNames = strings(), Kind _kind = Kind::Internal, - bool _arbitraryParameters = false, StateMutability _stateMutability = StateMutability::NonPayable, Declaration const* _declaration = nullptr, - bool _gasSet = false, - bool _valueSet = false, - bool _saltSet = false, - bool _bound = false + Options _options = Options{false, false, false, false, false} ): m_parameterTypes(std::move(_parameterTypes)), m_returnParameterTypes(std::move(_returnParameterTypes)), @@ -1298,12 +1325,8 @@ public: m_returnParameterNames(std::move(_returnParameterNames)), m_kind(_kind), m_stateMutability(_stateMutability), - m_arbitraryParameters(_arbitraryParameters), - m_gasSet(_gasSet), - m_valueSet(_valueSet), - m_bound(_bound), m_declaration(_declaration), - m_saltSet(_saltSet) + m_options(std::move(_options)) { solAssert( m_parameterNames.size() == m_parameterTypes.size(), @@ -1314,7 +1337,7 @@ public: "Return parameter names list must match return parameter types list!" ); solAssert( - !m_bound || !m_parameterTypes.empty(), + !bound() || !m_parameterTypes.empty(), "Attempted construction of bound function without self type" ); } @@ -1408,7 +1431,7 @@ public: /// The only functions that do not pad are hash functions, the low-level call functions /// and abi.encodePacked. bool padArguments() const; - bool takesArbitraryParameters() const { return m_arbitraryParameters; } + bool takesArbitraryParameters() const { return m_options.arbitraryParameters; } /// true iff the function takes a single bytes parameter and it is passed on without padding. bool takesSinglePackedBytesParameter() const { @@ -1427,10 +1450,10 @@ public: } } - bool gasSet() const { return m_gasSet; } - bool valueSet() const { return m_valueSet; } - bool saltSet() const { return m_saltSet; } - bool bound() const { return m_bound; } + bool gasSet() const { return m_options.gasSet; } + bool valueSet() const { return m_options.valueSet; } + bool saltSet() const { return m_options.saltSet; } + bool bound() const { return m_options.bound; } /// @returns a copy of this type, where gas or value are set manually. This will never set one /// of the parameters to false. @@ -1458,15 +1481,8 @@ private: std::vector m_returnParameterNames; Kind const m_kind; StateMutability m_stateMutability = StateMutability::NonPayable; - /// true if the function takes an arbitrary number of arguments of arbitrary types - bool const m_arbitraryParameters = false; - bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack - bool const m_valueSet = false; ///< true iff the value to be sent is on the stack - /// true iff the function is called as arg1.fun(arg2, ..., argn). - /// This is achieved through the "using for" directive. - bool const m_bound = false; Declaration const* m_declaration = nullptr; - bool m_saltSet = false; ///< true iff the salt value to be used is on the stack + Options const m_options; }; /** diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 5e49868a3..1876b6ac0 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -54,7 +54,7 @@ static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPoi void CompilerUtils::initialiseFreeMemoryPointer() { size_t reservedMemory = m_context.reservedMemory(); - solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, ""); + solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63); m_context << (u256(generalPurposeMemoryStart) + reservedMemory); storeFreeMemoryPointer(); } @@ -92,7 +92,7 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer() void CompilerUtils::revertWithStringData(Type const& _argumentType) { - solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory")), ""); + solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory"))); fetchFreeMemoryPointer(); m_context << util::selectorFromSignature("Error(string)"); m_context << Instruction::DUP2 << Instruction::MSTORE; @@ -173,9 +173,9 @@ void CompilerUtils::loadFromMemoryDynamic( if (auto arrayType = dynamic_cast(&_type)) { - solAssert(!arrayType->isDynamicallySized(), ""); - solAssert(!_fromCalldata, ""); - solAssert(_padToWordBoundaries, ""); + solAssert(!arrayType->isDynamicallySized()); + solAssert(!_fromCalldata); + solAssert(_padToWordBoundaries); if (_keepUpdatedMemoryOffset) m_context << arrayType->memoryDataSize() << Instruction::ADD; } @@ -251,7 +251,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem // Use the new Yul-based decoding function auto stackHeightBefore = m_context.stackHeight(); abiDecodeV2(_typeParameters, _fromMemory); - solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, ""); + solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2); return; } @@ -290,7 +290,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem ); // @todo If base type is an array or struct, it is still calldata-style encoded, so // we would have to convert it like below. - solAssert(arrayType.location() == DataLocation::Memory, ""); + solAssert(arrayType.location() == DataLocation::Memory); if (arrayType.isDynamicallySized()) { // compute data pointer @@ -430,7 +430,7 @@ void CompilerUtils::encodeToMemory( // stack: ... bool const encoderV2 = m_context.useABICoderV2(); TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; - solAssert(targetTypes.size() == _givenTypes.size(), ""); + solAssert(targetTypes.size() == _givenTypes.size()); for (Type const*& t: targetTypes) { Type const* tEncoding = t->fullEncodingType(_encodeAsLibraryTypes, encoderV2, !_padToWordBoundaries); @@ -449,7 +449,7 @@ void CompilerUtils::encodeToMemory( ); auto stackHeightBefore = m_context.stackHeight(); abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes, _padToWordBoundaries); - solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), ""); + solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes)); return; } @@ -489,8 +489,8 @@ void CompilerUtils::encodeToMemory( { // special case: convert storage reference type to value type - this is only // possible for library calls where we just forward the storage reference - solAssert(_encodeAsLibraryTypes, ""); - solAssert(_givenTypes[i]->sizeOnStack() == 1, ""); + solAssert(_encodeAsLibraryTypes); + solAssert(_givenTypes[i]->sizeOnStack() == 1); } else if ( _givenTypes[i]->dataStoredIn(DataLocation::Storage) || @@ -638,7 +638,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) { if (_type.baseType()->hasSimpleZeroValueInMemory()) { - solAssert(_type.baseType()->isValueType(), ""); + solAssert(_type.baseType()->isValueType()); Whiskers templ(R"({ let size := mul(length, ) // cheap way of zero-initializing a memory range @@ -774,9 +774,9 @@ void CompilerUtils::convertType( if (stackTypeCategory == Type::Category::UserDefinedValueType) { - solAssert(_cleanupNeeded, ""); + solAssert(_cleanupNeeded); auto& userDefined = dynamic_cast(_typeOnStack); - solAssert(_typeOnStack == _targetType || _targetType == userDefined.underlyingType(), ""); + solAssert(_typeOnStack == _targetType || _targetType == userDefined.underlyingType()); return convertType( userDefined.underlyingType(), _targetType, @@ -787,9 +787,9 @@ void CompilerUtils::convertType( } if (targetTypeCategory == Type::Category::UserDefinedValueType) { - solAssert(_cleanupNeeded, ""); + solAssert(_cleanupNeeded); auto& userDefined = dynamic_cast(_targetType); - solAssert(_typeOnStack.isImplicitlyConvertibleTo(userDefined.underlyingType()), ""); + solAssert(_typeOnStack.isImplicitlyConvertibleTo(userDefined.underlyingType())); return convertType( _typeOnStack, userDefined.underlyingType(), @@ -829,7 +829,7 @@ void CompilerUtils::convertType( } else if (targetTypeCategory == Type::Category::Address) { - solAssert(typeOnStack.numBytes() * 8 == 160, ""); + solAssert(typeOnStack.numBytes() * 8 == 160); rightShiftNumberOnStack(256 - 160); } else @@ -849,7 +849,7 @@ void CompilerUtils::convertType( break; } case Type::Category::Enum: - solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); + solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer); if (enumOverflowCheckPending) { EnumType const& enumType = dynamic_cast(_typeOnStack); @@ -885,13 +885,13 @@ void CompilerUtils::convertType( cleanHigherOrderBits(*typeOnStack); } else if (stackTypeCategory == Type::Category::Address) - solAssert(targetBytesType.numBytes() * 8 == 160, ""); + solAssert(targetBytesType.numBytes() * 8 == 160); leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8); } else if (targetTypeCategory == Type::Category::Enum) { solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested."); - solAssert(_typeOnStack.mobileType(), ""); + solAssert(_typeOnStack.mobileType()); // just clean convertType(_typeOnStack, *_typeOnStack.mobileType(), true); EnumType const& enumType = dynamic_cast(_targetType); @@ -964,13 +964,13 @@ void CompilerUtils::convertType( if (targetTypeCategory == Type::Category::FixedBytes) { unsigned const numBytes = dynamic_cast(_targetType).numBytes(); - solAssert(data.size() <= 32, ""); + solAssert(data.size() <= 32); m_context << (u256(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes)))); } else if (targetTypeCategory == Type::Category::Array) { auto const& arrayType = dynamic_cast(_targetType); - solAssert(arrayType.isByteArray(), ""); + solAssert(arrayType.isByteArray()); size_t storageSize = 32 + ((data.size() + 31) / 32) * 32; allocateMemory(storageSize); // stack: mempos @@ -995,10 +995,10 @@ void CompilerUtils::convertType( typeOnStack.isByteArray() && !typeOnStack.isString(), "Array types other than bytes not convertible to bytesNN." ); - solAssert(typeOnStack.isDynamicallySized(), ""); + solAssert(typeOnStack.isDynamicallySized()); bool fromCalldata = typeOnStack.dataStoredIn(DataLocation::CallData); - solAssert(typeOnStack.sizeOnStack() == (fromCalldata ? 2 : 1), ""); + solAssert(typeOnStack.sizeOnStack() == (fromCalldata ? 2 : 1)); if (fromCalldata) m_context << Instruction::SWAP1; @@ -1012,7 +1012,7 @@ void CompilerUtils::convertType( ); break; } - solAssert(targetTypeCategory == stackTypeCategory, ""); + solAssert(targetTypeCategory == stackTypeCategory); auto const& targetType = dynamic_cast(_targetType); switch (targetType.location()) { @@ -1034,9 +1034,9 @@ void CompilerUtils::convertType( typeOnStack.baseType()->isDynamicallyEncoded() ) { - solAssert(m_context.useABICoderV2(), ""); + solAssert(m_context.useABICoderV2()); // stack: offset length(optional in case of dynamically sized array) - solAssert(typeOnStack.sizeOnStack() == (typeOnStack.isDynamicallySized() ? 2 : 1), ""); + solAssert(typeOnStack.sizeOnStack() == (typeOnStack.isDynamicallySized() ? 2 : 1)); if (typeOnStack.isDynamicallySized()) m_context << Instruction::SWAP1; @@ -1122,9 +1122,9 @@ void CompilerUtils::convertType( typeOnStack.arrayType().isByteArray() && !typeOnStack.arrayType().isString(), "Array types other than bytes not convertible to bytesNN." ); - solAssert(typeOnStack.isDynamicallySized(), ""); - solAssert(typeOnStack.dataStoredIn(DataLocation::CallData), ""); - solAssert(typeOnStack.sizeOnStack() == 2, ""); + solAssert(typeOnStack.isDynamicallySized()); + solAssert(typeOnStack.dataStoredIn(DataLocation::CallData)); + solAssert(typeOnStack.sizeOnStack() == 2); m_context << Instruction::SWAP1; m_context.callYulFunction( @@ -1138,14 +1138,16 @@ void CompilerUtils::convertType( break; } - solAssert(_targetType.category() == Type::Category::Array, ""); + solAssert(_targetType.category() == Type::Category::Array); auto const& targetArrayType = dynamic_cast(_targetType); - solAssert(typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType), ""); + solAssert( + typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType) || + (typeOnStack.arrayType().isByteArray() && targetArrayType.isByteArray()) + ); solAssert( typeOnStack.arrayType().dataStoredIn(DataLocation::CallData) && typeOnStack.arrayType().isDynamicallySized() && - !typeOnStack.arrayType().baseType()->isDynamicallyEncoded(), - "" + !typeOnStack.arrayType().baseType()->isDynamicallyEncoded() ); if (!_targetType.dataStoredIn(DataLocation::CallData)) return convertType(typeOnStack.arrayType(), _targetType); @@ -1153,7 +1155,7 @@ void CompilerUtils::convertType( } case Type::Category::Struct: { - solAssert(targetTypeCategory == stackTypeCategory, ""); + solAssert(targetTypeCategory == stackTypeCategory); auto& targetType = dynamic_cast(_targetType); auto& typeOnStack = dynamic_cast(_typeOnStack); switch (targetType.location()) @@ -1182,7 +1184,7 @@ void CompilerUtils::convertType( // stack: for (auto const& member: typeOnStack->members(nullptr)) { - solAssert(!member.type->containsNestedMapping(), ""); + solAssert(!member.type->containsNestedMapping()); pair const& offsets = typeOnStack->storageOffsetsOfMember(member.name); _context << offsets.first << Instruction::DUP3 << Instruction::ADD; _context << u256(offsets.second); @@ -1209,7 +1211,7 @@ void CompilerUtils::convertType( { if (typeOnStack.isDynamicallyEncoded()) { - solAssert(m_context.useABICoderV2(), ""); + solAssert(m_context.useABICoderV2()); m_context.callYulFunction( m_context.utilFunctions().conversionFunction(typeOnStack, targetType), 1, @@ -1231,7 +1233,7 @@ void CompilerUtils::convertType( } break; case DataLocation::CallData: - solAssert(_typeOnStack == _targetType, ""); + solAssert(_typeOnStack == _targetType); // nothing to do break; } @@ -1241,7 +1243,7 @@ void CompilerUtils::convertType( { TupleType const& sourceTuple = dynamic_cast(_typeOnStack); TupleType const& targetTuple = dynamic_cast(_targetType); - solAssert(targetTuple.components().size() == sourceTuple.components().size(), ""); + solAssert(targetTuple.components().size() == sourceTuple.components().size()); unsigned depth = sourceTuple.sizeOnStack(); for (size_t i = 0; i < sourceTuple.components().size(); ++i) { @@ -1249,7 +1251,7 @@ void CompilerUtils::convertType( Type const* targetType = targetTuple.components()[i]; if (!sourceType) { - solAssert(!targetType, ""); + solAssert(!targetType); continue; } unsigned sourceSize = sourceType->sizeOnStack(); @@ -1291,7 +1293,7 @@ void CompilerUtils::convertType( break; default: // we used to allow conversions from function to address - solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address), ""); + solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address)); if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function) { FunctionType const& typeOnStack = dynamic_cast(_typeOnStack); @@ -1348,14 +1350,14 @@ void CompilerUtils::pushZeroValue(Type const& _type) } if (referenceType->location() == DataLocation::CallData) { - solAssert(referenceType->sizeOnStack() == 1 || referenceType->sizeOnStack() == 2, ""); + solAssert(referenceType->sizeOnStack() == 1 || referenceType->sizeOnStack() == 2); m_context << Instruction::CALLDATASIZE; if (referenceType->sizeOnStack() == 2) m_context << 0; return; } - solAssert(referenceType->location() == DataLocation::Memory, ""); + solAssert(referenceType->location() == DataLocation::Memory); if (auto arrayType = dynamic_cast(&_type)) if (arrayType->isDynamicallySized()) { @@ -1383,7 +1385,7 @@ void CompilerUtils::pushZeroValue(Type const& _type) } else if (auto arrayType = dynamic_cast(type)) { - solAssert(!arrayType->isDynamicallySized(), ""); + solAssert(!arrayType->isDynamicallySized()); if (arrayType->length() > 0) { _context << arrayType->length() << Instruction::SWAP1; @@ -1483,7 +1485,7 @@ void CompilerUtils::popStackSlots(size_t _amount) void CompilerUtils::popAndJump(unsigned _toHeight, evmasm::AssemblyItem const& _jumpTo) { - solAssert(m_context.stackHeight() >= _toHeight, ""); + solAssert(m_context.stackHeight() >= _toHeight); unsigned amount = m_context.stackHeight() - _toHeight; popStackSlots(amount); m_context.appendJumpTo(_jumpTo); @@ -1551,7 +1553,7 @@ void CompilerUtils::storeStringData(bytesConstRef _data) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords) { - solAssert(_type.isValueType(), ""); + solAssert(_type.isValueType()); Type const* type = &_type; if (auto const* userDefined = dynamic_cast(type)) type = &userDefined->underlyingType(); @@ -1603,7 +1605,7 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) void CompilerUtils::leftShiftNumberOnStack(unsigned _bits) { - solAssert(_bits < 256, ""); + solAssert(_bits < 256); if (m_context.evmVersion().hasBitwiseShifting()) m_context << _bits << Instruction::SHL; else @@ -1612,7 +1614,7 @@ void CompilerUtils::leftShiftNumberOnStack(unsigned _bits) void CompilerUtils::rightShiftNumberOnStack(unsigned _bits) { - solAssert(_bits < 256, ""); + solAssert(_bits < 256); // NOTE: If we add signed right shift, SAR rounds differently than SDIV if (m_context.evmVersion().hasBitwiseShifting()) m_context << _bits << Instruction::SHR; @@ -1627,7 +1629,7 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords, "Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")." ); - solAssert(!_type.isDynamicallyEncoded(), ""); + solAssert(!_type.isDynamicallyEncoded()); unsigned numBytes = _type.calldataEncodedSize(_padToWords); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 5a633d2e6..a76d1f3d9 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -780,6 +780,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case FunctionType::Kind::Send: case FunctionType::Kind::Transfer: + { _functionCall.expression().accept(*this); // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. @@ -788,6 +789,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // gas <- gas * !value m_context << Instruction::SWAP1 << Instruction::DUP2; m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; + FunctionType::Options callOptions; + callOptions.valueSet = true; + callOptions.gasSet = true; appendExternalFunctionCall( FunctionType( TypePointers{}, @@ -795,11 +799,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) strings(), strings(), FunctionType::Kind::BareCall, - false, StateMutability::NonPayable, nullptr, - true, - true + callOptions ), {}, false @@ -812,6 +814,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context.appendConditionalRevert(true); } break; + } case FunctionType::Kind::Selfdestruct: acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); m_context << Instruction::SELFDESTRUCT; @@ -1255,7 +1258,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) auto const functionPtr = dynamic_cast(arguments[0]->annotation().type); solAssert(functionPtr); - solAssert(functionPtr->sizeOnStack() == 2); // Account for tuples with one component which become that component if (auto const tupleType = dynamic_cast(arguments[1]->annotation().type)) @@ -1330,9 +1332,20 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } else if (function.kind() == FunctionType::Kind::ABIEncodeCall) { - // stack: - // Extract selector from the stack - m_context << Instruction::SWAP1 << Instruction::POP; + auto const& funType = dynamic_cast(*selectorType); + if (funType.kind() == FunctionType::Kind::Declaration) + { + solAssert(funType.hasDeclaration()); + solAssert(selectorType->sizeOnStack() == 0); + m_context << funType.externalIdentifier(); + } + else + { + solAssert(selectorType->sizeOnStack() == 2); + // stack: + // Extract selector from the stack + m_context << Instruction::SWAP1 << Instruction::POP; + } // Conversion will be done below dataOnStack = TypeProvider::uint(32); } @@ -1747,6 +1760,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case Type::Category::Function: if (member == "selector") { + auto const& functionType = dynamic_cast(*_memberAccess.expression().annotation().type); + if (functionType.kind() == FunctionType::Kind::External) + CompilerUtils(m_context).popStackSlots(functionType.sizeOnStack() - 2); m_context << Instruction::SWAP1 << Instruction::POP; /// need to store it as bytes4 utils().leftShiftNumberOnStack(224); @@ -1755,8 +1771,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) { auto const& functionType = dynamic_cast(*_memberAccess.expression().annotation().type); solAssert(functionType.kind() == FunctionType::Kind::External, ""); - // stack:
- m_context << Instruction::POP; + CompilerUtils(m_context).popStackSlots(functionType.sizeOnStack() - 1); } else solAssert( @@ -2265,12 +2280,29 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO void ExpressionCompiler::appendCompareOperatorCode(Token _operator, Type const& _type) { - solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types."); if (_operator == Token::Equal || _operator == Token::NotEqual) { - if (FunctionType const* funType = dynamic_cast(&_type)) + FunctionType const* functionType = dynamic_cast(&_type); + if (functionType && functionType->kind() == FunctionType::Kind::External) { - if (funType->kind() == FunctionType::Kind::Internal) + solUnimplementedAssert(functionType->sizeOnStack() == 2, ""); + m_context << Instruction::SWAP3; + + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + m_context << Instruction::SWAP1; + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + m_context << Instruction::EQ; + m_context << Instruction::SWAP2; + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context << Instruction::SWAP1; + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context << Instruction::EQ; + m_context << Instruction::AND; + } + else + { + solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types."); + if (functionType && functionType->kind() == FunctionType::Kind::Internal) { // We have to remove the upper bits (construction time value) because they might // be "unknown" in one of the operands and not in the other. @@ -2278,13 +2310,14 @@ void ExpressionCompiler::appendCompareOperatorCode(Token _operator, Type const& m_context << Instruction::SWAP1; m_context << ((u256(1) << 32) - 1) << Instruction::AND; } + m_context << Instruction::EQ; } - m_context << Instruction::EQ; if (_operator == Token::NotEqual) m_context << Instruction::ISZERO; } else { + solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types."); bool isSigned = false; if (auto type = dynamic_cast(&_type)) isSigned = type->isSigned(); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 106ad260f..217ec14b0 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -29,6 +29,7 @@ #include #include #include +#include using namespace std; using namespace solidity; @@ -3218,15 +3219,17 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) solAssert(fromType.arrayType().isByteArray(), "Array types other than bytes not convertible to bytesNN."); return bytesToFixedBytesConversionFunction(fromType.arrayType(), dynamic_cast(_to)); } - solAssert(_to.category() == Type::Category::Array, ""); + solAssert(_to.category() == Type::Category::Array); auto const& targetType = dynamic_cast(_to); - solAssert(fromType.arrayType().isImplicitlyConvertibleTo(targetType), ""); + solAssert( + fromType.arrayType().isImplicitlyConvertibleTo(targetType) || + (fromType.arrayType().isByteArray() && targetType.isByteArray()) + ); solAssert( fromType.arrayType().dataStoredIn(DataLocation::CallData) && fromType.arrayType().isDynamicallySized() && - !fromType.arrayType().baseType()->isDynamicallyEncoded(), - "" + !fromType.arrayType().baseType()->isDynamicallyEncoded() ); if (!targetType.dataStoredIn(DataLocation::CallData)) @@ -3608,7 +3611,7 @@ string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, St auto const& [srcSlotOffset, srcOffset] = _from.storageOffsetsOfMember(structMembers[i].name); t("memberOffset", formatNumber(srcSlotOffset)); if (memberType.isValueType()) - t("read", readFromStorageValueType(memberType, srcOffset, false)); + t("read", readFromStorageValueType(memberType, srcOffset, true)); else solAssert(srcOffset == 0, ""); @@ -4548,3 +4551,31 @@ string YulUtilFunctions::externalCodeFunction() .render(); }); } + +std::string YulUtilFunctions::externalFunctionPointersEqualFunction() +{ + std::string const functionName = "externalFunctionPointersEqualFunction"; + return m_functionCollector.createFunction(functionName, [&]() { + return util::Whiskers(R"( + function ( + leftAddress, + leftSelector, + rightAddress, + rightSelector + ) -> result { + result := and( + eq( + (leftAddress), (rightAddress) + ), + eq( + (leftSelector), (rightSelector) + ) + ) + } + )") + ("functionName", functionName) + ("addressCleanUpFunction", cleanupFunction(*TypeProvider::address())) + ("selectorCleanUpFunction", cleanupFunction(*TypeProvider::uint(32))) + .render(); + }); +} diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index a03ee75c2..52d672b1f 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -522,6 +522,9 @@ public: /// Signature: (address) -> mpos std::string externalCodeFunction(); + /// @return the name of a function that that checks if two external functions pointers are equal or not + std::string externalFunctionPointersEqualFunction(); + private: /// @returns the name of a function that copies a struct from calldata or memory to storage /// signature: (slot, value) -> diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 179b3e739..420c48baa 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -95,7 +95,7 @@ struct CopyTranslate: public yul::ASTCopier return ASTCopier::translate(_identifier); yul::Expression translated = translateReference(_identifier); - solAssert(holds_alternative(translated), ""); + solAssert(holds_alternative(translated)); return get(std::move(translated)); } @@ -115,14 +115,14 @@ private: if (suffix.empty() && varDecl->isLocalVariable()) { auto const& var = m_context.localVariable(*varDecl); - solAssert(var.type().sizeOnStack() == 1, ""); + solAssert(var.type().sizeOnStack() == 1); value = var.commaSeparatedList(); } else if (varDecl->isConstant()) { VariableDeclaration const* variable = rootConstVariableDeclaration(*varDecl); - solAssert(variable, ""); + solAssert(variable); if (variable->value()->annotation().type->category() == Type::Category::RationalNumber) { @@ -130,7 +130,7 @@ private: if (auto const* bytesType = dynamic_cast(variable->type())) intValue <<= 256 - 8 * bytesType->numBytes(); else - solAssert(variable->type()->category() == Type::Category::Integer, ""); + solAssert(variable->type()->category() == Type::Category::Integer); value = intValue.str(); } else if (auto const* literal = dynamic_cast(variable->value().get())) @@ -141,20 +141,20 @@ private: { case Type::Category::Bool: case Type::Category::Address: - solAssert(type->category() == variable->annotation().type->category(), ""); + solAssert(type->category() == variable->annotation().type->category()); value = toCompactHexWithPrefix(type->literalValue(literal)); break; case Type::Category::StringLiteral: { auto const& stringLiteral = dynamic_cast(*type); - solAssert(variable->type()->category() == Type::Category::FixedBytes, ""); + solAssert(variable->type()->category() == Type::Category::FixedBytes); unsigned const numBytes = dynamic_cast(*variable->type()).numBytes(); - solAssert(stringLiteral.value().size() <= numBytes, ""); + solAssert(stringLiteral.value().size() <= numBytes); value = formatNumber(u256(h256(stringLiteral.value(), h256::AlignLeft))); break; } default: - solAssert(false, ""); + solAssert(false); } } else @@ -167,25 +167,25 @@ private: else if (suffix == "offset") value = to_string(m_context.storageLocationOfStateVariable(*varDecl).second); else - solAssert(false, ""); + solAssert(false); } else if (varDecl->type()->dataStoredIn(DataLocation::Storage)) { - solAssert(suffix == "slot" || suffix == "offset", ""); - solAssert(varDecl->isLocalVariable(), ""); + solAssert(suffix == "slot" || suffix == "offset"); + solAssert(varDecl->isLocalVariable()); if (suffix == "slot") value = IRVariable{*varDecl}.part("slot").name(); else if (varDecl->type()->isValueType()) value = IRVariable{*varDecl}.part("offset").name(); else { - solAssert(!IRVariable{*varDecl}.hasPart("offset"), ""); + solAssert(!IRVariable{*varDecl}.hasPart("offset")); value = "0"; } } else if (varDecl->type()->dataStoredIn(DataLocation::CallData)) { - solAssert(suffix == "offset" || suffix == "length", ""); + solAssert(suffix == "offset" || suffix == "length"); value = IRVariable{*varDecl}.part(suffix).name(); } else if ( @@ -193,15 +193,15 @@ private: functionType && functionType->kind() == FunctionType::Kind::External ) { - solAssert(suffix == "selector" || suffix == "address", ""); - solAssert(varDecl->type()->sizeOnStack() == 2, ""); + solAssert(suffix == "selector" || suffix == "address"); + solAssert(varDecl->type()->sizeOnStack() == 2); if (suffix == "selector") value = IRVariable{*varDecl}.part("functionSelector").name(); else value = IRVariable{*varDecl}.part("address").name(); } else - solAssert(false, ""); + solAssert(false); if (isdigit(value.front())) return yul::Literal{_identifier.debugData, yul::LiteralKind::Number, yul::YulString{value}, {}}; @@ -268,7 +268,7 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va setLocation(_varDecl); solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable."); - solAssert(!_varDecl.isConstant(), ""); + solAssert(!_varDecl.isConstant()); if (!_varDecl.value()) return; @@ -355,7 +355,7 @@ string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const templ("sourceLocationComment", dispenseLocationComment(_constant, m_context)); templ("functionName", functionName); IRGeneratorForStatements generator(m_context, m_utils); - solAssert(_constant.value(), ""); + solAssert(_constant.value()); Type const& constantType = *_constant.type(); templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList()); templ("code", generator.code()); @@ -386,7 +386,7 @@ void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _var for (size_t i = 0; i < _varDeclStatement.declarations().size(); ++i) if (auto const& decl = _varDeclStatement.declarations()[i]) { - solAssert(tupleType->components()[i], ""); + solAssert(tupleType->components()[i]); define(m_context.addLocalVariable(*decl), IRVariable(*expression).tupleComponent(i)); } } @@ -443,7 +443,7 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) TokenTraits::AssignmentToBinaryOp(assignmentOperator); if (TokenTraits::isShiftOp(binaryOperator)) - solAssert(type(_assignment.rightHandSide()).mobileType(), ""); + solAssert(type(_assignment.rightHandSide()).mobileType()); IRVariable value = type(_assignment.leftHandSide()).isValueType() ? convert( @@ -460,11 +460,11 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) if (assignmentOperator != Token::Assign) { solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types."); - solAssert(binaryOperator != Token::Exp, ""); - solAssert(type(_assignment) == type(_assignment.leftHandSide()), ""); + solAssert(binaryOperator != Token::Exp); + solAssert(type(_assignment) == type(_assignment.leftHandSide())); IRVariable leftIntermediate = readFromLValue(*m_currentLValue); - solAssert(type(_assignment) == leftIntermediate.type(), ""); + solAssert(type(_assignment) == leftIntermediate.type()); define(_assignment) << ( TokenTraits::isShiftOp(binaryOperator) ? @@ -523,14 +523,14 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) { bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo; if (willBeWrittenTo) - solAssert(!m_currentLValue, ""); + solAssert(!m_currentLValue); if (_tuple.components().size() == 1) { - solAssert(_tuple.components().front(), ""); + solAssert(_tuple.components().front()); _tuple.components().front()->accept(*this); setLocation(_tuple); if (willBeWrittenTo) - solAssert(!!m_currentLValue, ""); + solAssert(!!m_currentLValue); else define(_tuple, *_tuple.components().front()); } @@ -544,7 +544,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) setLocation(_tuple); if (willBeWrittenTo) { - solAssert(!!m_currentLValue, ""); + solAssert(!!m_currentLValue); lvalues.emplace_back(std::move(m_currentLValue)); m_currentLValue.reset(); } @@ -568,7 +568,7 @@ bool IRGeneratorForStatements::visit(Block const& _block) { if (_block.unchecked()) { - solAssert(m_context.arithmetic() == Arithmetic::Checked, ""); + solAssert(m_context.arithmetic() == Arithmetic::Checked); m_context.setArithmetic(Arithmetic::Wrapping); } return true; @@ -578,7 +578,7 @@ void IRGeneratorForStatements::endVisit(Block const& _block) { if (_block.unchecked()) { - solAssert(m_context.arithmetic() == Arithmetic::Wrapping, ""); + solAssert(m_context.arithmetic() == Arithmetic::Wrapping); m_context.setArithmetic(Arithmetic::Checked); } } @@ -607,7 +607,7 @@ bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) void IRGeneratorForStatements::endVisit(PlaceholderStatement const& _placeholder) { - solAssert(m_placeholderCallback, ""); + solAssert(m_placeholderCallback); setLocation(_placeholder); appendCode() << m_placeholderCallback(); } @@ -776,7 +776,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) { setLocation(_binOp); - solAssert(!!_binOp.annotation().commonType, ""); + solAssert(!!_binOp.annotation().commonType); Type const* commonType = _binOp.annotation().commonType; langutil::Token op = _binOp.getOperator(); @@ -799,13 +799,8 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) if (TokenTraits::isCompareOp(op)) { - if (auto type = dynamic_cast(commonType)) - { - solAssert(op == Token::Equal || op == Token::NotEqual, "Invalid function pointer comparison!"); - solAssert(type->kind() != FunctionType::Kind::External, "External function comparison not allowed!"); - } + solAssert(commonType->isValueType()); - solAssert(commonType->isValueType(), ""); bool isSigned = false; if (auto type = dynamic_cast(commonType)) isSigned = type->isSigned(); @@ -813,8 +808,25 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) string args = expressionAsType(_binOp.leftExpression(), *commonType, true); args += ", " + expressionAsType(_binOp.rightExpression(), *commonType, true); + auto functionType = dynamic_cast(commonType); + solAssert(functionType ? (op == Token::Equal || op == Token::NotEqual) : true, "Invalid function pointer comparison!"); + string expr; - if (op == Token::Equal) + + if (functionType && functionType->kind() == FunctionType::Kind::External) + { + solUnimplementedAssert(functionType->sizeOnStack() == 2, ""); + expr = m_utils.externalFunctionPointersEqualFunction() + + "(" + + IRVariable{_binOp.leftExpression()}.part("address").name() + "," + + IRVariable{_binOp.leftExpression()}.part("functionSelector").name() + "," + + IRVariable{_binOp.rightExpression()}.part("address").name() + "," + + IRVariable{_binOp.rightExpression()}.part("functionSelector").name() + + ")"; + if (op == Token::NotEqual) + expr = "iszero(" + expr + ")"; + } + else if (op == Token::Equal) expr = "eq(" + move(args) + ")"; else if (op == Token::NotEqual) expr = "iszero(eq(" + move(args) + "))"; @@ -843,7 +855,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) else if (auto rationalNumberType = dynamic_cast(_binOp.leftExpression().annotation().type)) { solAssert(rationalNumberType->integerType(), "Invalid literal as the base for exponentiation."); - solAssert(dynamic_cast(commonType), ""); + solAssert(dynamic_cast(commonType)); define(_binOp) << m_utils.overflowCheckedIntLiteralExpFunction( *rationalNumberType, @@ -939,7 +951,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) { FunctionDefinition const* functionDef = ASTNode::resolveFunctionCall(_functionCall, &m_context.mostDerivedContract()); - solAssert(!functionType->takesArbitraryParameters(), ""); + solAssert(!functionType->takesArbitraryParameters()); vector args; if (functionType->bound()) @@ -950,7 +962,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) if (functionDef) { - solAssert(functionDef->isImplemented(), ""); + solAssert(functionDef->isImplemented()); define(_functionCall) << m_context.enqueueFunctionForCodeGeneration(*functionDef) << @@ -1053,7 +1065,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case FunctionType::Kind::Error: { ErrorDefinition const* error = dynamic_cast(ASTNode::referencedDeclaration(_functionCall.expression())); - solAssert(error, ""); + solAssert(error); revertWithError( error->functionType(true)->externalSignature(), error->functionType(true)->parameterTypes(), @@ -1064,7 +1076,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case FunctionType::Kind::Wrap: case FunctionType::Kind::Unwrap: { - solAssert(arguments.size() == 1, ""); + solAssert(arguments.size() == 1); FunctionType::Kind kind = functionType->kind(); if (kind == FunctionType::Kind::Wrap) solAssert( @@ -1074,7 +1086,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) "" ); else - solAssert(type(*arguments.at(0)).category() == Type::Category::UserDefinedValueType, ""); + solAssert(type(*arguments.at(0)).category() == Type::Category::UserDefinedValueType); define(_functionCall, *arguments.at(0)); break; @@ -1108,7 +1120,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case FunctionType::Kind::ABIEncodeWithSignature: { bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked; - solAssert(functionType->padArguments() != isPacked, ""); + solAssert(functionType->padArguments() != isPacked); bool const hasSelectorOrSignature = functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector || functionType->kind() == FunctionType::Kind::ABIEncodeCall || @@ -1122,7 +1134,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) if (functionType->kind() == FunctionType::Kind::ABIEncodeCall) { - solAssert(arguments.size() == 2, ""); + solAssert(arguments.size() == 2); // Account for tuples with one component which become that component if (type(*arguments[1]).category() == Type::Category::Tuple) { @@ -1150,10 +1162,21 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } if (functionType->kind() == FunctionType::Kind::ABIEncodeCall) - selector = convert( - IRVariable(*arguments[0]).part("functionSelector"), - *TypeProvider::fixedBytes(4) - ).name(); + { + auto const& selectorType = dynamic_cast(type(*arguments.front())); + if (selectorType.kind() == FunctionType::Kind::Declaration) + { + solAssert(selectorType.hasDeclaration()); + selector = formatNumber(selectorType.externalIdentifier() << (256 - 32)); + } + else + { + selector = convert( + IRVariable(*arguments[0]).part("functionSelector"), + *TypeProvider::fixedBytes(4) + ).name(); + } + } else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature) { // hash the signature @@ -1234,7 +1257,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) referenceType && referenceType->dataStoredIn(DataLocation::CallData) ) { - solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), ""); + solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata())); IRVariable var = convert(*arguments[0], *TypeProvider::bytesCalldata()); templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, false)); templ("offset", var.part("offset").name()); @@ -1256,8 +1279,8 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } case FunctionType::Kind::Revert: { - solAssert(arguments.size() == parameterTypes.size(), ""); - solAssert(arguments.size() <= 1, ""); + solAssert(arguments.size() == parameterTypes.size()); + solAssert(arguments.size() <= 1); solAssert( arguments.empty() || arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), @@ -1276,7 +1299,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case FunctionType::Kind::ObjectCreation: { ArrayType const& arrayType = dynamic_cast(*_functionCall.annotation().type); - solAssert(arguments.size() == 1, ""); + solAssert(arguments.size() == 1); IRVariable value = convert(*arguments[0], *TypeProvider::uint256()); define(_functionCall) << @@ -1288,7 +1311,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } case FunctionType::Kind::KECCAK256: { - solAssert(arguments.size() == 1, ""); + solAssert(arguments.size() == 1); ArrayType const* arrayType = TypeProvider::bytesMemory(); @@ -1316,10 +1339,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } case FunctionType::Kind::ArrayPop: { - solAssert(functionType->bound(), ""); - solAssert(functionType->parameterTypes().empty(), ""); + solAssert(functionType->bound()); + solAssert(functionType->parameterTypes().empty()); ArrayType const* arrayType = dynamic_cast(functionType->selfType()); - solAssert(arrayType, ""); + solAssert(arrayType); define(_functionCall) << m_utils.storageArrayPopFunction(*arrayType) << "(" << @@ -1330,7 +1353,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case FunctionType::Kind::ArrayPush: { ArrayType const* arrayType = dynamic_cast(functionType->selfType()); - solAssert(arrayType, ""); + solAssert(arrayType); if (arguments.empty()) { @@ -1391,8 +1414,8 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) {FunctionType::Kind::AddMod, "addmod"}, {FunctionType::Kind::MulMod, "mulmod"}, }; - solAssert(functions.find(functionType->kind()) != functions.end(), ""); - solAssert(arguments.size() == 3 && parameterTypes.size() == 3, ""); + solAssert(functions.find(functionType->kind()) != functions.end()); + solAssert(arguments.size() == 3 && parameterTypes.size() == 3); IRVariable modulus(m_context.newYulVariable(), *(parameterTypes[2])); define(modulus, *arguments[2]); @@ -1417,7 +1440,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) {FunctionType::Kind::Selfdestruct, "selfdestruct"}, {FunctionType::Kind::BlockHash, "blockhash"}, }; - solAssert(functions.find(functionType->kind()) != functions.end(), ""); + solAssert(functions.find(functionType->kind()) != functions.end()); string args; for (size_t i = 0; i < arguments.size(); ++i) @@ -1474,7 +1497,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) t("saltSet", functionType->saltSet()); if (functionType->saltSet()) t("salt", IRVariable(_functionCall.expression()).part("salt").name()); - solAssert(IRVariable(_functionCall).stackSlots().size() == 1, ""); + solAssert(IRVariable(_functionCall).stackSlots().size() == 1); t("address", IRVariable(_functionCall).commaSeparatedList()); t("isTryCall", _functionCall.annotation().tryCall); if (_functionCall.annotation().tryCall) @@ -1488,7 +1511,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case FunctionType::Kind::Send: case FunctionType::Kind::Transfer: { - solAssert(arguments.size() == 1 && parameterTypes.size() == 1, ""); + solAssert(arguments.size() == 1 && parameterTypes.size() == 1); string address{IRVariable(_functionCall.expression()).part("address").name()}; string value{expressionAsType(*arguments[0], *(parameterTypes[0]))}; Whiskers templ(R"( @@ -1517,10 +1540,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case FunctionType::Kind::RIPEMD160: case FunctionType::Kind::SHA256: { - solAssert(!_functionCall.annotation().tryCall, ""); - solAssert(!functionType->valueSet(), ""); - solAssert(!functionType->gasSet(), ""); - solAssert(!functionType->bound(), ""); + solAssert(!_functionCall.annotation().tryCall); + solAssert(!functionType->valueSet()); + solAssert(!functionType->gasSet()); + solAssert(!functionType->bound()); static map> precompiles = { {FunctionType::Kind::ECRecover, std::make_tuple(1, 0)}, @@ -1595,7 +1618,7 @@ void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options) for (size_t i = 0; i < _options.names().size(); ++i) { string const& name = *_options.names()[i]; - solAssert(name == "salt" || name == "gas" || name == "value", ""); + solAssert(name == "salt" || name == "gas" || name == "value"); define(IRVariable(_options).part(name), *_options.options()[i]); } @@ -1613,7 +1636,7 @@ bool IRGeneratorForStatements::visit(MemberAccess const& _memberAccess) innerExpression->expression().annotation().type->category() == Type::Category::Address ) { - solAssert(innerExpression->annotation().type->category() == Type::Category::Array, ""); + solAssert(innerExpression->annotation().type->category() == Type::Category::Array); // Skip visiting
.code innerExpression->expression().accept(*this); @@ -1634,7 +1657,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) if (memberFunctionType && memberFunctionType->bound()) { define(IRVariable(_memberAccess).part("self"), _memberAccess.expression()); - solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); + solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static); if (memberFunctionType->kind() == FunctionType::Kind::Internal) assignInternalFunctionIDIfNotCalledDirectly( _memberAccess, @@ -1650,9 +1673,9 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) else { auto const& functionDefinition = dynamic_cast(memberFunctionType->declaration()); - solAssert(memberFunctionType->kind() == FunctionType::Kind::DelegateCall, ""); + solAssert(memberFunctionType->kind() == FunctionType::Kind::DelegateCall); auto contract = dynamic_cast(functionDefinition.scope()); - solAssert(contract && contract->isLibrary(), ""); + solAssert(contract && contract->isLibrary()); define(IRVariable(_memberAccess).part("address")) << linkerSymbol(*contract) << "\n"; define(IRVariable(_memberAccess).part("functionSelector")) << memberFunctionType->externalIdentifier() << "\n"; } @@ -1665,7 +1688,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { ContractType const& type = dynamic_cast(*_memberAccess.expression().annotation().type); if (type.isSuper()) - solAssert(false, ""); + solAssert(false); // ordinary contract type else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration) @@ -1713,7 +1736,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) ")\n"; else if (set{"send", "transfer"}.count(member)) { - solAssert(dynamic_cast(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable, ""); + solAssert(dynamic_cast(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable); define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression()); } else if (set{"call", "callcode", "delegatecall", "staticcall"}.count(member)) @@ -1741,7 +1764,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) functionType.kind() == FunctionType::Kind::Internal ) { - solAssert(functionType.hasDeclaration(), ""); + solAssert(functionType.hasDeclaration()); solAssert( functionType.kind() == FunctionType::Kind::Error || functionType.declaration().isPartOfExternalInterface(), @@ -1809,7 +1832,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { Type const* arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); auto const& contractType = dynamic_cast(*arg); - solAssert(!contractType.isSuper(), ""); + solAssert(!contractType.isSuper()); ContractDefinition const& contract = contractType.contractDefinition(); m_context.subObjectsCreated().insert(&contract); appendCode() << Whiskers(R"( @@ -1833,7 +1856,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { Type const* arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); auto const& contractType = dynamic_cast(*arg); - solAssert(!contractType.isSuper(), ""); + solAssert(!contractType.isSuper()); ContractDefinition const& contract = contractType.contractDefinition(); define(_memberAccess) << formatNumber(u256{contract.interfaceId()} << (256 - 32)) << "\n"; } @@ -1960,7 +1983,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) } else if (member == "pop" || member == "push") { - solAssert(type.location() == DataLocation::Storage, ""); + solAssert(type.location() == DataLocation::Storage); define(IRVariable{_memberAccess}.part("slot"), IRVariable{_memberAccess.expression()}.part("slot")); } else @@ -1996,8 +2019,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) *_memberAccess.annotation().referencedDeclaration ).resolveVirtual(m_context.mostDerivedContract(), super); - solAssert(resolvedFunctionDef.functionType(true), ""); - solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal, ""); + solAssert(resolvedFunctionDef.functionType(true)); + solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal); assignInternalFunctionIDIfNotCalledDirectly(_memberAccess, resolvedFunctionDef); } else if (auto const* variable = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) @@ -2055,19 +2078,19 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) // The old code generator had a generic "else" case here // without any specific code being generated, // but it would still be better to have an exhaustive list. - solAssert(false, ""); + solAssert(false); } else if (EnumType const* enumType = dynamic_cast(&actualType)) define(_memberAccess) << to_string(enumType->memberValue(_memberAccess.memberName())) << "\n"; else if (dynamic_cast(&actualType)) - solAssert(member == "wrap" || member == "unwrap", ""); + solAssert(member == "wrap" || member == "unwrap"); else if (auto const* arrayType = dynamic_cast(&actualType)) - solAssert(arrayType->isByteArray() && member == "concat", ""); + solAssert(arrayType->isByteArray() && member == "concat"); else // The old code generator had a generic "else" case here // without any specific code being generated, // but it would still be better to have an exhaustive list. - solAssert(false, ""); + solAssert(false); break; } case Type::Category::Module: @@ -2083,17 +2106,17 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) ); if (auto variable = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) { - solAssert(variable->isConstant(), ""); + solAssert(variable->isConstant()); handleVariableReference(*variable, static_cast(_memberAccess)); } else if (auto const* function = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) { auto funType = dynamic_cast(_memberAccess.annotation().type); - solAssert(function && function->isFree(), ""); - solAssert(function->functionType(true), ""); - solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal, ""); - solAssert(funType->kind() == FunctionType::Kind::Internal, ""); - solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); + solAssert(function && function->isFree()); + solAssert(function->functionType(true)); + solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal); + solAssert(funType->kind() == FunctionType::Kind::Internal); + solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static); assignInternalFunctionIDIfNotCalledDirectly(_memberAccess, *function); } @@ -2117,7 +2140,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) yul::Statement modified = bodyCopier(_inlineAsm.operations()); - solAssert(holds_alternative(modified), ""); + solAssert(holds_alternative(modified)); // Do not provide dialect so that we get the full type information. appendCode() << yul::AsmPrinter()(std::get(modified)) << "\n"; @@ -2160,7 +2183,7 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) dynamic_cast(baseType).arrayType(); if (baseType.category() == Type::Category::ArraySlice) - solAssert(arrayType.dataStoredIn(DataLocation::CallData) && arrayType.isDynamicallySized(), ""); + solAssert(arrayType.dataStoredIn(DataLocation::CallData) && arrayType.isDynamicallySized()); solAssert(_indexAccess.indexExpression(), "Index expression expected."); @@ -2253,8 +2276,8 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) } else if (baseType.category() == Type::Category::TypeType) { - solAssert(baseType.sizeOnStack() == 0, ""); - solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); + solAssert(baseType.sizeOnStack() == 0); + solAssert(_indexAccess.annotation().type->sizeOnStack() == 0); // no-op - this seems to be a lone array type (`structType[];`) } else @@ -2279,7 +2302,7 @@ void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAcces { case DataLocation::CallData: { - solAssert(baseType.isDynamicallySized(), ""); + solAssert(baseType.isDynamicallySized()); IRVariable sliceStart{m_context.newYulVariable(), *TypeProvider::uint256()}; if (_indexRangeAccess.startExpression()) define(sliceStart, IRVariable{*_indexRangeAccess.startExpression()}); @@ -2317,18 +2340,18 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) switch (magicVar->type()->category()) { case Type::Category::Contract: - solAssert(_identifier.name() == "this", ""); + solAssert(_identifier.name() == "this"); define(_identifier) << "address()\n"; break; case Type::Category::Integer: - solAssert(_identifier.name() == "now", ""); + solAssert(_identifier.name() == "now"); define(_identifier) << "timestamp()\n"; break; case Type::Category::TypeType: { auto typeType = dynamic_cast(magicVar->type()); if (auto contractType = dynamic_cast(typeType->actualType())) - solAssert(!contractType->isSuper() || _identifier.name() == "super", ""); + solAssert(!contractType->isSuper() || _identifier.name() == "super"); break; } default: @@ -2338,11 +2361,11 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) } else if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) { - solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual, ""); + solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual); FunctionDefinition const& resolvedFunctionDef = functionDef->resolveVirtual(m_context.mostDerivedContract()); - solAssert(resolvedFunctionDef.functionType(true), ""); - solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal, ""); + solAssert(resolvedFunctionDef.functionType(true)); + solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal); assignInternalFunctionIDIfNotCalledDirectly(_identifier, resolvedFunctionDef); } else if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) @@ -2437,9 +2460,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall( ) { FunctionType const& funType = dynamic_cast(type(_functionCall.expression())); - solAssert(!funType.takesArbitraryParameters(), ""); - solAssert(_arguments.size() == funType.parameterTypes().size(), ""); - solAssert(!funType.isBareCall(), ""); + solAssert(!funType.takesArbitraryParameters()); + solAssert(_arguments.size() == funType.parameterTypes().size()); + solAssert(!funType.isBareCall()); FunctionType::Kind const funKind = funType.kind(); solAssert( @@ -2543,7 +2566,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( string const retVars = IRVariable(_functionCall).commaSeparatedList(); templ("retVars", retVars); - solAssert(retVars.empty() == returnInfo.returnTypes.empty(), ""); + solAssert(retVars.empty() == returnInfo.returnTypes.empty()); templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true)); templ("dynamicReturnSize", returnInfo.dynamicReturnSize); @@ -2552,7 +2575,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall; - solAssert(funType.padArguments(), ""); + solAssert(funType.padArguments()); templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, parameterTypes, encodeForLibraryCall)); templ("argumentString", joinHumanReadablePrefixed(argumentStrings)); @@ -2605,7 +2628,7 @@ void IRGeneratorForStatements::appendBareCall( ); FunctionType::Kind const funKind = funType.kind(); - solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); + solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall()); solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); solAssert( funKind == FunctionType::Kind::BareCall || @@ -2613,7 +2636,7 @@ void IRGeneratorForStatements::appendBareCall( funKind == FunctionType::Kind::BareStaticCall, "" ); - solAssert(!_functionCall.annotation().tryCall, ""); + solAssert(!_functionCall.annotation().tryCall); Whiskers templ(R"( let := () @@ -2825,7 +2848,7 @@ string IRGeneratorForStatements::binaryOperation( "Not yet implemented - FixedPointType." ); IntegerType const* type = dynamic_cast(&_type); - solAssert(type, ""); + solAssert(type); bool checked = m_context.arithmetic() == Arithmetic::Checked; switch (_operator) { @@ -2865,9 +2888,9 @@ std::string IRGeneratorForStatements::shiftOperation( "Not yet implemented - FixedPointType." ); IntegerType const* amountType = dynamic_cast(&_amountToShift.type()); - solAssert(amountType, ""); + solAssert(amountType); - solAssert(_operator == Token::SHL || _operator == Token::SAR, ""); + solAssert(_operator == Token::SHL || _operator == Token::SAR); return Whiskers(R"( @@ -2886,7 +2909,7 @@ std::string IRGeneratorForStatements::shiftOperation( void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp) { langutil::Token const op = _binOp.getOperator(); - solAssert(op == Token::Or || op == Token::And, ""); + solAssert(op == Token::Or || op == Token::And); _binOp.leftExpression().accept(*this); setLocation(_binOp); @@ -2933,7 +2956,7 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable if (_memory.byteArrayElement) { - solAssert(_lvalue.type == *TypeProvider::byte(), ""); + solAssert(_lvalue.type == *TypeProvider::byte()); appendCode() << "mstore8(" + _memory.address + ", byte(0, " + prepared.commaSeparatedList() + "))\n"; } else @@ -2957,9 +2980,9 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable } else { - solAssert(_lvalue.type.sizeOnStack() == 1, ""); + solAssert(_lvalue.type.sizeOnStack() == 1); auto const* valueReferenceType = dynamic_cast(&_value.type()); - solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory), ""); + solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory)); appendCode() << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n"; } }, @@ -2968,7 +2991,7 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable { solUnimplementedAssert(_lvalue.type.isValueType()); solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1); - solAssert(_lvalue.type == *_immutable.variable->type(), ""); + solAssert(_lvalue.type == *_immutable.variable->type()); size_t memOffset = m_context.immutableMemoryOffset(*_immutable.variable); IRVariable prepared(m_context.newYulVariable(), _lvalue.type); @@ -3028,7 +3051,7 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue) [&](IRLValue::Immutable const& _immutable) { solUnimplementedAssert(_lvalue.type.isValueType()); solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1); - solAssert(_lvalue.type == *_immutable.variable->type(), ""); + solAssert(_lvalue.type == *_immutable.variable->type()); if (m_context.executionContext() == IRGenerationContext::ExecutionContext::Creation) { string readFunction = m_utils.readFromMemory(*_immutable.variable->type()); @@ -3050,13 +3073,13 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue) void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue _lvalue) { - solAssert(!m_currentLValue, ""); + solAssert(!m_currentLValue); if (_expression.annotation().willBeWrittenTo) { m_currentLValue.emplace(std::move(_lvalue)); if (_lvalue.type.dataStoredIn(DataLocation::CallData)) - solAssert(holds_alternative(_lvalue.kind), ""); + solAssert(holds_alternative(_lvalue.kind)); } else // Only define the expression, if it will not be written to. @@ -3130,7 +3153,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement) size_t i = 0; for (ASTPointer const& varDecl: successClause.parameters()->parameters()) { - solAssert(varDecl, ""); + solAssert(varDecl); define(m_context.addLocalVariable(*varDecl), successClause.parameters()->parameters().size() == 1 ? IRVariable(externalCall) : @@ -3171,7 +3194,7 @@ void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement) appendCode() << runFallback << " := 0\n"; if (errorClause->parameters()) { - solAssert(errorClause->parameters()->parameters().size() == 1, ""); + solAssert(errorClause->parameters()->parameters().size() == 1); IRVariable const& var = m_context.addLocalVariable(*errorClause->parameters()->parameters().front()); define(var) << dataVariable << "\n"; } @@ -3192,7 +3215,7 @@ void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement) appendCode() << runFallback << " := 0\n"; if (panicClause->parameters()) { - solAssert(panicClause->parameters()->parameters().size() == 1, ""); + solAssert(panicClause->parameters()->parameters().size() == 1); IRVariable const& var = m_context.addLocalVariable(*panicClause->parameters()->parameters().front()); define(var) << code << "\n"; } @@ -3218,7 +3241,7 @@ void IRGeneratorForStatements::handleCatchFallback(TryCatchClause const& _fallba setLocation(_fallback); if (_fallback.parameters()) { - solAssert(m_context.evmVersion().supportsReturndata(), ""); + solAssert(m_context.evmVersion().supportsReturndata()); solAssert( _fallback.parameters()->parameters().size() == 1 && _fallback.parameters()->parameters().front() && @@ -3254,7 +3277,7 @@ void IRGeneratorForStatements::revertWithError( for (ASTPointer const& arg: _errorArguments) { errorArgumentVars += IRVariable(*arg).stackSlots(); - solAssert(arg->annotation().type, ""); + solAssert(arg->annotation().type); errorArgumentTypes.push_back(arg->annotation().type); } templ("argumentVars", joinHumanReadablePrefixed(errorArgumentVars)); @@ -3272,6 +3295,6 @@ bool IRGeneratorForStatements::visit(TryCatchClause const& _clause) string IRGeneratorForStatements::linkerSymbol(ContractDefinition const& _library) const { - solAssert(_library.isLibrary(), ""); + solAssert(_library.isLibrary()); return "linkersymbol(" + util::escapeAndQuoteString(_library.fullyQualifiedName()) + ")"; } diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp index 0edfc27ff..9e31d7fc9 100644 --- a/libsolidity/formal/CHC.cpp +++ b/libsolidity/formal/CHC.cpp @@ -78,6 +78,9 @@ CHC::CHC( void CHC::analyze(SourceUnit const& _source) { + if (!shouldAnalyze(_source)) + return; + if (!m_settings.solvers.z3 && !m_settings.solvers.smtlib2) { if (!m_noSolverWarning) @@ -137,6 +140,9 @@ vector CHC::unhandledQueries() const bool CHC::visit(ContractDefinition const& _contract) { + if (!shouldAnalyze(_contract)) + return false; + resetContractAnalysis(); initContract(_contract); clearIndices(&_contract); @@ -152,6 +158,9 @@ bool CHC::visit(ContractDefinition const& _contract) void CHC::endVisit(ContractDefinition const& _contract) { + if (!shouldAnalyze(_contract)) + return; + for (auto base: _contract.annotation().linearizedBaseContracts) { if (auto constructor = base->constructor()) diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 68545f4f6..b42e9b355 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -1035,6 +1035,12 @@ void SMTEncoder::visitPublicGetter(FunctionCall const& _funCall) } } +bool SMTEncoder::shouldAnalyze(SourceUnit const& _source) const +{ + return m_settings.contracts.isDefault() || + m_settings.contracts.has(*_source.annotation().path); +} + bool SMTEncoder::shouldAnalyze(ContractDefinition const& _contract) const { if (!_contract.canBeDeployed()) diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h index 7ca19358d..fe84189be 100644 --- a/libsolidity/formal/SMTEncoder.h +++ b/libsolidity/formal/SMTEncoder.h @@ -223,6 +223,8 @@ protected: /// @returns true if @param _contract is set for analysis in the settings /// and it is not abstract. bool shouldAnalyze(ContractDefinition const& _contract) const; + /// @returns true if @param _source is set for analysis in the settings. + bool shouldAnalyze(SourceUnit const& _source) const; bool isPublicGetter(Expression const& _expr); diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index 5317acee4..e1c35e9ea 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -44,7 +44,7 @@ struct OptimiserSettings static char constexpr DefaultYulOptimiserSteps[] = "dhfoDgvulfnTUtnIf" // None of these can make stack problems worse "[" - "xa[r]scLM" // Turn into SSA and simplify + "xa[r]EscLM" // Turn into SSA and simplify "cCTUtTOntnfDIul" // Perform structural simplification "Lcul" // Simplify again "Vcul [j]" // Reverse SSA diff --git a/libsolidity/lsp/LanguageServer.cpp b/libsolidity/lsp/LanguageServer.cpp index 254706cc6..fb704d819 100644 --- a/libsolidity/lsp/LanguageServer.cpp +++ b/libsolidity/lsp/LanguageServer.cpp @@ -38,6 +38,7 @@ #include using namespace std; +using namespace std::string_literals; using namespace std::placeholders; using namespace solidity::lsp; @@ -96,10 +97,10 @@ LanguageServer::LanguageServer(Transport& _transport): {"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)}, {"initialized", [](auto, auto) {}}, {"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }}, - {"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _1, _2)}, - {"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _1, _2)}, - {"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _1, _2)}, - {"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _1, _2)}, + {"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)}, + {"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)}, + {"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)}, + {"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)}, }, m_fileRepository("/" /* basePath */), m_compilerStack{m_fileRepository.reader()} @@ -260,6 +261,10 @@ bool LanguageServer::run() else m_client.error({}, ErrorCode::ParseError, "\"method\" has to be a string."); } + catch (RequestError const& error) + { + m_client.error(id, error.code(), error.comment() ? *error.comment() : ""s); + } catch (...) { m_client.error(id, ErrorCode::InternalError, "Unhandled exception: "s + boost::current_exception_diagnostic_information()); @@ -268,24 +273,23 @@ bool LanguageServer::run() return m_state == State::ExitRequested; } -bool LanguageServer::checkServerInitialized(MessageID _id) +void LanguageServer::requireServerInitialized() { - if (m_state != State::Initialized) - { - m_client.error(_id, ErrorCode::ServerNotInitialized, "Server is not properly initialized."); - return false; - } - else - return true; + lspAssert( + m_state == State::Initialized, + ErrorCode::ServerNotInitialized, + "Server is not properly initialized." + ); } void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args) { - if (m_state != State::Started) - { - m_client.error(_id, ErrorCode::RequestFailed, "Initialize called at the wrong time."); - return; - } + lspAssert( + m_state == State::Started, + ErrorCode::RequestFailed, + "Initialize called at the wrong time." + ); + m_state = State::Initialized; // The default of FileReader is to use `.`, but the path from where the LSP was started @@ -294,11 +298,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args) if (Json::Value uri = _args["rootUri"]) { rootPath = uri.asString(); - if (!boost::starts_with(rootPath, "file://")) - { - m_client.error(_id, ErrorCode::InvalidParams, "rootUri only supports file URI scheme."); - return; - } + lspAssert( + boost::starts_with(rootPath, "file://"), + ErrorCode::InvalidParams, + "rootUri only supports file URI scheme." + ); + rootPath = rootPath.substr(7); } else if (Json::Value rootPath = _args["rootPath"]) @@ -317,23 +322,23 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args) m_client.reply(_id, move(replyArgs)); } - -void LanguageServer::handleWorkspaceDidChangeConfiguration(MessageID _id, Json::Value const& _args) +void LanguageServer::handleWorkspaceDidChangeConfiguration(Json::Value const& _args) { - if (!checkServerInitialized(_id)) - return; + requireServerInitialized(); if (_args["settings"].isObject()) changeConfiguration(_args["settings"]); } -void LanguageServer::handleTextDocumentDidOpen(MessageID _id, Json::Value const& _args) +void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args) { - if (!checkServerInitialized(_id)) - return; + requireServerInitialized(); - if (!_args["textDocument"]) - m_client.error(_id, ErrorCode::RequestFailed, "Text document parameter missing."); + lspAssert( + _args["textDocument"], + ErrorCode::RequestFailed, + "Text document parameter missing." + ); string text = _args["textDocument"]["text"].asString(); string uri = _args["textDocument"]["uri"].asString(); @@ -342,41 +347,37 @@ void LanguageServer::handleTextDocumentDidOpen(MessageID _id, Json::Value const& compileAndUpdateDiagnostics(); } -void LanguageServer::handleTextDocumentDidChange(MessageID _id, Json::Value const& _args) +void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args) { - if (!checkServerInitialized(_id)) - return; + requireServerInitialized(); string const uri = _args["textDocument"]["uri"].asString(); for (Json::Value jsonContentChange: _args["contentChanges"]) { - if (!jsonContentChange.isObject()) - { - m_client.error(_id, ErrorCode::RequestFailed, "Invalid content reference."); - return; - } + lspAssert( + jsonContentChange.isObject(), + ErrorCode::RequestFailed, + "Invalid content reference." + ); string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri); - if (!m_fileRepository.sourceUnits().count(sourceUnitName)) - { - m_client.error(_id, ErrorCode::RequestFailed, "Unknown file: " + uri); - return; - } + lspAssert( + m_fileRepository.sourceUnits().count(sourceUnitName), + ErrorCode::RequestFailed, + "Unknown file: " + uri + ); string text = jsonContentChange["text"].asString(); if (jsonContentChange["range"].isObject()) // otherwise full content update { optional change = parseRange(sourceUnitName, jsonContentChange["range"]); - if (!change || !change->hasText()) - { - m_client.error( - _id, - ErrorCode::RequestFailed, - "Invalid source range: " + jsonCompactPrint(jsonContentChange["range"]) - ); - return; - } + lspAssert( + change && change->hasText(), + ErrorCode::RequestFailed, + "Invalid source range: " + jsonCompactPrint(jsonContentChange["range"]) + ); + string buffer = m_fileRepository.sourceUnits().at(sourceUnitName); buffer.replace(static_cast(change->start), static_cast(change->end - change->start), move(text)); text = move(buffer); @@ -387,13 +388,15 @@ void LanguageServer::handleTextDocumentDidChange(MessageID _id, Json::Value cons compileAndUpdateDiagnostics(); } -void LanguageServer::handleTextDocumentDidClose(MessageID _id, Json::Value const& _args) +void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args) { - if (!checkServerInitialized(_id)) - return; + requireServerInitialized(); - if (!_args["textDocument"]) - m_client.error(_id, ErrorCode::RequestFailed, "Text document parameter missing."); + lspAssert( + _args["textDocument"], + ErrorCode::RequestFailed, + "Text document parameter missing." + ); string uri = _args["textDocument"]["uri"].asString(); m_openFiles.erase(uri); diff --git a/libsolidity/lsp/LanguageServer.h b/libsolidity/lsp/LanguageServer.h index 802dd8198..a3ab37198 100644 --- a/libsolidity/lsp/LanguageServer.h +++ b/libsolidity/lsp/LanguageServer.h @@ -60,12 +60,12 @@ public: private: /// Checks if the server is initialized (to be used by messages that need it to be initialized). /// Reports an error and returns false if not. - bool checkServerInitialized(MessageID _id); + void requireServerInitialized(); void handleInitialize(MessageID _id, Json::Value const& _args); - void handleWorkspaceDidChangeConfiguration(MessageID _id, Json::Value const& _args); - void handleTextDocumentDidOpen(MessageID _id, Json::Value const& _args); - void handleTextDocumentDidChange(MessageID _id, Json::Value const& _args); - void handleTextDocumentDidClose(MessageID _id, Json::Value const& _args); + void handleWorkspaceDidChangeConfiguration(Json::Value const& _args); + void handleTextDocumentDidOpen(Json::Value const& _args); + void handleTextDocumentDidChange(Json::Value const& _args); + void handleTextDocumentDidClose(Json::Value const& _args); /// Invoked when the server user-supplied configuration changes (initiated by the client). void changeConfiguration(Json::Value const&); diff --git a/libsolidity/lsp/Transport.h b/libsolidity/lsp/Transport.h index c6ed8fa8a..2d0b49517 100644 --- a/libsolidity/lsp/Transport.h +++ b/libsolidity/lsp/Transport.h @@ -17,6 +17,8 @@ // SPDX-License-Identifier: GPL-3.0 #pragma once +#include + #include #include @@ -45,6 +47,32 @@ enum class ErrorCode RequestFailed = -32803 }; +/** + * Error exception used to bail out on errors in the LSP function-call handlers. + */ +class RequestError: public util::Exception +{ +public: + explicit RequestError(ErrorCode _code): + m_code{_code} + { + } + + ErrorCode code() const noexcept { return m_code; } + +private: + ErrorCode m_code; +}; + +#define lspAssert(condition, errorCode, errorMessage) \ + if (!(condition)) \ + { \ + BOOST_THROW_EXCEPTION( \ + RequestError(errorCode) << \ + errinfo_comment(errorMessage) \ + ); \ + } + /** * Transport layer API * diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 16b68535a..fde673e3d 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -120,6 +120,8 @@ add_library(yul optimiser/DeadCodeEliminator.h optimiser/Disambiguator.cpp optimiser/Disambiguator.h + optimiser/EqualStoreEliminator.cpp + optimiser/EqualStoreEliminator.h optimiser/EquivalentFunctionDetector.cpp optimiser/EquivalentFunctionDetector.h optimiser/EquivalentFunctionCombiner.cpp diff --git a/libyul/optimiser/EqualStoreEliminator.cpp b/libyul/optimiser/EqualStoreEliminator.cpp new file mode 100644 index 000000000..dcba98ce4 --- /dev/null +++ b/libyul/optimiser/EqualStoreEliminator.cpp @@ -0,0 +1,70 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Optimisation stage that removes mstore and sstore operations if they store the same + * value that is already known to be in that slot. + */ + +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::util; +using namespace solidity::evmasm; +using namespace solidity::yul; + +void EqualStoreEliminator::run(OptimiserStepContext const& _context, Block& _ast) +{ + EqualStoreEliminator eliminator{ + _context.dialect, + SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)) + }; + eliminator(_ast); + + StatementRemover remover{eliminator.m_pendingRemovals}; + remover(_ast); +} + +void EqualStoreEliminator::visit(Statement& _statement) +{ + // No need to consider potential changes through complex arguments since + // isSimpleStore only returns something if the arguments are identifiers. + if (ExpressionStatement const* expression = get_if(&_statement)) + { + if (auto vars = isSimpleStore(StoreLoadLocation::Storage, *expression)) + { + if (auto const* currentValue = valueOrNullptr(m_storage, vars->first)) + if (*currentValue == vars->second) + m_pendingRemovals.insert(&_statement); + } + else if (auto vars = isSimpleStore(StoreLoadLocation::Memory, *expression)) + { + if (auto const* currentValue = valueOrNullptr(m_memory, vars->first)) + if (*currentValue == vars->second) + m_pendingRemovals.insert(&_statement); + } + } + + DataFlowAnalyzer::visit(_statement); +} diff --git a/libyul/optimiser/EqualStoreEliminator.h b/libyul/optimiser/EqualStoreEliminator.h new file mode 100644 index 000000000..796fcc538 --- /dev/null +++ b/libyul/optimiser/EqualStoreEliminator.h @@ -0,0 +1,60 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Optimisation stage that removes mstore and sstore operations if they store the same + * value that is already known to be in that slot. + */ + +#pragma once + +#include +#include + +namespace solidity::yul +{ + +/** + * Optimisation stage that removes mstore and sstore operations if they store the same + * value that is already known to be in that slot. + * + * Works best if the code is in SSA form - without literal arguments. + * + * Prerequisite: Disambiguator, ForLoopInitRewriter. + */ +class EqualStoreEliminator: public DataFlowAnalyzer +{ +public: + static constexpr char const* name{"EqualStoreEliminator"}; + static void run(OptimiserStepContext const&, Block& _ast); + +private: + EqualStoreEliminator( + Dialect const& _dialect, + std::map _functionSideEffects + ): + DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)) + {} + +protected: + using ASTModifier::visit; + void visit(Statement& _statement) override; + + std::set m_pendingRemovals; +}; + +} diff --git a/libyul/optimiser/OptimizerUtilities.cpp b/libyul/optimiser/OptimizerUtilities.cpp index 23596a745..ba06b2180 100644 --- a/libyul/optimiser/OptimizerUtilities.cpp +++ b/libyul/optimiser/OptimizerUtilities.cpp @@ -57,3 +57,18 @@ optional yul::toEVMInstruction(Dialect const& _dialect, Yul return builtin->instruction; return nullopt; } + +void StatementRemover::operator()(Block& _block) +{ + util::iterateReplacing( + _block.statements, + [&](Statement& _statement) -> std::optional> + { + if (m_toRemove.count(&_statement)) + return {vector{}}; + else + return nullopt; + } + ); + ASTModifier::operator()(_block); +} diff --git a/libyul/optimiser/OptimizerUtilities.h b/libyul/optimiser/OptimizerUtilities.h index d80b16316..b491e57e1 100644 --- a/libyul/optimiser/OptimizerUtilities.h +++ b/libyul/optimiser/OptimizerUtilities.h @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -48,4 +49,14 @@ bool isRestrictedIdentifier(Dialect const& _dialect, YulString const& _identifie /// Helper function that returns the instruction, if the `_name` is a BuiltinFunction std::optional toEVMInstruction(Dialect const& _dialect, YulString const& _name); +class StatementRemover: public ASTModifier +{ +public: + explicit StatementRemover(std::set const& _toRemove): m_toRemove(_toRemove) {} + + void operator()(Block& _block) override; +private: + std::set const& m_toRemove; +}; + } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 4012fa970..0f2194061 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -204,6 +205,7 @@ map> const& OptimiserSuite::allSteps() ConditionalUnsimplifier, ControlFlowSimplifier, DeadCodeEliminator, + EqualStoreEliminator, EquivalentFunctionCombiner, ExpressionInliner, ExpressionJoiner, @@ -244,6 +246,7 @@ map const& OptimiserSuite::stepNameToAbbreviationMap() {ConditionalUnsimplifier::name, 'U'}, {ControlFlowSimplifier::name, 'n'}, {DeadCodeEliminator::name, 'D'}, + {EqualStoreEliminator::name, 'E'}, {EquivalentFunctionCombiner::name, 'v'}, {ExpressionInliner::name, 'e'}, {ExpressionJoiner::name, 'j'}, diff --git a/libyul/optimiser/UnusedAssignEliminator.cpp b/libyul/optimiser/UnusedAssignEliminator.cpp index 74dd599a1..273aa6b79 100644 --- a/libyul/optimiser/UnusedAssignEliminator.cpp +++ b/libyul/optimiser/UnusedAssignEliminator.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include diff --git a/libyul/optimiser/UnusedStoreBase.cpp b/libyul/optimiser/UnusedStoreBase.cpp index 27700bdf7..8e34d172f 100644 --- a/libyul/optimiser/UnusedStoreBase.cpp +++ b/libyul/optimiser/UnusedStoreBase.cpp @@ -156,18 +156,3 @@ void UnusedStoreBase::merge(TrackedStores& _target, vector&& _sou merge(_target, move(ts)); _source.clear(); } - -void StatementRemover::operator()(Block& _block) -{ - util::iterateReplacing( - _block.statements, - [&](Statement& _statement) -> std::optional> - { - if (m_toRemove.count(&_statement)) - return {vector{}}; - else - return nullopt; - } - ); - ASTModifier::operator()(_block); -} diff --git a/libyul/optimiser/UnusedStoreBase.h b/libyul/optimiser/UnusedStoreBase.h index 3bd4e4297..15dccb04a 100644 --- a/libyul/optimiser/UnusedStoreBase.h +++ b/libyul/optimiser/UnusedStoreBase.h @@ -105,14 +105,4 @@ protected: size_t m_forLoopNestingDepth = 0; }; -class StatementRemover: public ASTModifier -{ -public: - explicit StatementRemover(std::set const& _toRemove): m_toRemove(_toRemove) {} - - void operator()(Block& _block) override; -private: - std::set const& m_toRemove; -}; - } diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index f6c9151eb..16e9078f3 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -34,7 +34,7 @@ else BUILD_DIR="$1" fi -# solbuildpackpusher/solidity-buildpack-deps:emscripten-8 +# solbuildpackpusher/solidity-buildpack-deps:emscripten-9 docker run -v "$(pwd):/root/project" -w /root/project \ - solbuildpackpusher/solidity-buildpack-deps@sha256:842d6074e0e7e5355c89122c1cafc1fdb59696596750e7d56e5f35c0d883ad59 \ + solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24\ ./scripts/ci/build_emscripten.sh "$BUILD_DIR" diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py index 9d35b9988..dd317735f 100755 --- a/scripts/bytecodecompare/prepare_report.py +++ b/scripts/bytecodecompare/prepare_report.py @@ -187,7 +187,7 @@ def parse_cli_output(source_file_name: Path, cli_output: str) -> FileReport: return file_report -def prepare_compiler_input( # pylint: disable=too-many-arguments +def prepare_compiler_input( compiler_path: Path, source_file_name: Path, optimize: bool, @@ -256,7 +256,7 @@ def detect_metadata_cli_option_support(compiler_path: Path): return process.returncode == 0 -def run_compiler( # pylint: disable=too-many-arguments +def run_compiler( compiler_path: Path, source_file_name: Path, optimize: bool, @@ -320,7 +320,7 @@ def run_compiler( # pylint: disable=too-many-arguments return parse_cli_output(Path(source_file_name), process.stdout) -def generate_report( # pylint: disable=too-many-arguments,too-many-locals +def generate_report( source_file_names: List[str], compiler_path: Path, interface: CompilerInterface, diff --git a/scripts/deps-ppa/static_z3.sh b/scripts/deps-ppa/static_z3.sh index e8dec0fae..353a8a8d9 100755 --- a/scripts/deps-ppa/static_z3.sh +++ b/scripts/deps-ppa/static_z3.sh @@ -25,9 +25,9 @@ set -ev keyid=70D110489D66E2F6 email=builds@ethereum.org packagename=z3-static -version=4.8.13 +version=4.8.14 -DISTRIBUTIONS="focal groovy hirsute" +DISTRIBUTIONS="focal hirsute impish jammy" for distribution in $DISTRIBUTIONS do diff --git a/scripts/docker/buildpack-deps/Dockerfile.emscripten b/scripts/docker/buildpack-deps/Dockerfile.emscripten index 5f86b1e19..0d1d1a28e 100644 --- a/scripts/docker/buildpack-deps/Dockerfile.emscripten +++ b/scripts/docker/buildpack-deps/Dockerfile.emscripten @@ -33,12 +33,12 @@ # Using $(em-config CACHE)/sysroot/usr seems to work, though, and still has cmake find the # dependencies automatically. FROM emscripten/emsdk:2.0.33 AS base -LABEL version="8" +LABEL version="9" ADD emscripten.jam /usr/src RUN set -ex; \ cd /usr/src; \ - git clone https://github.com/Z3Prover/z3.git -b z3-4.8.13 --depth 1 ; \ + git clone https://github.com/Z3Prover/z3.git -b z3-4.8.14 --depth 1 ; \ cd z3; \ mkdir build; \ cd build; \ diff --git a/scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz b/scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz index 1d00eabce..fb79ea946 100644 --- a/scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz +++ b/scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz @@ -22,7 +22,7 @@ # (c) 2016-2021 solidity contributors. #------------------------------------------------------------------------------ FROM gcr.io/oss-fuzz-base/base-clang:latest as base -LABEL version="14" +LABEL version="15" ARG DEBIAN_FRONTEND=noninteractive @@ -61,7 +61,7 @@ RUN set -ex; \ # Z3 RUN set -ex; \ - git clone --depth 1 -b z3-4.8.13 https://github.com/Z3Prover/z3.git \ + git clone --depth 1 -b z3-4.8.14 https://github.com/Z3Prover/z3.git \ /usr/src/z3; \ cd /usr/src/z3; \ mkdir build; \ diff --git a/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 b/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 index 8ed35d67e..3ecf9143a 100644 --- a/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 +++ b/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 @@ -22,7 +22,7 @@ # (c) 2016-2019 solidity contributors. #------------------------------------------------------------------------------ FROM buildpack-deps:focal AS base -LABEL version="9" +LABEL version="10" ARG DEBIAN_FRONTEND=noninteractive diff --git a/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang b/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang index 0ac9d4abf..d262fa5fa 100644 --- a/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang +++ b/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang @@ -22,7 +22,7 @@ # (c) 2016-2019 solidity contributors. #------------------------------------------------------------------------------ FROM buildpack-deps:focal AS base -LABEL version="9" +LABEL version="10" ARG DEBIAN_FRONTEND=noninteractive diff --git a/scripts/endToEndExtraction/remove-testcases.py b/scripts/endToEndExtraction/remove-testcases.py index 28822e044..ca9a63599 100755 --- a/scripts/endToEndExtraction/remove-testcases.py +++ b/scripts/endToEndExtraction/remove-testcases.py @@ -33,12 +33,12 @@ def parse_call(call): return function.strip(), arguments.strip(), results.strip() -def colorize(left, right, id): +def colorize(left, right, index): red = "\x1b[31m" yellow = "\x1b[33m" reset = "\x1b[0m" colors = [red, yellow] - color = colors[id % len(colors)] + color = colors[index % len(colors)] function, _arguments, _results = parse_call(right) left = left.replace("compileAndRun", color + "compileAndRun" + reset) right = right.replace("constructor", color + "constructor" + reset) diff --git a/scripts/endToEndExtraction/verify-testcases.py b/scripts/endToEndExtraction/verify-testcases.py index 8e1c60a79..b01187042 100755 --- a/scripts/endToEndExtraction/verify-testcases.py +++ b/scripts/endToEndExtraction/verify-testcases.py @@ -9,8 +9,6 @@ # # verify-testcases.py will compare both traces. If these traces are identical, the extracted tests were # identical with the tests specified in SolidityEndToEndTest.cpp. -# -# pylint: disable=too-many-instance-attributes import re import os @@ -32,11 +30,11 @@ class Trace: def get_input(self): return self._input - def set_input(self, input): + def set_input(self, bytecode): if self.kind == "create": # remove cbor encoded metadata from bytecode - length = int(input[-4:], 16) * 2 - self._input = input[:len(input) - length - 4] + length = int(bytecode[-4:], 16) * 2 + self._input = bytecode[:len(bytecode) - length - 4] def get_output(self): return self._output @@ -110,21 +108,21 @@ class TraceAnalyser: @staticmethod def parse_parameters(line, trace): - input = re.search(r'\s*in:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if input: - trace.input = input.group(1) - output = re.search(r'\s*out:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if output: - trace.output = output.group(1) - result = re.search(r'\s*result:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if result: - trace.result = result.group(1) - gas_used = re.search(r'\s*gas\sused:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if gas_used: - trace.gas = gas_used.group(1) - value = re.search(r'\s*value:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if value: - trace.value = value.group(1) + input_match = re.search(r'\s*in:\s*([a-fA-F0-9]*)', line, re.M | re.I) + if input_match: + trace.input = input_match.group(1) + output_match = re.search(r'\s*out:\s*([a-fA-F0-9]*)', line, re.M | re.I) + if output_match: + trace.output = output_match.group(1) + result_match = re.search(r'\s*result:\s*([a-fA-F0-9]*)', line, re.M | re.I) + if result_match: + trace.result = result_match.group(1) + gas_used_match = re.search(r'\s*gas\sused:\s*([a-fA-F0-9]*)', line, re.M | re.I) + if gas_used_match: + trace.gas = gas_used_match.group(1) + value_match = re.search(r'\s*value:\s*([a-fA-F0-9]*)', line, re.M | re.I) + if value_match: + trace.value = value_match.group(1) def diff(self, analyser): if not self.ready: @@ -154,7 +152,8 @@ class TraceAnalyser: print(len(intersection), "test-cases - ", len(mismatches), " mismatche(s)") - def check_traces(self, test_name, left, right, mismatches): + @classmethod + def check_traces(cls, test_name, left, right, mismatches): for trace_id, trace in enumerate(left.traces): left_trace = trace right_trace = right.traces[trace_id] diff --git a/scripts/error_codes.py b/scripts/error_codes.py index fe9c73b04..f3d35cae4 100755 --- a/scripts/error_codes.py +++ b/scripts/error_codes.py @@ -18,7 +18,7 @@ def read_file(file_name): with open(file_name, "r", encoding="latin-1" if is_latin else ENCODING) as f: content = f.read() finally: - if content == None: + if content is None: print(f"Error reading: {file_name}") return content @@ -44,11 +44,11 @@ def find_ids_in_source_file(file_name, id_to_file_names): if in_comment(source, m.start()): continue underscore_pos = m.group(0).index("_") - id = m.group(0)[0:underscore_pos] - if id in id_to_file_names: - id_to_file_names[id].append(file_name) + error_id = m.group(0)[0:underscore_pos] + if error_id in id_to_file_names: + id_to_file_names[error_id].append(file_name) else: - id_to_file_names[id] = [file_name] + id_to_file_names[error_id] = [file_name] def find_ids_in_source_files(file_names): @@ -76,16 +76,16 @@ def fix_ids_in_source_file(file_name, id_to_count, available_ids): destination.extend(source[k:m.start()]) underscore_pos = m.group(0).index("_") - id = m.group(0)[0:underscore_pos] + error_id = m.group(0)[0:underscore_pos] # incorrect id or id has a duplicate somewhere - if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or id_to_count[id] > 1): - assert id in id_to_count + if not in_comment(source, m.start()) and (len(error_id) != 4 or error_id[0] == "0" or id_to_count[error_id] > 1): + assert error_id in id_to_count new_id = get_next_id(available_ids) assert new_id not in id_to_count - id_to_count[id] -= 1 + id_to_count[error_id] -= 1 else: - new_id = id + new_id = error_id destination.extend(new_id + "_error") k = m.end() @@ -104,7 +104,7 @@ def fix_ids_in_source_files(file_names, id_to_count): id_to_count contains number of appearances of every id in sources """ - available_ids = {str(id) for id in range(1000, 10000)} - id_to_count.keys() + available_ids = {str(error_id) for error_id in range(1000, 10000)} - id_to_count.keys() for file_name in file_names: fix_ids_in_source_file(file_name, id_to_count, available_ids) @@ -113,8 +113,8 @@ def find_files(top_dir, sub_dirs, extensions): """Builds a list of files with given extensions in specified subdirectories""" source_file_names = [] - for dir in sub_dirs: - for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")): + for directory in sub_dirs: + for root, _, file_names in os.walk(os.path.join(top_dir, directory), onerror=lambda e: sys.exit(f"Walk error: {e}")): for file_name in file_names: _, ext = path.splitext(file_name) if ext in extensions: @@ -145,27 +145,27 @@ def find_ids_in_cmdline_test_err(file_name): def print_ids(ids): - for k, id in enumerate(sorted(ids)): + for k, error_id in enumerate(sorted(ids)): if k % 10 > 0: print(" ", end="") elif k > 0: print() - print(id, end="") + print(error_id, end="") def print_ids_per_file(ids, id_to_file_names, top_dir): file_name_to_ids = {} - for id in ids: - for file_name in id_to_file_names[id]: + for error_id in ids: + for file_name in id_to_file_names[error_id]: relpath = path.relpath(file_name, top_dir) if relpath not in file_name_to_ids: file_name_to_ids[relpath] = [] - file_name_to_ids[relpath].append(id) + file_name_to_ids[relpath].append(error_id) for file_name in sorted(file_name_to_ids): print(file_name) - for id in sorted(file_name_to_ids[file_name]): - print(f" {id}", end="") + for error_id in sorted(file_name_to_ids[file_name]): + print(f" {error_id}", end="") print() @@ -254,8 +254,6 @@ def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False): def main(argv): - # pylint: disable=too-many-branches, too-many-locals, too-many-statements - check = False fix = False no_confirm = False @@ -277,7 +275,7 @@ def main(argv): if [check, fix, examine_coverage, next_id].count(True) != 1: print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next") - exit(1) + sys.exit(1) cwd = os.getcwd() @@ -289,23 +287,23 @@ def main(argv): source_id_to_file_names = find_ids_in_source_files(source_file_names) ok = True - for id in sorted(source_id_to_file_names): - if len(id) != 4: - print(f"ID {id} length != 4") + for error_id in sorted(source_id_to_file_names): + if len(error_id) != 4: + print(f"ID {error_id} length != 4") ok = False - if id[0] == "0": - print(f"ID {id} starts with zero") + if error_id[0] == "0": + print(f"ID {error_id} starts with zero") ok = False - if len(source_id_to_file_names[id]) > 1: - print(f"ID {id} appears {len(source_id_to_file_names[id])} times") + if len(source_id_to_file_names[error_id]) > 1: + print(f"ID {error_id} appears {len(source_id_to_file_names[error_id])} times") ok = False if examine_coverage: if not ok: print("Incorrect IDs have to be fixed before applying --examine-coverage") - exit(1) + sys.exit(1) res = 0 if examine_id_coverage(cwd, source_id_to_file_names) else 1 - exit(res) + sys.exit(res) ok &= examine_id_coverage(cwd, source_id_to_file_names, new_ids_only=True) @@ -314,18 +312,18 @@ def main(argv): if next_id: if not ok: print("Incorrect IDs have to be fixed before applying --next") - exit(1) - available_ids = {str(id) for id in range(1000, 10000)} - source_id_to_file_names.keys() + sys.exit(1) + available_ids = {str(error_id) for error_id in range(1000, 10000)} - source_id_to_file_names.keys() next_id = get_next_id(available_ids) print(f"Next ID: {next_id}") - exit(0) + sys.exit(0) if ok: print("No incorrect IDs found") - exit(0) + sys.exit(0) if check: - exit(1) + sys.exit(1) assert fix, "Unexpected state, should not come here without --fix" @@ -338,14 +336,14 @@ def main(argv): while len(answer) == 0 or answer not in "YNyn": answer = input("[Y/N]? ") if answer not in "yY": - exit(1) + sys.exit(1) # number of appearances for every id - source_id_to_count = { id: len(file_names) for id, file_names in source_id_to_file_names.items() } + source_id_to_count = { error_id: len(file_names) for error_id, file_names in source_id_to_file_names.items() } fix_ids_in_source_files(source_file_names, source_id_to_count) print("Fixing completed") - exit(2) + sys.exit(2) if __name__ == "__main__": diff --git a/scripts/isolate_tests.py b/scripts/isolate_tests.py index 4abe6a0bd..e641d58d1 100755 --- a/scripts/isolate_tests.py +++ b/scripts/isolate_tests.py @@ -106,8 +106,8 @@ def write_cases(f, solidityTests, yulTests): # When code examples are extracted they are indented by 8 spaces, which violates the style guide, # so before checking remove 4 spaces from each line. remainder = dedent(test) - hash = hashlib.sha256(test.encode("utf-8")).hexdigest() - sol_filename = f'test_{hash}_{cleaned_filename}.{language}' + source_code_hash = hashlib.sha256(test.encode("utf-8")).hexdigest() + sol_filename = f'test_{source_code_hash}_{cleaned_filename}.{language}' with open(sol_filename, mode='w', encoding='utf8', newline='') as fi: fi.write(remainder) diff --git a/scripts/pylint_all.py b/scripts/pylint_all.py index 8dbe6a7fc..a60b000ed 100755 --- a/scripts/pylint_all.py +++ b/scripts/pylint_all.py @@ -6,9 +6,9 @@ Runs pylint on all Python files in project directories known to contain Python s from argparse import ArgumentParser from os import path, walk -from sys import exit from textwrap import dedent import subprocess +import sys PROJECT_ROOT = path.dirname(path.dirname(path.realpath(__file__))) PYLINT_RCFILE = f"{PROJECT_ROOT}/scripts/pylintrc" @@ -89,7 +89,7 @@ def main(): success = pylint_all_filenames(options.dev_mode, rootdirs) if not success: - exit(1) + sys.exit(1) else: print("No problems found.") @@ -98,4 +98,4 @@ if __name__ == "__main__": try: main() except KeyboardInterrupt: - exit("Interrupted by user. Exiting.") + sys.exit("Interrupted by user. Exiting.") diff --git a/scripts/pylintrc b/scripts/pylintrc index 50dbebee0..02d529567 100644 --- a/scripts/pylintrc +++ b/scripts/pylintrc @@ -14,24 +14,23 @@ # ATTENTION: This list should be extended with care, consider using NOLINT comments inside your # python files instead, as the goal is actually to reduce the list of globally disabled checks. # -# TODO: What could be eliminated in future PRs: bad-continuation, invalid-name, redefined-builtin, -# undefined-variable, unused-*, useless-object-inheritance. +# TODO: What could be eliminated in future PRs: invalid-name, pointless-string-statement, redefined-outer-name. disable= - bad-continuation, bad-indentation, bad-whitespace, - consider-using-sys-exit, duplicate-code, invalid-name, missing-docstring, no-else-return, - no-self-use, pointless-string-statement, - redefined-builtin, redefined-outer-name, - singleton-comparison, too-few-public-methods, + too-many-arguments, + too-many-branches, + too-many-instance-attributes, + too-many-locals, too-many-public-methods, + too-many-statements, ungrouped-imports [BASIC] diff --git a/scripts/regressions.py b/scripts/regressions.py index 046044197..e30c0aeb9 100755 --- a/scripts/regressions.py +++ b/scripts/regressions.py @@ -41,7 +41,8 @@ class regressor: "build/test/tools/ossfuzz") self._logpath = os.path.join(self._repo_root, "test_results") - def parseCmdLine(self, description, args): + @classmethod + def parseCmdLine(cls, description, args): argParser = ArgumentParser(description) argParser.add_argument('-o', '--out-dir', required=True, type=str, help="""Directory where test results will be written""") diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 60e931083..725649f80 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -57,7 +57,7 @@ packagename=solc static_build_distribution=hirsute -DISTRIBUTIONS="focal hirsute impish" +DISTRIBUTIONS="focal hirsute impish jammy" if is_release then diff --git a/scripts/splitSources.py b/scripts/splitSources.py index 181741f9c..d31202fb7 100755 --- a/scripts/splitSources.py +++ b/scripts/splitSources.py @@ -40,7 +40,7 @@ def writeSourceToFile(lines): filePath, srcName = extractSourceName(lines[0]) # print("sourceName is ", srcName) # print("filePath is", filePath) - if filePath != False: + if filePath: os.system("mkdir -p " + filePath) with open(srcName, mode='a+', encoding='utf8', newline='') as f: createdSources.append(srcName) diff --git a/scripts/wasm-rebuild/docker-scripts/isolate_tests.py b/scripts/wasm-rebuild/docker-scripts/isolate_tests.py index c447c1e67..41487325e 100755 --- a/scripts/wasm-rebuild/docker-scripts/isolate_tests.py +++ b/scripts/wasm-rebuild/docker-scripts/isolate_tests.py @@ -47,8 +47,8 @@ def write_cases(f, tests): cleaned_filename = f.replace(".","_").replace("-","_").replace(" ","_").lower() for test in tests: remainder = re.sub(r'^ {4}', '', test, 0, re.MULTILINE) - hash = hashlib.sha256(test).hexdigest() - with open(f'test_{hash}_{cleaned_filename}.sol', 'w', encoding='utf8') as _f: + source_code_hash = hashlib.sha256(test).hexdigest() + with open(f'test_{source_code_hash}_{cleaned_filename}.sol', 'w', encoding='utf8') as _f: _f.write(remainder) diff --git a/test/Common.cpp b/test/Common.cpp index 2a663aa1b..a702cddd6 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -102,6 +102,8 @@ void CommonOptions::addOptions() ("testpath", po::value(&this->testPath)->default_value(solidity::test::testPath()), "path to test files") ("vm", po::value>(&vmPaths), "path to evmc library, can be supplied multiple times.") ("ewasm", po::bool_switch(&ewasm)->default_value(ewasm), "tries to automatically find an ewasm vm and enable ewasm test-execution.") + ("batches", po::value(&this->batches)->default_value(1), "set number of batches to split the tests into") + ("selected-batch", po::value(&this->selectedBatch)->default_value(0), "zero-based number of batch to execute") ("no-semantic-tests", po::bool_switch(&disableSemanticTests)->default_value(disableSemanticTests), "disable semantic tests") ("no-smt", po::bool_switch(&disableSMT)->default_value(disableSMT), "disable SMT checker") ("optimize", po::bool_switch(&optimize)->default_value(optimize), "enables optimization") @@ -126,6 +128,17 @@ void CommonOptions::validate() const ConfigException, "Invalid test path specified." ); + assertThrow( + batches > 0, + ConfigException, + "Batches needs to be at least 1." + ); + assertThrow( + selectedBatch < batches, + ConfigException, + "Selected batch has to be less than number of batches." + ); + if (enforceGasTest) { assertThrow( diff --git a/test/Common.h b/test/Common.h index f7c8fa733..f3ea9e6f4 100644 --- a/test/Common.h +++ b/test/Common.h @@ -20,6 +20,7 @@ #include #include +#include #include @@ -67,6 +68,8 @@ struct CommonOptions bool useABIEncoderV1 = false; bool showMessages = false; bool showMetadata = false; + size_t batches = 1; + size_t selectedBatch = 0; langutil::EVMVersion evmVersion() const; @@ -96,4 +99,27 @@ bool isValidSemanticTestPath(boost::filesystem::path const& _testPath); bool loadVMs(CommonOptions const& _options); +/** + * Component to help with splitting up all tests into batches. + */ +class Batcher +{ +public: + Batcher(size_t _offset, size_t _batches): + m_offset(_offset), + m_batches(_batches) + { + solAssert(m_batches > 0 && m_offset < m_batches); + } + Batcher(Batcher const&) = delete; + Batcher& operator=(Batcher const&) = delete; + + bool checkAndAdvance() { return (m_counter++) % m_batches == m_offset; } + +private: + size_t const m_offset; + size_t const m_batches; + size_t m_counter = 0; +}; + } diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 6fcd684f4..bfb546736 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -30,6 +30,7 @@ #pragma warning(disable:4535) // calling _set_se_translator requires /EHa #endif #include +#include #if defined(_MSC_VER) #pragma warning(pop) #endif @@ -60,6 +61,41 @@ void removeTestSuite(std::string const& _name) master.remove(id); } +/** + * Class that traverses the boost test tree and removes unit tests that are + * not in the current batch. + */ +class BoostBatcher: public test_tree_visitor +{ +public: + BoostBatcher(solidity::test::Batcher& _batcher): + m_batcher(_batcher) + {} + + void visit(test_case const& _testCase) override + { + if (!m_batcher.checkAndAdvance()) + // disabling them would be nicer, but it does not work like this: + // const_cast(_testCase).p_run_status.value = test_unit::RS_DISABLED; + m_path.back()->remove(_testCase.p_id); + } + bool test_suite_start(test_suite const& _testSuite) override + { + m_path.push_back(&const_cast(_testSuite)); + return test_tree_visitor::test_suite_start(_testSuite); + } + void test_suite_finish(test_suite const& _testSuite) override + { + m_path.pop_back(); + test_tree_visitor::test_suite_finish(_testSuite); + } + +private: + solidity::test::Batcher& m_batcher; + std::vector m_path; +}; + + void runTestCase(TestCase::Config const& _config, TestCase::TestCaseCreator const& _testCaseCreator) { try @@ -100,7 +136,8 @@ int registerTests( bool _enforceViaYul, bool _enforceCompileToEwasm, vector const& _labels, - TestCase::TestCaseCreator _testCaseCreator + TestCase::TestCaseCreator _testCaseCreator, + solidity::test::Batcher& _batcher ) { int numTestsAdded = 0; @@ -131,33 +168,38 @@ int registerTests( _enforceViaYul, _enforceCompileToEwasm, _labels, - _testCaseCreator + _testCaseCreator, + _batcher ); _suite.add(sub_suite); } else { - // This must be a vector of unique_ptrs because Boost.Test keeps the equivalent of a string_view to the filename - // that is passed in. If the strings were stored directly in the vector, pointers/references to them would be - // invalidated on reallocation. - static vector> filenames; + // TODO would be better to set the test to disabled. + if (_batcher.checkAndAdvance()) + { + // This must be a vector of unique_ptrs because Boost.Test keeps the equivalent of a string_view to the filename + // that is passed in. If the strings were stored directly in the vector, pointers/references to them would be + // invalidated on reallocation. + static vector> filenames; - filenames.emplace_back(make_unique(_path.string())); - auto test_case = make_test_case( - [config, _testCaseCreator] - { - BOOST_REQUIRE_NO_THROW({ - runTestCase(config, _testCaseCreator); - }); - }, - _path.stem().string(), - *filenames.back(), - 0 - ); - for (auto const& _label: _labels) - test_case->add_label(_label); - _suite.add(test_case); - numTestsAdded = 1; + filenames.emplace_back(make_unique(_path.string())); + auto test_case = make_test_case( + [config, _testCaseCreator] + { + BOOST_REQUIRE_NO_THROW({ + runTestCase(config, _testCaseCreator); + }); + }, + _path.stem().string(), + *filenames.back(), + 0 + ); + for (auto const& _label: _labels) + test_case->add_label(_label); + _suite.add(test_case); + numTestsAdded = 1; + } } return numTestsAdded; } @@ -172,6 +214,7 @@ void initializeOptions() solidity::test::CommonOptions::setSingleton(std::move(options)); } + } // TODO: Prototype -- why isn't this declared in the boost headers? @@ -180,6 +223,8 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ); test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) { + using namespace solidity::test; + master_test_suite_t& master = framework::master_test_suite(); master.p_name.value = "SolidityTests"; @@ -191,6 +236,17 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) if (solidity::test::CommonOptions::get().disableSemanticTests) cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl; + if (!solidity::test::CommonOptions::get().enforceGasTest) + cout << endl << "WARNING :: Gas Cost Expectations are not being enforced" << endl << endl; + + Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches); + if (CommonOptions::get().batches > 1) + cout << "Batch " << CommonOptions::get().selectedBatch << " out of " << CommonOptions::get().batches << endl; + + // Batch the boost tests + BoostBatcher boostBatcher(batcher); + traverse_test_tree(master, boostBatcher, true); + // Include the interactive tests in the automatic tests as well for (auto const& ts: g_interactiveTestsuites) { @@ -202,15 +258,19 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests) continue; - solAssert(registerTests( + //TODO + //solAssert( + registerTests( master, options.testPath / ts.path, ts.subpath, options.enforceViaYul, options.enforceCompileToEwasm, ts.labels, - ts.testCaseCreator - ) > 0, std::string("no ") + ts.title + " tests found"); + ts.testCaseCreator, + batcher + ); + // > 0, std::string("no ") + ts.title + " tests found"); } if (solidity::test::CommonOptions::get().disableSemanticTests) diff --git a/test/externalTests.sh b/test/externalTests.sh index 86f13401e..560ca2744 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -42,3 +42,8 @@ printTask "Running external tests..." "$REPO_ROOT/externalTests/gnosis-v2.sh" "$@" "$REPO_ROOT/externalTests/colony.sh" "$@" "$REPO_ROOT/externalTests/ens.sh" "$@" +"$REPO_ROOT/externalTests/trident.sh" "$@" +"$REPO_ROOT/externalTests/euler.sh" "$@" +"$REPO_ROOT/externalTests/yield-liquidator.sh" "$@" +"$REPO_ROOT/externalTests/bleeps.sh" "$@" +"$REPO_ROOT/externalTests/pool-together.sh" "$@" diff --git a/test/externalTests/bleeps.sh b/test/externalTests/bleeps.sh new file mode 100755 index 000000000..df0e5e39e --- /dev/null +++ b/test/externalTests/bleeps.sh @@ -0,0 +1,84 @@ +#!/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) 2022 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +source scripts/common.sh +source test/externalTests/common.sh + +verify_input "$@" +BINARY_TYPE="$1" +BINARY_PATH="$2" + +function compile_fn { npm run compile; } +function test_fn { npm run test; } + +function bleeps_test +{ + local repo="https://github.com/wighawag/bleeps" + local ref_type=tag + local ref=bleeps_migrations # TODO: There's a 0.4.19 contract in 'main' that would need patching for the latest compiler. + local config_file="hardhat.config.ts" + local config_var=config + + local compile_only_presets=() + local settings_presets=( + "${compile_only_presets[@]}" + #ir-no-optimize # Compilation fails with: "YulException: Variable param_0 is 2 slot(s) too deep inside the stack." + #ir-optimize-evm-only # Compilation fails with: "YulException: Variable param_0 is 2 slot(s) too deep inside the stack." + ir-optimize-evm+yul + #legacy-no-optimize # Compilation fails with: "CompilerError: Stack too deep, try removing local variables." + #legacy-optimize-evm-only # Compilation fails with: "CompilerError: Stack too deep, try removing local variables." + legacy-optimize-evm+yul + ) + + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" + + setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" + download_project "$repo" "$ref_type" "$ref" "$DIR" + + pushd "common-lib/" + neutralize_package_json_hooks + npm install + npm run build + popd + + pushd "contracts/" + sed -i 's|"bleeps-common": "workspace:\*",|"bleeps-common": "file:../common-lib/",|g' package.json + + neutralize_package_lock + neutralize_package_json_hooks + force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" + force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var" + npm install npm-run-all + npm install + + replace_version_pragmas + + for preset in $SELECTED_PRESETS; do + hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var" + done + + popd +} + +external_test Bleeps bleeps_test diff --git a/test/externalTests/colony.sh b/test/externalTests/colony.sh index 9bebc2722..8386b0b49 100755 --- a/test/externalTests/colony.sh +++ b/test/externalTests/colony.sh @@ -27,6 +27,7 @@ source test/externalTests/common.sh verify_input "$@" BINARY_TYPE="$1" BINARY_PATH="$2" +SELECTED_PRESETS="$3" function compile_fn { yarn run provision:token:contracts; } function test_fn { yarn run test:contracts; } @@ -34,7 +35,8 @@ function test_fn { yarn run test:contracts; } function colony_test { local repo="https://github.com/solidity-external-tests/colonyNetwork.git" - local branch=develop_080 + local ref_type=branch + local ref="develop_080" local config_file="truffle.js" local compile_only_presets=( @@ -49,16 +51,15 @@ function colony_test legacy-optimize-evm+yul ) - local selected_optimizer_presets - selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") - print_optimizer_presets_or_exit "$selected_optimizer_presets" + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" - download_project "$repo" "$branch" "$DIR" + download_project "$repo" "$ref_type" "$ref" "$DIR" [[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH" neutralize_package_json_hooks - force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" + force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$SELECTED_PRESETS")" yarn install git submodule update --init @@ -70,7 +71,7 @@ function colony_test replace_version_pragmas [[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" - for preset in $selected_optimizer_presets; do + for preset in $SELECTED_PRESETS; do truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn done } diff --git a/test/externalTests/common.sh b/test/externalTests/common.sh index 400304f3b..37bd8d74d 100644 --- a/test/externalTests/common.sh +++ b/test/externalTests/common.sh @@ -24,7 +24,16 @@ set -e CURRENT_EVM_VERSION=london -function print_optimizer_presets_or_exit +AVAILABLE_PRESETS=( + legacy-no-optimize + ir-no-optimize + legacy-optimize-evm-only + ir-optimize-evm-only + legacy-optimize-evm+yul + ir-optimize-evm+yul +) + +function print_presets_or_exit { local selected_presets="$1" @@ -37,10 +46,22 @@ function verify_input { local binary_type="$1" local binary_path="$2" + local selected_presets="$3" - (( $# == 2 )) || fail "Usage: $0 native|solcjs " + (( $# >= 2 && $# <= 3 )) || fail "Usage: $0 native|solcjs [preset]" [[ $binary_type == native || $binary_type == solcjs ]] || fail "Invalid binary type: '${binary_type}'. Must be either 'native' or 'solcjs'." [[ -f "$binary_path" ]] || fail "The compiler binary does not exist at '${binary_path}'" + + if [[ $selected_presets != "" ]] + then + for preset in $selected_presets + do + if [[ " ${AVAILABLE_PRESETS[*]} " != *" $preset "* ]] + then + fail "Preset '${preset}' does not exist. Available presets: ${AVAILABLE_PRESETS[*]}." + fi + done + fi } function setup_solc @@ -77,12 +98,24 @@ function setup_solc function download_project { local repo="$1" - local solcjs_branch="$2" - local test_dir="$3" + local ref_type="$2" + local solcjs_ref="$3" + local test_dir="$4" - printLog "Cloning $solcjs_branch of $repo..." - git clone --depth 1 "$repo" -b "$solcjs_branch" "$test_dir/ext" - cd ext + [[ $ref_type == commit || $ref_type == branch || $ref_type == tag ]] || assertFail + + printLog "Cloning ${ref_type} ${solcjs_ref} of ${repo}..." + if [[ $ref_type == commit ]]; then + mkdir ext + cd ext + git init + git remote add origin "$repo" + git fetch --depth 1 origin "$solcjs_ref" + git reset --hard FETCH_HEAD + else + git clone --depth 1 "$repo" -b "$solcjs_ref" "$test_dir/ext" + cd ext + fi echo "Current commit hash: $(git rev-parse HEAD)" } @@ -117,6 +150,19 @@ function neutralize_package_json_hooks sed -i 's|"prepare": *".*"|"prepare": ""|g' package.json } +function neutralize_packaged_contracts +{ + # Frameworks will build contracts from any package that contains a configuration file. + # This is both unnecessary (any files imported from these packages will get compiled again as a + # part of the main project anyway) and trips up our version check because it won't use our + # custom compiler binary. + printLog "Removing framework config and artifacts from npm packages..." + find node_modules/ -type f '(' -name 'hardhat.config.*' -o -name 'truffle-config.*' ')' -delete + + # Some npm packages also come packaged with pre-built artifacts. + find node_modules/ -path '*artifacts/build-info/*.json' -delete +} + function force_solc_modules { local custom_solcjs_path="${1:-solc/}" @@ -176,14 +222,37 @@ function force_hardhat_compiler_binary echo "Config file: ${config_file}" echo "Binary type: ${binary_type}" echo "Compiler path: ${solc_path}" - hardhat_solc_build_subtask "$SOLCVERSION_SHORT" "$SOLCVERSION" "$binary_type" "$solc_path" >> "$config_file" + + local language="${config_file##*.}" + hardhat_solc_build_subtask "$SOLCVERSION_SHORT" "$SOLCVERSION" "$binary_type" "$solc_path" "$language" >> "$config_file" +} + +function force_hardhat_unlimited_contract_size +{ + local config_file="$1" + local config_var_name="$2" + + printLog "Configuring Hardhat..." + echo "-------------------------------------" + echo "Allow unlimited contract size: true" + echo "-------------------------------------" + + if [[ $config_file == *\.js ]]; then + [[ $config_var_name == "" ]] || assertFail + echo "module.exports.networks.hardhat.allowUnlimitedContractSize = true" >> "$config_file" + else + [[ $config_file == *\.ts ]] || assertFail + [[ $config_var_name != "" ]] || assertFail + echo "${config_var_name}.networks!.hardhat!.allowUnlimitedContractSize = true" >> "$config_file" + fi } function force_hardhat_compiler_settings { local config_file="$1" local preset="$2" - local evm_version="${3:-"$CURRENT_EVM_VERSION"}" + local config_var_name="$3" + local evm_version="${4:-"$CURRENT_EVM_VERSION"}" printLog "Configuring Hardhat..." echo "-------------------------------------" @@ -195,10 +264,16 @@ function force_hardhat_compiler_settings echo "Compiler version (full): ${SOLCVERSION}" echo "-------------------------------------" - { - echo -n 'module.exports["solidity"] = ' - hardhat_compiler_settings "$SOLCVERSION_SHORT" "$preset" "$evm_version" - } >> "$config_file" + local settings + settings=$(hardhat_compiler_settings "$SOLCVERSION_SHORT" "$preset" "$evm_version") + if [[ $config_file == *\.js ]]; then + [[ $config_var_name == "" ]] || assertFail + echo "module.exports['solidity'] = ${settings}" >> "$config_file" + else + [[ $config_file == *\.ts ]] || assertFail + [[ $config_var_name != "" ]] || assertFail + echo "${config_var_name}.solidity = {compilers: [${settings}]}" >> "$config_file" + fi } function truffle_verify_compiler_version @@ -216,8 +291,12 @@ function hardhat_verify_compiler_version local full_solc_version="$2" printLog "Verify that the correct version (${solc_version}/${full_solc_version}) of the compiler was used to compile the contracts..." - grep '"solcVersion": "'"${solc_version}"'"' --with-filename artifacts/build-info/*.json || fail "Wrong compiler version detected." - grep '"solcLongVersion": "'"${full_solc_version}"'"' --with-filename artifacts/build-info/*.json || fail "Wrong compiler version detected." + local build_info_files + build_info_files=$(find . -path '*artifacts/build-info/*.json') + for build_info_file in $build_info_files; do + grep '"solcVersion": "'"${solc_version}"'"' --with-filename "$build_info_file" || fail "Wrong compiler version detected in ${build_info_file}." + grep '"solcLongVersion": "'"${full_solc_version}"'"' --with-filename "$build_info_file" || fail "Wrong compiler version detected in ${build_info_file}." + done } function truffle_clean @@ -249,6 +328,8 @@ function settings_from_preset local preset="$1" local evm_version="$2" + [[ " ${AVAILABLE_PRESETS[*]} " == *" $preset "* ]] || assertFail + case "$preset" in # NOTE: Remember to update `parallelism` of `t_ems_ext` job in CI config if you add/remove presets legacy-no-optimize) echo "{evmVersion: '${evm_version}', viaIR: false, optimizer: {enabled: false}}" ;; @@ -292,16 +373,27 @@ function hardhat_solc_build_subtask { local full_solc_version="$2" local binary_type="$3" local solc_path="$4" + local language="$5" [[ $binary_type == native || $binary_type == solcjs ]] || assertFail [[ $binary_type == native ]] && local is_solcjs=false [[ $binary_type == solcjs ]] && local is_solcjs=true - echo "const {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} = require('hardhat/builtin-tasks/task-names');" - echo "const assert = require('assert');" - echo - echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args, hre, runSuper) => {" + if [[ $language == js ]]; then + echo "const {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} = require('hardhat/builtin-tasks/task-names');" + echo "const assert = require('assert');" + echo + echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args, hre, runSuper) => {" + else + [[ $language == ts ]] || assertFail + echo "import {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} from 'hardhat/builtin-tasks/task-names';" + echo "import assert = require('assert');" + echo "import {subtask} from 'hardhat/config';" + echo + echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args: any, _hre: any, _runSuper: any) => {" + fi + echo " assert(args.solcVersion == '${solc_version}', 'Unexpected solc version: ' + args.solcVersion)" echo " return {" echo " compilerPath: '$(realpath "$solc_path")'," @@ -367,9 +459,10 @@ function hardhat_run_test local compile_only_presets="$3" local compile_fn="$4" local test_fn="$5" + local config_var_name="$6" hardhat_clean - force_hardhat_compiler_settings "$config_file" "$preset" + force_hardhat_compiler_settings "$config_file" "$preset" "$config_var_name" compile_and_run_test compile_fn test_fn hardhat_verify_compiler_version "$preset" "$compile_only_presets" } diff --git a/test/externalTests/ens.sh b/test/externalTests/ens.sh index f425f97cf..d3fcb0b13 100755 --- a/test/externalTests/ens.sh +++ b/test/externalTests/ens.sh @@ -27,49 +27,48 @@ source test/externalTests/common.sh verify_input "$@" BINARY_TYPE="$1" BINARY_PATH="$2" +SELECTED_PRESETS="$3" -function compile_fn { npx truffle compile; } -function test_fn { npm run test; } +function compile_fn { yarn build; } +function test_fn { yarn test; } function ens_test { - local repo="https://github.com/ensdomains/ens.git" - local branch=master - local config_file="truffle.js" + local repo="https://github.com/ensdomains/ens-contracts.git" + local ref_type=tag + local ref="v0.0.8" # The project is in flux right now and master might be too unstable for us + local config_file="hardhat.config.js" - local compile_only_presets=() + local compile_only_presets=( + legacy-no-optimize # Compiles but tests fail to deploy GovernorCompatibilityBravo (code too large). + ) local settings_presets=( "${compile_only_presets[@]}" - #ir-no-optimize # "YulException: Variable var_ttl_236 is 1 slot(s) too deep inside the stack." - #ir-optimize-evm-only # "YulException: Variable var_ttl_236 is 1 slot(s) too deep inside the stack." - ir-optimize-evm+yul - legacy-no-optimize + #ir-no-optimize # Compilation fails with "YulException: Variable var__945 is 1 slot(s) too deep inside the stack." + #ir-optimize-evm-only # Compilation fails with "YulException: Variable var__945 is 1 slot(s) too deep inside the stack." + #ir-optimize-evm+yul # Compilation fails with "YulException: Variable _5 is 1 too deep in the stack [ _5 usr$i usr$h _7 usr$scratch usr$k usr$f _4 usr$len usr$j_2 RET _2 _1 var_data_mpos usr$totallen usr$x _12 ]" legacy-optimize-evm-only legacy-optimize-evm+yul ) - local selected_optimizer_presets - selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") - print_optimizer_presets_or_exit "$selected_optimizer_presets" + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" - download_project "$repo" "$branch" "$DIR" - [[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH" - - # Use latest Truffle. Older versions crash on the output from 0.8.0. - force_truffle_version ^5.1.55 + download_project "$repo" "$ref_type" "$ref" "$DIR" neutralize_package_lock neutralize_package_json_hooks - force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" - npm install + force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" + force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" + yarn install replace_version_pragmas - [[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" + neutralize_packaged_contracts - for preset in $selected_optimizer_presets; do - truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn + for preset in $SELECTED_PRESETS; do + hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn done } -external_test Ens ens_test +external_test ENS ens_test diff --git a/test/externalTests/euler.sh b/test/externalTests/euler.sh new file mode 100755 index 000000000..485d7ae56 --- /dev/null +++ b/test/externalTests/euler.sh @@ -0,0 +1,74 @@ +#!/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) 2022 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +source scripts/common.sh +source test/externalTests/common.sh + +verify_input "$@" +BINARY_TYPE="$1" +BINARY_PATH="$2" +SELECTED_PRESETS="$3" + +function compile_fn { npm run compile; } +function test_fn { npx --no hardhat --no-compile test; } + +function euler_test +{ + local repo="https://github.com/euler-xyz/euler-contracts" + local ref_type=branch + local ref="master" + local config_file="hardhat.config.js" + + local compile_only_presets=() + local settings_presets=( + "${compile_only_presets[@]}" + #ir-no-optimize # Compilation fails with "YulException: Variable var_utilisation_307 is 6 slot(s) too deep inside the stack." + #ir-optimize-evm-only # Compilation fails with "YulException: Variable var_utilisation_307 is 6 slot(s) too deep inside the stack." + #ir-optimize-evm+yul # Compilation fails with "YulException: Variable var_status_mpos is 3 too deep in the stack" + legacy-optimize-evm-only + legacy-optimize-evm+yul + legacy-no-optimize + ) + + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" + + setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" + download_project "$repo" "$ref_type" "$ref" "$DIR" + + neutralize_package_lock + neutralize_package_json_hooks + force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" + force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" + force_hardhat_unlimited_contract_size "$config_file" + npm install + + replace_version_pragmas + neutralize_packaged_contracts + + for preset in $SELECTED_PRESETS; do + hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn + done +} + +external_test Euler euler_test diff --git a/test/externalTests/gnosis-v2.sh b/test/externalTests/gnosis-v2.sh index f963c74a0..16f3d6f62 100755 --- a/test/externalTests/gnosis-v2.sh +++ b/test/externalTests/gnosis-v2.sh @@ -27,6 +27,7 @@ source test/externalTests/common.sh verify_input "$@" BINARY_TYPE="$1" BINARY_PATH="$2" +SELECTED_PRESETS="$3" function compile_fn { npx truffle compile; } function test_fn { npm test; } @@ -34,7 +35,8 @@ function test_fn { npm test; } function gnosis_safe_test { local repo="https://github.com/solidity-external-tests/safe-contracts.git" - local branch=v2_080 + local ref_type=branch + local ref="v2_080" local config_file="truffle-config.js" local compile_only_presets=( @@ -49,12 +51,11 @@ function gnosis_safe_test legacy-optimize-evm+yul ) - local selected_optimizer_presets - selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") - print_optimizer_presets_or_exit "$selected_optimizer_presets" + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" - download_project "$repo" "$branch" "$DIR" + download_project "$repo" "$ref_type" "$ref" "$DIR" [[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH" sed -i 's|github:gnosis/mock-contract#sol_0_5_0|github:solidity-external-tests/mock-contract#master_080|g' package.json @@ -62,13 +63,13 @@ function gnosis_safe_test neutralize_package_lock neutralize_package_json_hooks - force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" + force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$SELECTED_PRESETS")" npm install --package-lock replace_version_pragmas [[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" - for preset in $selected_optimizer_presets; do + for preset in $SELECTED_PRESETS; do truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn done } diff --git a/test/externalTests/gnosis.sh b/test/externalTests/gnosis.sh index 7d8bc2442..62dea10df 100755 --- a/test/externalTests/gnosis.sh +++ b/test/externalTests/gnosis.sh @@ -27,6 +27,7 @@ source test/externalTests/common.sh verify_input "$@" BINARY_TYPE="$1" BINARY_PATH="$2" +SELECTED_PRESETS="$3" function compile_fn { npx truffle compile; } function test_fn { npm test; } @@ -34,7 +35,8 @@ function test_fn { npm test; } function gnosis_safe_test { local repo="https://github.com/solidity-external-tests/safe-contracts.git" - local branch=development_080 + local ref_type=branch + local ref="development_080" local config_file="truffle-config.js" local compile_only_presets=() @@ -48,25 +50,24 @@ function gnosis_safe_test legacy-optimize-evm+yul ) - local selected_optimizer_presets - selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") - print_optimizer_presets_or_exit "$selected_optimizer_presets" + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" - download_project "$repo" "$branch" "$DIR" + download_project "$repo" "$ref_type" "$ref" "$DIR" [[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH" sed -i 's|github:gnosis/mock-contract#sol_0_5_0|github:solidity-external-tests/mock-contract#master_080|g' package.json neutralize_package_lock neutralize_package_json_hooks - force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" + force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$SELECTED_PRESETS")" npm install --package-lock replace_version_pragmas [[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" - for preset in $selected_optimizer_presets; do + for preset in $SELECTED_PRESETS; do truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn done } diff --git a/test/externalTests/pool-together.sh b/test/externalTests/pool-together.sh new file mode 100755 index 000000000..1cabd6a50 --- /dev/null +++ b/test/externalTests/pool-together.sh @@ -0,0 +1,78 @@ +#!/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) 2022 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +source scripts/common.sh +source test/externalTests/common.sh + +verify_input "$@" +BINARY_TYPE="$1" +BINARY_PATH="$2" +SELECTED_PRESETS="$3" + +function compile_fn { yarn compile; } +function test_fn { yarn test; } + +function pool_together_test +{ + local repo="https://github.com/pooltogether/v4-core" + local ref_type=branch + local ref=master + local config_file="hardhat.config.ts" + local config_var="config" + + local compile_only_presets=() + local settings_presets=( + "${compile_only_presets[@]}" + #ir-no-optimize # Compilation fails with "YulException: Variable var_amount_205 is 9 slot(s) too deep inside the stack." + #ir-optimize-evm-only # Compilation fails with "YulException: Variable var_amount_205 is 9 slot(s) too deep inside the stack." + #ir-optimize-evm+yul # FIXME: ICE due to https://github.com/ethereum/solidity/issues/12558 + legacy-no-optimize + legacy-optimize-evm-only + legacy-optimize-evm+yul + ) + + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" + + setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" + download_project "$repo" "$ref_type" "$ref" "$DIR" + + neutralize_package_lock + neutralize_package_json_hooks + force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" + force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var" + yarn install + + # These come with already compiled artifacts. We want them recompiled with latest compiler. + rm -r node_modules/@pooltogether/yield-source-interface/artifacts/ + rm -r node_modules/@pooltogether/uniform-random-number/artifacts/ + rm -r node_modules/@pooltogether/owner-manager-contracts/artifacts/ + + replace_version_pragmas + + for preset in $SELECTED_PRESETS; do + hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var" + done +} + +external_test Pool-Together-V4 pool_together_test diff --git a/test/externalTests/trident.sh b/test/externalTests/trident.sh new file mode 100755 index 000000000..26c3d3810 --- /dev/null +++ b/test/externalTests/trident.sh @@ -0,0 +1,97 @@ +#!/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) 2021 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +source scripts/common.sh +source test/externalTests/common.sh + +verify_input "$@" +BINARY_TYPE="$1" +BINARY_PATH="$2" + +function compile_fn { yarn build; } + +function test_fn { + # shellcheck disable=SC2046 + TS_NODE_TRANSPILE_ONLY=1 npx hardhat test --no-compile $( + # TODO: We need to skip Migration.test.ts because it fails and makes other tests fail too. + # Replace this with `yarn test` once https://github.com/sushiswap/trident/issues/283 is fixed. + find test/ -name "*.test.ts" ! -path "test/Migration.test.ts" | LC_ALL=C sort + ) +} + +function trident_test +{ + local repo="https://github.com/sushiswap/trident" + local ref_type=commit + # FIXME: Switch back to master branch when https://github.com/sushiswap/trident/issues/303 gets fixed. + local ref="0cab5ae884cc9a41223d52791be775c3a053cb26" # master as of 2021-12-16 + local config_file="hardhat.config.ts" + local config_var=config + + local compile_only_presets=() + local settings_presets=( + "${compile_only_presets[@]}" + #ir-no-optimize # Compilation fails with: "YulException: Variable var_amount_165 is 9 slot(s) too deep inside the stack." + #ir-optimize-evm-only # Compilation fails with: "YulException: Variable var_amount_165 is 9 slot(s) too deep inside the stack." + #ir-optimize-evm+yul # Compilation fails with: "YulException: Cannot swap Variable var_nearestTick with Variable _4: too deep in the stack by 4 slots" + legacy-no-optimize + legacy-optimize-evm-only + legacy-optimize-evm+yul + ) + + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" + + setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" + download_project "$repo" "$ref_type" "$ref" "$DIR" + + # TODO: Currently tests work only with the exact versions from yarn.lock. + # Re-enable this when https://github.com/sushiswap/trident/issues/284 is fixed. + #neutralize_package_lock + + neutralize_package_json_hooks + force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" + force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var" + yarn install + + replace_version_pragmas + force_solc_modules "${DIR}/solc" + + # BentoBoxV1Flat.sol requires a few small tweaks to compile on 0.8.x. + # TODO: Remove once https://github.com/sushiswap/trident/pull/282 gets merged. + sed -i 's|uint128(-1)|type(uint128).max|g' contracts/flat/BentoBoxV1Flat.sol + sed -i 's|uint64(-1)|type(uint64).max|g' contracts/flat/BentoBoxV1Flat.sol + sed -i 's|uint32(-1)|type(uint32).max|g' contracts/flat/BentoBoxV1Flat.sol + sed -i 's|IERC20(0)|IERC20(address(0))|g' contracts/flat/BentoBoxV1Flat.sol + sed -i 's|IStrategy(0)|IStrategy(address(0))|g' contracts/flat/BentoBoxV1Flat.sol + + # @sushiswap/core package contains contracts that get built with 0.6.12 and fail our compiler + # version check. It's not used by tests so we can remove it. + rm -r node_modules/@sushiswap/core/ + + for preset in $SELECTED_PRESETS; do + hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var" + done +} + +external_test Trident trident_test diff --git a/test/externalTests/yield-liquidator.sh b/test/externalTests/yield-liquidator.sh new file mode 100755 index 000000000..6712941e7 --- /dev/null +++ b/test/externalTests/yield-liquidator.sh @@ -0,0 +1,74 @@ +#!/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) 2022 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +source scripts/common.sh +source test/externalTests/common.sh + +verify_input "$@" +BINARY_TYPE="$1" +BINARY_PATH="$2" +SELECTED_PRESETS="$3" + +function compile_fn { npm run build; } +function test_fn { npm run test; } + +function yield_liquidator_test +{ + local repo="https://github.com/yieldprotocol/yield-liquidator-v2" + local ref_type=branch + local ref="master" + local config_file="hardhat.config.ts" + local config_var="module.exports" + + local compile_only_presets=() + local settings_presets=( + "${compile_only_presets[@]}" + #ir-no-optimize # Compilation fails with "YulException: Variable var_roles_168_mpos is 2 slot(s) too deep inside the stack." + #ir-optimize-evm-only # Compilation fails with "YulException: Variable var__33 is 6 slot(s) too deep inside the stack." + ir-optimize-evm+yul + legacy-optimize-evm-only + legacy-optimize-evm+yul + legacy-no-optimize + ) + + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" + + setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" + download_project "$repo" "$ref_type" "$ref" "$DIR" + + neutralize_package_lock + neutralize_package_json_hooks + force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" + force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var" + force_hardhat_unlimited_contract_size "$config_file" "$config_var" + npm install + + replace_version_pragmas + + for preset in $SELECTED_PRESETS; do + hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var" + done +} + +external_test Yield-Liquidator-V2 yield_liquidator_test diff --git a/test/externalTests/zeppelin.sh b/test/externalTests/zeppelin.sh index 9865c7e40..5c54ceb8b 100755 --- a/test/externalTests/zeppelin.sh +++ b/test/externalTests/zeppelin.sh @@ -27,6 +27,7 @@ source test/externalTests/common.sh verify_input "$@" BINARY_TYPE="$1" BINARY_PATH="$2" +SELECTED_PRESETS="$3" function compile_fn { npm run compile; } function test_fn { npm test; } @@ -34,7 +35,8 @@ function test_fn { npm test; } function zeppelin_test { local repo="https://github.com/OpenZeppelin/openzeppelin-contracts.git" - local branch=master + local ref_type=branch + local ref="master" local config_file="hardhat.config.js" local compile_only_presets=( @@ -49,21 +51,20 @@ function zeppelin_test legacy-optimize-evm+yul ) - local selected_optimizer_presets - selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") - print_optimizer_presets_or_exit "$selected_optimizer_presets" + [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}") + print_presets_or_exit "$SELECTED_PRESETS" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" - download_project "$repo" "$branch" "$DIR" + download_project "$repo" "$ref_type" "$ref" "$DIR" neutralize_package_json_hooks force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" - force_hardhat_compiler_settings "$config_file" "$(first_word "$selected_optimizer_presets")" + force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" npm install replace_version_pragmas - for preset in $selected_optimizer_presets; do + for preset in $SELECTED_PRESETS; do hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn done } diff --git a/test/formal/rule.py b/test/formal/rule.py index ac0f0c8a6..8fbf00688 100644 --- a/test/formal/rule.py +++ b/test/formal/rule.py @@ -39,6 +39,7 @@ class Rule: self.error('Rule is incorrect.\nModel: ' + str(m)) self.solver.pop() - def error(self, msg): + @classmethod + def error(cls, msg): print(msg) sys.exit(1) diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index 6ee55d586..29796922e 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -56,7 +56,8 @@ public: else generatedDocumentation = m_compilerStack.natspecDev(_contractName); Json::Value expectedDocumentation; - util::jsonParseStrict(_expectedDocumentationString, expectedDocumentation); + std::string parseError; + BOOST_REQUIRE_MESSAGE(util::jsonParseStrict(_expectedDocumentationString, expectedDocumentation, &parseError), parseError); expectedDocumentation["version"] = Json::Value(Natspec::c_natspecVersion); expectedDocumentation["kind"] = Json::Value(_userDocumentation ? "user" : "dev"); @@ -118,7 +119,8 @@ BOOST_AUTO_TEST_CASE(user_newline_break) char const* natspec = R"ABCDEF( { - "methods": { + "methods": + { "f()": { "notice": "world" @@ -146,8 +148,10 @@ BOOST_AUTO_TEST_CASE(user_multiline_empty_lines) char const* natspec = R"ABCDEF( { - "methods": { - "f()": { + "methods": + { + "f()": + { "notice": "hello world" } } @@ -185,18 +189,27 @@ BOOST_AUTO_TEST_CASE(dev_and_user_basic_test) } )"; - char const* devNatspec = "{" - "\"methods\":{" - " \"mul(uint256)\":{ \n" - " \"details\": \"Multiplies a number by 7\"\n" - " }\n" - " }\n" - "}}"; + char const* devNatspec = R"R( + { + "methods": + { + "mul(uint256)": + { + "details": "Multiplies a number by 7" + } + } + })R"; - char const* userNatspec = "{" - "\"methods\":{" - " \"mul(uint256)\":{ \"notice\": \"Multiplies `a` by 7\"}" - "}}"; + char const* userNatspec = R"R( + { + "methods": + { + "mul(uint256)": + { + "notice": "Multiplies `a` by 7" + } + } + })R"; checkNatspec(sourceCode, "test", devNatspec, false); checkNatspec(sourceCode, "test", userNatspec, true); @@ -297,16 +310,16 @@ BOOST_AUTO_TEST_CASE(public_state_variable) char const* devDoc = R"R( { - "methods" : {}, - "stateVariables" : + "methods": {}, + "stateVariables": { - "state" : + "state": { - "details" : "example of dev", - "return" : "returns state", - "returns" : + "details": "example of dev", + "return": "returns state", + "returns": { - "_0" : "returns state" + "_0": "returns state" } } } @@ -316,9 +329,9 @@ BOOST_AUTO_TEST_CASE(public_state_variable) char const* userDoc = R"R( { - "methods" : + "methods": { - "state()" : + "state()": { "notice": "example of notice" } @@ -346,15 +359,15 @@ BOOST_AUTO_TEST_CASE(public_state_variable_struct) char const* devDoc = R"R( { - "methods" : {}, - "stateVariables" : + "methods": {}, + "stateVariables": { - "coinStack" : + "coinStack": { - "returns" : + "returns": { - "observeGraphicURL" : "Front pic", - "reverseGraphicURL" : "Back pic" + "observeGraphicURL": "Front pic", + "reverseGraphicURL": "Back pic" } } } @@ -364,9 +377,9 @@ BOOST_AUTO_TEST_CASE(public_state_variable_struct) char const* userDoc = R"R( { - "methods" : + "methods": { - "coinStack(uint256)" : + "coinStack(uint256)": { "notice": "Get the n-th coin I own" } @@ -406,12 +419,12 @@ BOOST_AUTO_TEST_CASE(private_state_variable) char const* devDoc = R"( { - "methods" : {}, - "stateVariables" : + "methods": {}, + "stateVariables": { - "state" : + "state": { - "details" : "example of dev" + "details": "example of dev" } } } @@ -634,9 +647,11 @@ BOOST_AUTO_TEST_CASE(dev_return_no_params) char const* natspec = R"ABCDEF( { - "methods": { - "mul(uint256,uint256)": { - "returns": { "d": "The result of the multiplication" + "methods": + { + "mul(uint256,uint256)": + { + "returns": { "d": "The result of the multiplication" } } } })ABCDEF"; @@ -866,19 +881,24 @@ BOOST_AUTO_TEST_CASE(dev_multiline_return) } )"; - char const* natspec = "{" - "\"methods\":{" - " \"mul(uint256,uint256)\":{ \n" - " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" - " \"params\": {\n" - " \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" - " \"second\": \"Documentation for the second parameter\"\n" - " },\n" - " \"returns\": {\n" - " \"d\": \"The result of the multiplication and cookies with nutella\",\n" - " }\n" - " }\n" - "}}"; + char const* natspec = R"R({ + "methods": + { + "mul(uint256,uint256)": + { + "details": "Multiplies a number by 7 and adds second parameter", + "params": + { + "a": "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines", + "second": "Documentation for the second parameter" + }, + "returns": + { + "d": "The result of the multiplication and cookies with nutella" + } + } + } + })R"; checkNatspec(sourceCode, "test", natspec, false); } @@ -901,19 +921,25 @@ BOOST_AUTO_TEST_CASE(dev_multiline_comment) } )"; - char const* natspec = "{" - "\"methods\":{" - " \"mul(uint256,uint256)\":{ \n" - " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" - " \"params\": {\n" - " \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" - " \"second\": \"Documentation for the second parameter\"\n" - " },\n" - " \"returns\": {\n" - " \"d\": \"The result of the multiplication and cookies with nutella\",\n" - " }\n" - " }\n" - "}}"; + char const* natspec = R"R( + { + "methods": + { + "mul(uint256,uint256)": + { + "details": "Multiplies a number by 7 and adds second parameter", + "params": + { + "a": "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines", + "second": "Documentation for the second parameter" + }, + "returns": + { + "d": "The result of the multiplication and cookies with nutella" + } + } + } + })R"; checkNatspec(sourceCode, "test", natspec, false); } @@ -1004,13 +1030,14 @@ BOOST_AUTO_TEST_CASE(natspec_notice_without_tag) char const* natspec = R"ABCDEF( { - "methods" : { - "mul(uint256)" : { - "notice" : "I do something awesome" - } - } - } - )ABCDEF"; + "methods": + { + "mul(uint256)": + { + "notice": "I do something awesome" + } + } + })ABCDEF"; checkNatspec(sourceCode, "test", natspec, true); } @@ -1027,11 +1054,13 @@ BOOST_AUTO_TEST_CASE(natspec_multiline_notice_without_tag) char const* natspec = R"ABCDEF( { - "methods" : { - "mul(uint256)" : { - "notice" : "I do something awesome which requires two lines to explain" - } - } + "methods": + { + "mul(uint256)": + { + "notice": "I do something awesome which requires two lines to explain" + } + } } )ABCDEF"; @@ -1047,7 +1076,7 @@ BOOST_AUTO_TEST_CASE(empty_comment) )"; char const* natspec = R"ABCDEF( { - "methods" : {} + "methods": {} } )ABCDEF"; @@ -1135,8 +1164,10 @@ BOOST_AUTO_TEST_CASE(user_constructor) )"; char const* natspec = R"ABCDEF({ - "methods": { - "constructor" : { + "methods": + { + "constructor": + { "notice": "this is a really nice constructor" } } @@ -1157,12 +1188,15 @@ BOOST_AUTO_TEST_CASE(user_constructor_and_function) )"; char const* natspec = R"ABCDEF({ - "methods" : { - "mul(uint256,uint256)" : { - "notice" : "another multiplier" + "methods": + { + "mul(uint256,uint256)": + { + "notice": "another multiplier" }, - "constructor" : { - "notice" : "this is a really nice constructor" + "constructor": + { + "notice": "this is a really nice constructor" } } })ABCDEF"; @@ -1181,11 +1215,14 @@ BOOST_AUTO_TEST_CASE(dev_constructor) )"; char const *natspec = R"ABCDEF({ - "methods" : { - "constructor" : { - "params" : { - "a" : "the parameter a is really nice and very useful", - "second" : "the second parameter is not very useful, it just provides additional confusion" + "methods": + { + "constructor": + { + "params": + { + "a": "the parameter a is really nice and very useful", + "second": "the second parameter is not very useful, it just provides additional confusion" } } } @@ -1228,21 +1265,27 @@ BOOST_AUTO_TEST_CASE(dev_constructor_and_function) )"; char const *natspec = R"ABCDEF({ - "methods" : { - "mul(uint256,uint256)" : { - "details" : "Multiplies a number by 7 and adds second parameter", - "params" : { - "a" : "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines", - "second" : "Documentation for the second parameter" + "methods": + { + "mul(uint256,uint256)": + { + "details": "Multiplies a number by 7 and adds second parameter", + "params": + { + "a": "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines", + "second": "Documentation for the second parameter" }, - "returns" : { + "returns": + { "d": "The result of the multiplication and cookies with nutella" } }, - "constructor" : { - "params" : { - "a" : "the parameter a is really nice and very useful", - "second" : "the second parameter is not very useful, it just provides additional confusion" + "constructor": + { + "params": + { + "a": "the parameter a is really nice and very useful", + "second": "the second parameter is not very useful, it just provides additional confusion" } } } @@ -1292,7 +1335,8 @@ BOOST_AUTO_TEST_CASE(slash3_slash3) )"; char const* natspec = R"ABCDEF({ - "methods": { + "methods": + { "f()": { "notice": "lorem ipsum" } } })ABCDEF"; @@ -1311,7 +1355,8 @@ BOOST_AUTO_TEST_CASE(slash3_slash4) )"; char const* natspec = R"ABCDEF({ - "methods": { + "methods": + { "f()": { "notice": "lorem" } } })ABCDEF"; @@ -1340,12 +1385,12 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit_variable) })ABCDEF"; char const *natspec1 = R"ABCDEF({ - "methods" : {}, - "stateVariables" : + "methods": {}, + "stateVariables": { - "x" : + "x": { - "details" : "test" + "details": "test" } } })ABCDEF"; @@ -1406,12 +1451,12 @@ BOOST_AUTO_TEST_CASE(dev_explicit_inherit_variable) })ABCDEF"; char const *natspec1 = R"ABCDEF({ - "methods" : {}, - "stateVariables" : + "methods": {}, + "stateVariables": { - "x" : + "x": { - "details" : "test" + "details": "test" } } })ABCDEF"; @@ -1467,7 +1512,7 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit) function transfer(address to, uint amount) virtual override external returns (bool) { return false; - } + } } contract Token is Middle { @@ -1513,8 +1558,8 @@ BOOST_AUTO_TEST_CASE(user_default_inherit) contract Middle is ERC20 { function transfer(address to, uint amount) virtual override external returns (bool) { - return false; - } + return false; + } } contract Token is Middle { @@ -2295,7 +2340,7 @@ BOOST_AUTO_TEST_CASE(dev_return_name_no_description) { "returns": { - "a": "a", + "a": "a" } } } @@ -2308,7 +2353,7 @@ BOOST_AUTO_TEST_CASE(dev_return_name_no_description) { "returns": { - "b": "a", + "b": "a" } } } @@ -2333,11 +2378,11 @@ BOOST_AUTO_TEST_CASE(error) char const* devdoc = R"X({ "errors":{ "E(uint256,uint256)": [{ - "details" : "an error.", - "params" : + "details": "an error.", + "params": { - "a" : "first parameter", - "b" : "second parameter" + "a": "first parameter", + "b": "second parameter" } }] }, @@ -2349,7 +2394,7 @@ BOOST_AUTO_TEST_CASE(error) char const* userdoc = R"X({ "errors":{ "E(uint256,uint256)": [{ - "notice" : "Something failed." + "notice": "Something failed." }] }, "methods": {} @@ -2384,22 +2429,23 @@ BOOST_AUTO_TEST_CASE(error_multiple) char const* devdoc = R"X({ "methods": {}, - "errors": { + "errors": + { "E(uint256,uint256)": [ { - "details" : "an error.", - "params" : + "details": "an error.", + "params": { - "x" : "first parameter", - "y" : "second parameter" + "x": "first parameter", + "y": "second parameter" } }, { - "details" : "X an error.", - "params" : + "details": "X an error.", + "params": { - "a" : "X first parameter", - "b" : "X second parameter" + "a": "X first parameter", + "b": "X second parameter" } } ] @@ -2411,8 +2457,8 @@ BOOST_AUTO_TEST_CASE(error_multiple) char const* userdoc = R"X({ "errors":{ "E(uint256,uint256)": [ - { "notice" : "Something failed." }, - { "notice" : "X Something failed." } + { "notice": "Something failed." }, + { "notice": "X Something failed." } ] }, "methods": {} @@ -2465,7 +2511,8 @@ BOOST_AUTO_TEST_CASE(custom_inheritance) } )"; - char const* natspecA = R"ABCDEF({ + char const* natspecA = R"ABCDEF( + { "methods": { "g(uint256)": @@ -2473,8 +2520,9 @@ BOOST_AUTO_TEST_CASE(custom_inheritance) "custom:since": "2014" } } - )ABCDEF"; - char const* natspecB = R"ABCDEF({ + })ABCDEF"; + char const* natspecB = R"ABCDEF( + { "methods": {} })ABCDEF"; @@ -2482,7 +2530,7 @@ BOOST_AUTO_TEST_CASE(custom_inheritance) checkNatspec(sourceCode, "B", natspecB, false); } -BOOST_AUTO_TEST_CASE(dev_different_amount_return_parameters) +BOOST_AUTO_TEST_CASE(dev_struct_getter_override) { char const *sourceCode = R"( interface IThing { @@ -2506,11 +2554,11 @@ BOOST_AUTO_TEST_CASE(dev_different_amount_return_parameters) { "value()": { - "returns": - { - "x": "a number", - "y": "another number" - } + "returns": + { + "x": "a number", + "y": "another number" + } } } })ABCDEF"; @@ -2534,6 +2582,58 @@ BOOST_AUTO_TEST_CASE(dev_different_amount_return_parameters) checkNatspec(sourceCode, "Thing", natspec2, false); } +BOOST_AUTO_TEST_CASE(dev_struct_getter_override_different_return_parameter_names) +{ + char const *sourceCode = R"( + interface IThing { + /// @return x a number + /// @return y another number + function value() external view returns (uint128 x, uint128 y); + } + + contract Thing is IThing { + struct Value { + uint128 a; + uint128 b; + } + + Value public override value; + } + )"; + + char const *natspec = R"ABCDEF({ + "methods": + { + "value()": + { + "returns": + { + "x": "a number", + "y": "another number" + } + } + } + })ABCDEF"; + + char const *natspec2 = R"ABCDEF({ + "methods": {}, + "stateVariables": + { + "value": + { + "returns": + { + "a": "a number", + "b": "another number" + } + } + } + })ABCDEF"; + + checkNatspec(sourceCode, "IThing", natspec, false); + checkNatspec(sourceCode, "Thing", natspec2, false); +} + } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/semanticTests/abiencodedecode/abi_encode_call_declaration.sol b/test/libsolidity/semanticTests/abiencodedecode/abi_encode_call_declaration.sol new file mode 100644 index 000000000..a70c6b903 --- /dev/null +++ b/test/libsolidity/semanticTests/abiencodedecode/abi_encode_call_declaration.sol @@ -0,0 +1,54 @@ +pragma abicoder v2; + +contract X { + // no "returns" on purpose + function a(uint) public pure {} + function b(uint) external pure {} +} + +contract Base { + function a(uint x) external pure returns (uint) { return x + 1; } +} + +contract C is Base { + function test() public view returns (uint r) { + bool success; + bytes memory result; + (success, result) = address(this).staticcall(abi.encodeCall(X.a, 1)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 2); + + (success, result) = address(this).staticcall(abi.encodeCall(X.b, 10)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 13); + + (success, result) = address(this).staticcall(abi.encodeCall(Base.a, 100)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 114); + + (success, result) = address(this).staticcall(abi.encodeCall(this.a, 1000)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 1115); + + (success, result) = address(this).staticcall(abi.encodeCall(C.b, 10000)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 11116); + + return r; + } + function b(uint x) external view returns (uint) { + return this.a(x); + } + +} + +// ==== +// compileViaYul: also +// EVMVersion: >=byzantium +// ---- +// test() -> 11116 diff --git a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol index ed9a07f7f..7f5799805 100644 --- a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol +++ b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol @@ -178,7 +178,7 @@ contract DepositContract is IDepositContract, ERC165 { // compileViaYul: also // ---- // constructor() -// gas irOptimized: 1558001 +// gas irOptimized: 1557137 // gas legacy: 2436584 // gas legacyOptimized: 1776483 // supportsInterface(bytes4): 0x0 -> 0 diff --git a/test/libsolidity/semanticTests/functionCall/mapping_array_internal_argument.sol b/test/libsolidity/semanticTests/functionCall/mapping_array_internal_argument.sol index f01a590eb..d3e2ba075 100644 --- a/test/libsolidity/semanticTests/functionCall/mapping_array_internal_argument.sol +++ b/test/libsolidity/semanticTests/functionCall/mapping_array_internal_argument.sol @@ -20,7 +20,7 @@ contract test { // compileViaYul: also // ---- // set(uint8,uint8,uint8,uint8,uint8): 1, 21, 22, 42, 43 -> 0, 0, 0, 0 -// gas irOptimized: 111965 +// gas irOptimized: 111896 // gas legacy: 113806 // gas legacyOptimized: 111781 // get(uint8): 1 -> 21, 22, 42, 43 diff --git a/test/libsolidity/semanticTests/functionTypes/comparison_operator_for_external_function_cleans_dirty_bits.sol b/test/libsolidity/semanticTests/functionTypes/comparison_operator_for_external_function_cleans_dirty_bits.sol new file mode 100644 index 000000000..3347d9b77 --- /dev/null +++ b/test/libsolidity/semanticTests/functionTypes/comparison_operator_for_external_function_cleans_dirty_bits.sol @@ -0,0 +1,17 @@ +contract C { + function g() external {} + function comparison_operators_for_external_function_pointers_with_dirty_bits() external returns (bool) { + function() external g_ptr_dirty = this.g; + assembly { + g_ptr_dirty.address := or(g_ptr_dirty.address, shl(160, sub(0,1))) + g_ptr_dirty.selector := or(g_ptr_dirty.selector, shl(32, sub(0,1))) + } + function() external g_ptr = this.g; + return g_ptr == g_ptr_dirty; + } +} +// ==== +// compileViaYul: also +// EVMVersion: >=constantinople +// ---- +// comparison_operators_for_external_function_pointers_with_dirty_bits() -> true diff --git a/test/libsolidity/semanticTests/functionTypes/comparison_operators_for_external_functions.sol b/test/libsolidity/semanticTests/functionTypes/comparison_operators_for_external_functions.sol new file mode 100644 index 000000000..da75ea071 --- /dev/null +++ b/test/libsolidity/semanticTests/functionTypes/comparison_operators_for_external_functions.sol @@ -0,0 +1,81 @@ +contract C { + function f() external {} + function g() external {} + function h() pure external {} + function i() view external {} + + function comparison_operators_for_external_functions() public returns (bool) { + assert( + this.f != this.g && + this.f != this.h && + this.f != this.i && + + this.g != this.h && + this.g != this.i && + + this.h != this.i && + + this.f == this.f && + this.g == this.g && + this.h == this.h && + this.i == this.i + ); + return true; + } + + function comparison_operators_for_local_external_function_pointers() public returns (bool) { + function () external f_local = this.f; + function () external g_local = this.g; + function () external pure h_local = this.h; + function () external view i_local = this.i; + + assert( + f_local == this.f && + g_local == this.g && + h_local == this.h && + i_local == this.i && + + f_local != this.g && + f_local != this.h && + f_local != this.i && + + g_local != this.f && + g_local != this.h && + g_local != this.i && + + h_local != this.f && + h_local != this.g && + h_local != this.i && + + i_local != this.f && + i_local != this.g && + i_local != this.h + ); + + assert( + f_local == f_local && + f_local != g_local && + f_local != h_local && + f_local != i_local + ); + + assert( + g_local == g_local && + g_local != h_local && + g_local != i_local + ); + + assert( + h_local == h_local && + i_local == i_local && + h_local != i_local + ); + + return true; + } +} +// ==== +// compileViaYul: also +// ---- +// comparison_operators_for_external_functions() -> true +// comparison_operators_for_local_external_function_pointers() -> true diff --git a/test/libsolidity/semanticTests/functionTypes/stack_height_check_on_adding_gas_variable_to_function.sol b/test/libsolidity/semanticTests/functionTypes/stack_height_check_on_adding_gas_variable_to_function.sol new file mode 100644 index 000000000..5726860b9 --- /dev/null +++ b/test/libsolidity/semanticTests/functionTypes/stack_height_check_on_adding_gas_variable_to_function.sol @@ -0,0 +1,25 @@ +contract C { + function g() external {} + function h() external payable {} + function test_function() external returns (bool){ + assert ( + this.g.address == this.g.address && + this.g{gas: 42}.address == this.g.address && + this.g{gas: 42}.selector == this.g.selector + ); + assert ( + this.h.address == this.h.address && + this.h{gas: 42}.address == this.h.address && + this.h{gas: 42}.selector == this.h.selector + ); + assert ( + this.h{gas: 42, value: 5}.address == this.h.address && + this.h{gas: 42, value: 5}.selector == this.h.selector + ); + return true; + } +} +// ==== +// compileViaYul: also +// ---- +// test_function() -> true diff --git a/test/libsolidity/semanticTests/structs/struct_copy.sol b/test/libsolidity/semanticTests/structs/struct_copy.sol index 0bbb6489e..f170dca3f 100644 --- a/test/libsolidity/semanticTests/structs/struct_copy.sol +++ b/test/libsolidity/semanticTests/structs/struct_copy.sol @@ -38,12 +38,12 @@ contract c { // compileViaYul: also // ---- // set(uint256): 7 -> true -// gas irOptimized: 110011 +// gas irOptimized: 110119 // gas legacy: 110616 // gas legacyOptimized: 110006 // retrieve(uint256): 7 -> 1, 3, 4, 2 // copy(uint256,uint256): 7, 8 -> true -// gas irOptimized: 118707 +// gas irOptimized: 118698 // gas legacy: 119166 // gas legacyOptimized: 118622 // retrieve(uint256): 7 -> 1, 3, 4, 2 diff --git a/test/libsolidity/semanticTests/viaYul/conversion/explicit_string_bytes_calldata_cast.sol b/test/libsolidity/semanticTests/viaYul/conversion/explicit_string_bytes_calldata_cast.sol new file mode 100644 index 000000000..4a5b01d39 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/conversion/explicit_string_bytes_calldata_cast.sol @@ -0,0 +1,11 @@ +// Triggered ICE before +contract C { + function f(string calldata data) external pure returns(string memory) { + bytes calldata test = bytes(data[:3]); + return string(test); + } +} +// ==== +// compileViaYul: also +// ---- +// f(string): 0x20, 3, "123" -> 0x20, 3, "123" diff --git a/test/libsolidity/semanticTests/viaYul/copy_struct_invalid_ir_bug.sol b/test/libsolidity/semanticTests/viaYul/copy_struct_invalid_ir_bug.sol new file mode 100644 index 000000000..70e3a34d4 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/copy_struct_invalid_ir_bug.sol @@ -0,0 +1,28 @@ +contract C { + struct Struct { + function () external el; + } + Struct[] array; + int externalCalled = 0; + + function ext() external { + externalCalled++; + } + + function f() public { + array.push(Struct(this.ext)); + array.push(array[0]); + + array[0].el(); + array[1].el(); + + assert(externalCalled == 2); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> +// gas irOptimized: 113142 +// gas legacy: 112937 +// gas legacyOptimized: 112608 diff --git a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_2.sol b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_2.sol index c4714e2e3..b611bc95f 100644 --- a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_2.sol +++ b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_2.sol @@ -14,7 +14,8 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 4984: (266-272): CHC: Overflow (resulting value larger than 2**256 - 1) happens here.\nCounterexample:\nx = 115792089237316195423570985008687907853269984665640564039457584007913129639926, once = true\n\nTransaction trace:\nC.constructor(){ msg.value: 28100 }\nState: x = 115792089237316195423570985008687907853269984665640564039457584007913129639926, once = false\nC.f(){ msg.value: 8 } -// Warning 6328: (235-273): CHC: Assertion violation happens here.\nCounterexample:\nx = 0, once = true\n\nTransaction trace:\nC.constructor(){ msg.value: 0 }\nState: x = 0, once = false\nC.f(){ msg.value: 8 } +// Warning 4984: (266-272): CHC: Overflow (resulting value larger than 2**256 - 1) happens here. +// Warning 6328: (235-273): CHC: Assertion violation happens here. // Info 1180: Contract invariant(s) for :C:\nonce\n diff --git a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls.sol b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls.sol index 96223b7e9..313da678e 100644 --- a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls.sol +++ b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls.sol @@ -16,5 +16,5 @@ contract C { // ---- // Warning 1218: (131-165): CHC: Error trying to invoke SMT solver. // Warning 6328: (131-165): CHC: Assertion violation might happen here. -// Info 1180: Reentrancy property(ies) for :C:\n(!( >= 2) && (((:var 1).balances[address(this)] + ((- 1) * (:var 0).balances[address(this)])) <= 0))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(address(this).balance == x)\n = 2 -> Assertion failed at assert(address(this).balance >= x)\n +// Info 1180: Reentrancy property(ies) for :C:\n(!( >= 2) && (((:var 0).balances[address(this)] + ((- 1) * (:var 1).balances[address(this)])) >= 0))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(address(this).balance == x)\n = 2 -> Assertion failed at assert(address(this).balance >= x)\n // Warning 4661: (131-165): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls_2.sol b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls_2.sol index c53f156d6..a1eb278bc 100644 --- a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls_2.sol +++ b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_ext_calls_2.sol @@ -9,7 +9,7 @@ contract C { // ==== // SMTEngine: all // SMTIgnoreCex: yes +// SMTIgnoreInv: yes // ---- // Warning 9302: (82-93): Return value of low-level calls not used. // Warning 6328: (97-131): CHC: Assertion violation happens here. -// Info 1180: Reentrancy property(ies) for :C:\n(!( >= 2) && (((:var 1).balances[address(this)] + ((- 1) * (:var 0).balances[address(this)])) <= 0))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(address(this).balance == x)\n = 2 -> Assertion failed at assert(address(this).balance >= x)\n diff --git a/test/libsolidity/smtCheckerTests/blockchain_state/this_does_not_change_external_call.sol b/test/libsolidity/smtCheckerTests/blockchain_state/this_does_not_change_external_call.sol index f16964501..49d2eb33b 100644 --- a/test/libsolidity/smtCheckerTests/blockchain_state/this_does_not_change_external_call.sol +++ b/test/libsolidity/smtCheckerTests/blockchain_state/this_does_not_change_external_call.sol @@ -17,5 +17,4 @@ contract C { // ==== // SMTEngine: all // SMTIgnoreOS: macos -// ---- -// Info 1180: Contract invariant(s) for :C:\n(((address(this) + ((- 1) * t)) <= 0) && ((address(this) + ((- 1) * t)) >= 0))\nReentrancy property(ies) for :C:\n((!((t + ((- 1) * address(this))) = 0) || ( <= 0)) && (!((t + ((- 1) * address(this))) <= 0) || ((t' + ((- 1) * address(this))) <= 0)) && (!((t + ((- 1) * address(this))) >= 0) || ((address(this) + ((- 1) * t')) <= 0)))\n((!( >= 2) || !((t + ((- 1) * address(this))) = 0)) && (!((t + ((- 1) * address(this))) <= 0) || ((t' + ((- 1) * address(this))) <= 0)) && (!((t + ((- 1) * address(this))) >= 0) || ((address(this) + ((- 1) * t')) <= 0)))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(address(this) == t)\n = 2 -> Assertion failed at assert(a == t)\n +// SMTIgnoreInv: yes diff --git a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol index f08a26bf7..98b551c17 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol @@ -45,5 +45,5 @@ contract C { // SMTIgnoreOS: macos // ---- // Warning 6328: (255-269): CHC: Assertion violation happens here.\nCounterexample:\nx = 0\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.test()\n C.reset_if_overflow() -- internal call -// Warning 6328: (502-519): CHC: Assertion violation happens here.\nCounterexample:\nx = 2\noldx = 1\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.set(1)\nState: x = 1\nC.test()\n C.reset_if_overflow() -- internal call +// Warning 6328: (502-519): CHC: Assertion violation happens here. // Warning 6328: (615-629): CHC: Assertion violation happens here.\nCounterexample:\nx = 1\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.set(10)\nState: x = 10\nC.test()\n C.reset_if_overflow() -- internal call diff --git a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/constructor_state_variable_init_chain_alternate.sol b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/constructor_state_variable_init_chain_alternate.sol index f0b5d48e1..805a75bbb 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/constructor_state_variable_init_chain_alternate.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/constructor_state_variable_init_chain_alternate.sol @@ -24,5 +24,6 @@ contract D is C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (286-300): CHC: Assertion violation happens here.\nCounterexample:\nx = 2\na = 1\n\nTransaction trace:\nD.constructor(1) +// Warning 6328: (286-300): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/call_mutex_unsafe.sol b/test/libsolidity/smtCheckerTests/external_calls/call_mutex_unsafe.sol index ad17e3d05..64494971a 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/call_mutex_unsafe.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/call_mutex_unsafe.sol @@ -21,6 +21,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- // Warning 9302: (212-228): Return value of low-level calls not used. -// Warning 6328: (232-246): CHC: Assertion violation happens here.\nCounterexample:\nx = 1, lock = false\n_a = 0x0\ny = 0\n\nTransaction trace:\nC.constructor()\nState: x = 0, lock = false\nC.f(0x0)\n _a.call("aaaaa") -- untrusted external call, synthesized as:\n C.set(1) -- reentrant call +// Warning 6328: (232-246): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol index f9293f693..813185adf 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol @@ -12,5 +12,5 @@ contract C { // ---- // Warning 9302: (96-117): Return value of low-level calls not used. // Warning 6328: (121-156): CHC: Assertion violation might happen here. -// Warning 6328: (175-211): CHC: Assertion violation happens here.\nCounterexample:\n\ni = 0x0\n\nTransaction trace:\nC.constructor()\nC.g(0x0)\n i.call{value: 10}("") -- untrusted external call +// Warning 6328: (175-211): CHC: Assertion violation happens here. // Warning 4661: (121-156): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol b/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol index 9d27d6d3b..eb5cd97dd 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol @@ -20,4 +20,4 @@ contract C { // SMTEngine: all // ---- // Warning 6328: (69-85): CHC: Assertion violation happens here.\nCounterexample:\n\n_x = 100\n = 0\n\nTransaction trace:\nState.constructor()\nState.f(100) -// Warning 6328: (203-217): CHC: Assertion violation happens here.\nCounterexample:\ns = 0, z = 0\n\nTransaction trace:\nC.constructor()\nState: s = 0, z = 0\nC.f() +// Warning 6328: (203-217): CHC: Assertion violation happens here.\nCounterexample:\ns = 0, z = 3\n\nTransaction trace:\nC.constructor()\nState: s = 0, z = 3\nC.f() diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_call_with_value_2.sol b/test/libsolidity/smtCheckerTests/external_calls/external_call_with_value_2.sol index 4b51d9d9e..2b94abc0e 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_call_with_value_2.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_call_with_value_2.sol @@ -12,6 +12,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- // Warning 6328: (150-186): CHC: Assertion violation might happen here. // Warning 6328: (205-240): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol index 2468a1a72..317400e1d 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol @@ -36,4 +36,4 @@ contract C { // SMTIgnoreCex: yes // ---- // Warning 6328: (495-532): CHC: Assertion violation happens here. -// Info 1180: Reentrancy property(ies) for :C:\n(((owner + ((- 1) * owner')) <= 0) && !( = 1) && ((owner + ((- 1) * owner')) >= 0))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(prevOwner == owner)\n = 3 -> Assertion failed at assert(owner == address(0) || y != z)\n +// Info 1180: Reentrancy property(ies) for :C:\n(((owner + ((- 1) * owner')) >= 0) && !( = 1) && ((owner + ((- 1) * owner')) <= 0))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(prevOwner == owner)\n = 3 -> Assertion failed at assert(owner == address(0) || y != z)\n diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_3.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_3.sol index 508047c39..8372285ee 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_3.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_3.sol @@ -40,5 +40,4 @@ contract C { } // ==== // SMTEngine: all -// ---- -// Info 1180: Contract invariant(s) for :C:\n((insidef || (z <= 0)) && (y <= 0))\nReentrancy property(ies) for :C:\n((!insidef || !( >= 2)) && (insidef' || !insidef) && (!(y <= 0) || (y' <= 0)))\n((!insidef || !( >= 3)) && (insidef' || !insidef))\n = 0 -> no errors\n = 2 -> Assertion failed at assert(z == y)\n = 3 -> Assertion failed at assert(prevOwner == owner)\n +// SMTIgnoreInv: yes diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol index 4ab3f21e6..ea9764e17 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol @@ -42,6 +42,9 @@ contract C { // ==== // SMTEngine: all // SMTIgnoreCex: yes +// SMTIgnoreOS: macos // ---- +// Warning 1218: (437-463): CHC: Error trying to invoke SMT solver. // Warning 6328: (419-433): CHC: Assertion violation happens here. -// Warning 6328: (437-463): CHC: Assertion violation happens here. +// Warning 6328: (437-463): CHC: Assertion violation might happen here. +// Warning 4661: (437-463): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_reentrancy_2.sol b/test/libsolidity/smtCheckerTests/external_calls/external_reentrancy_2.sol index 19ff2bbad..1e8ce8816 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_reentrancy_2.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_reentrancy_2.sol @@ -13,4 +13,6 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (117-131): CHC: Assertion violation happens here.\nCounterexample:\nlocked = false\ntarget = 0x0\n\nTransaction trace:\nC.constructor()\nState: locked = true\nC.call(0x0)\n D(target).e() -- untrusted external call, synthesized as:\n C.call(0x0) -- reentrant call +// Warning 1218: (117-131): CHC: Error trying to invoke SMT solver. +// Warning 6328: (117-131): CHC: Assertion violation might happen here. +// Warning 4661: (117-131): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/file_level/import.sol b/test/libsolidity/smtCheckerTests/file_level/import.sol index 85ae71293..7bc2a5445 100644 --- a/test/libsolidity/smtCheckerTests/file_level/import.sol +++ b/test/libsolidity/smtCheckerTests/file_level/import.sol @@ -21,6 +21,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (B:238-252): CHC: Assertion violation happens here.\nCounterexample:\ndata = {x: 21238}\nx = 8\ny = 21238\n\nTransaction trace:\nC.constructor()\nState: data = {x: 0}\nC.g()\n C.f(7) -- internal call\n A:set({x: 0}, 7) -- internal call\n A:set({x: 8}, 8) -- internal call -// Warning 6328: (B:308-322): CHC: Assertion violation happens here.\nCounterexample:\ndata = {x: 6}\nx = 0\ny = 6\n\nTransaction trace:\nC.constructor()\nState: data = {x: 0}\nC.g()\n C.f(7) -- internal call\n A:set({x: 0}, 7) -- internal call\n A:set({x: 0}, 8) -- internal call +// Warning 6328: (B:238-252): CHC: Assertion violation happens here. +// Warning 6328: (B:308-322): CHC: 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 3a3b295e4..5efcb2da1 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol @@ -25,4 +25,4 @@ contract C // SMTIgnoreOS: macos // ---- // Warning 6328: (234-253): CHC: Assertion violation happens here. -// Info 1180: Reentrancy property(ies) for :C:\n!( = 1)\n((!((map[1] + ((- 1) * map[0])) >= 0) || ((map'[0] + ((- 1) * map'[1])) <= 0)) && !( = 2) && (!((map[1] + ((- 1) * map[0])) <= 0) || ((map'[1] + ((- 1) * map'[0])) <= 0)))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(map[0] == map[1])\n = 2 -> Assertion failed at assert(map[0] == map[1])\n = 3 -> Assertion failed at assert(map[0] == 0)\n +// Info 1180: Reentrancy property(ies) for :C:\n!( = 1)\n((!((map[1] + ((- 1) * map[0])) <= 0) || ((map'[1] + ((- 1) * map'[0])) <= 0)) && !( = 2) && (!((map[1] + ((- 1) * map[0])) >= 0) || ((map'[0] + ((- 1) * map'[1])) <= 0)))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(map[0] == map[1])\n = 2 -> Assertion failed at assert(map[0] == map[1])\n = 3 -> Assertion failed at assert(map[0] == 0)\n diff --git a/test/libsolidity/smtCheckerTests/functions/virtual_function_called_by_constructor.sol b/test/libsolidity/smtCheckerTests/functions/virtual_function_called_by_constructor.sol index e30a5e743..0117f9b49 100644 --- a/test/libsolidity/smtCheckerTests/functions/virtual_function_called_by_constructor.sol +++ b/test/libsolidity/smtCheckerTests/functions/virtual_function_called_by_constructor.sol @@ -27,4 +27,4 @@ contract C is A { // ---- // Warning 6328: (199-214): CHC: Assertion violation happens here.\nCounterexample:\nx = 2\n\nTransaction trace:\nA.constructor()\nState: x = 2\nA.i() // Warning 6328: (387-401): CHC: Assertion violation happens here.\nCounterexample:\nx = 10\n\nTransaction trace:\nC.constructor()\nState: x = 10\nC.i() -// Info 1180: Contract invariant(s) for :A:\n(!(x <= 1) && !(x >= 3))\nContract invariant(s) for :C:\n(!(x <= 9) && !(x >= 11))\n +// Info 1180: Contract invariant(s) for :A:\n(!(x <= 1) && !(x >= 3))\nContract invariant(s) for :C:\n(!(x >= 11) && !(x <= 9))\n diff --git a/test/libsolidity/smtCheckerTests/operators/assignment_contract_member_variable.sol b/test/libsolidity/smtCheckerTests/operators/assignment_contract_member_variable.sol index 284f9bcbd..efa29edc1 100644 --- a/test/libsolidity/smtCheckerTests/operators/assignment_contract_member_variable.sol +++ b/test/libsolidity/smtCheckerTests/operators/assignment_contract_member_variable.sol @@ -27,6 +27,6 @@ contract A { // ==== // SMTEngine: all // SMTIgnoreCex: yes +// SMTIgnoreInv: yes // ---- // Warning 6328: (392-408): CHC: Assertion violation happens here. -// Info 1180: Contract invariant(s) for :A:\n(((x = (- 2)) && (y = (- 2))) || ((x = 0) && (y = 0)))\n(((x = 0) && (y = 0)) || ((x = (- 2)) && (y = (- 2))))\n diff --git a/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol b/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol index 1c6e205d3..4186f39fe 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol @@ -20,4 +20,4 @@ contract C // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (262-284): CHC: Assertion violation happens here.\nCounterexample:\narray = [299, 0]\nx = 99\np = 0\n\nTransaction trace:\nC.constructor()\nState: array = [0, 0]\nC.f(99, 0) +// Warning 6328: (262-284): CHC: Assertion violation happens here.\nCounterexample:\narray = [200, 0]\nx = 0\np = 0\n\nTransaction trace:\nC.constructor()\nState: array = [0, 0]\nC.f(0, 0) diff --git a/test/libsolidity/smtCheckerTests/operators/conditional_assignment_6.sol b/test/libsolidity/smtCheckerTests/operators/conditional_assignment_6.sol index 1affbb6ca..b26fcbd93 100644 --- a/test/libsolidity/smtCheckerTests/operators/conditional_assignment_6.sol +++ b/test/libsolidity/smtCheckerTests/operators/conditional_assignment_6.sol @@ -25,4 +25,4 @@ contract C { // SMTIgnoreOS: macos // ---- // Warning 2072: (255-261): Unused local variable. -// Info 1180: Reentrancy property(ies) for :C:\n((!(x' >= 3) || (a' = a)) && (!(x' <= 0) || !(x >= 2)) && ( <= 0) && (!(x <= 2) || !(x' >= 3)))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(x == 2 || x == 1)\n +// Info 1180: Reentrancy property(ies) for :C:\n((!(x' >= 3) || (a' = a)) && ( <= 0) && (!(x' <= 0) || !(x >= 2)) && (!(x <= 2) || !(x' >= 3)))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(x == 2 || x == 1)\n diff --git a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol index 71608eabd..7729cebb9 100644 --- a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol +++ b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol @@ -13,4 +13,4 @@ contract C { // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (135-169): CHC: Assertion violation happens here.\nCounterexample:\n\n_i = 0\nx = 1236\n\nTransaction trace:\nC.constructor()\nC.g(0){ msg.value: 38 }\n _i.f() -- untrusted external call, synthesized as:\n C.g(0){ msg.value: 1 } -- reentrant call\n _i.f() -- untrusted external call +// Warning 6328: (135-169): CHC: Assertion violation happens here.\nCounterexample:\n\n_i = 0\nx = 2997\n\nTransaction trace:\nC.constructor()\nC.g(0){ msg.value: 2803 }\n _i.f() -- untrusted external call, synthesized as:\n C.g(0){ msg.value: 2446 } -- reentrant call\n _i.f() -- untrusted external call diff --git a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_2.sol b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_2.sol index 206d95c46..e704cfd71 100644 --- a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_2.sol +++ b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_2.sol @@ -13,4 +13,6 @@ contract C { // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (157-191): CHC: Assertion violation happens here.\nCounterexample:\n\n_i = 0\nx = 101\n\nTransaction trace:\nC.constructor()\nC.g(0){ msg.value: 69 }\n _i.f{ value: 100 }() -- untrusted external call +// Warning 1218: (157-191): CHC: Error trying to invoke SMT solver. +// Warning 6328: (157-191): CHC: Assertion violation might happen here. +// Warning 4661: (157-191): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/try_catch/try_public_var_mapping.sol b/test/libsolidity/smtCheckerTests/try_catch/try_public_var_mapping.sol index 2caae5e16..0854dedd1 100644 --- a/test/libsolidity/smtCheckerTests/try_catch/try_public_var_mapping.sol +++ b/test/libsolidity/smtCheckerTests/try_catch/try_public_var_mapping.sol @@ -21,5 +21,5 @@ contract C { // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (280-300): CHC: Assertion violation happens here. +// Warning 6328: (280-300): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.f() // Info 1180: Contract invariant(s) for :C:\n!(m[0].length <= 1)\n(!(m[0][1] >= 43) && !(m[0][1] <= 41))\n diff --git a/test/libsolidity/smtCheckerTests/types/address_transfer_insufficient.sol b/test/libsolidity/smtCheckerTests/types/address_transfer_insufficient.sol index 4b55c3241..59568cb5b 100644 --- a/test/libsolidity/smtCheckerTests/types/address_transfer_insufficient.sol +++ b/test/libsolidity/smtCheckerTests/types/address_transfer_insufficient.sol @@ -10,7 +10,8 @@ contract C } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (180-204): CHC: Assertion violation happens here.\nCounterexample:\n\na = 0x2297\nb = 0x2297\n\nTransaction trace:\nC.constructor()\nC.f(0x2297, 0x2297) +// Warning 6328: (180-204): CHC: Assertion violation happens here. // Warning 1236: (101-116): BMC: Insufficient funds happens here. // Warning 1236: (120-136): BMC: Insufficient funds happens here. diff --git a/test/libsolidity/smtCheckerTests/types/mapping_equal_keys_2.sol b/test/libsolidity/smtCheckerTests/types/mapping_equal_keys_2.sol index 0290d31e1..3b8a8846e 100644 --- a/test/libsolidity/smtCheckerTests/types/mapping_equal_keys_2.sol +++ b/test/libsolidity/smtCheckerTests/types/mapping_equal_keys_2.sol @@ -8,5 +8,6 @@ contract C } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (86-100): CHC: Assertion violation happens here.\nCounterexample:\n\nx = 0\ny = 1\n\nTransaction trace:\nC.constructor()\nC.f(0, 1) +// Warning 6328: (86-100): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/types/static_array_length_1.sol b/test/libsolidity/smtCheckerTests/types/static_array_length_1.sol index e8b6f1a32..61d18c417 100644 --- a/test/libsolidity/smtCheckerTests/types/static_array_length_1.sol +++ b/test/libsolidity/smtCheckerTests/types/static_array_length_1.sol @@ -7,6 +7,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (102-122): CHC: Assertion violation happens here.\nCounterexample:\n\na = [0x09, 0x09]\n\nTransaction trace:\nC.constructor()\nC.f([0x09, 0x09]) -// Warning 6328: (141-161): CHC: Assertion violation happens here.\nCounterexample:\n\na = [0x09, 0x09]\n\nTransaction trace:\nC.constructor()\nC.f([0x09, 0x09]) +// Warning 6328: (102-122): CHC: Assertion violation happens here. +// Warning 6328: (141-161): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol index 9d4623ae0..698262382 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol @@ -20,4 +20,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (289-322): CHC: Assertion violation happens here.\nCounterexample:\ns = {innerM, sum: 10}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: s = {innerM, sum: 10}\nC.g(){ msg.sender: 0x0985 } +// Warning 6328: (289-322): CHC: Assertion violation happens here.\nCounterexample:\ns = {innerM, sum: 11}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: s = {innerM, sum: 11}\nC.g(){ msg.sender: 0x0985 } diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol index 8779cd095..fcf30555e 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol @@ -26,4 +26,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (307-327): CHC: Assertion violation happens here.\nCounterexample:\nt = {x: 10, s: {innerM, sum: 21239}}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: t = {x: 10, s: {innerM, sum: 21239}}\nC.g() +// Warning 6328: (307-327): CHC: Assertion violation happens here.\nCounterexample:\nt = {x: 11, s: {innerM, sum: 21239}}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: t = {x: 11, s: {innerM, sum: 21239}}\nC.g() diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_4.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_4.sol index 957c72c12..618b1172d 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_4.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_4.sol @@ -25,5 +25,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (305-325): CHC: Assertion violation happens here.\nCounterexample:\nt = {x: 10, s: {innerM, sum: 21239}}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: t = {x: 10, s: {innerM, sum: 21239}}\nC.g() +// Warning 6328: (305-325): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/userTypes/fixedpoint.sol b/test/libsolidity/smtCheckerTests/userTypes/fixedpoint.sol index a8aa0bd9a..4bff3a498 100644 --- a/test/libsolidity/smtCheckerTests/userTypes/fixedpoint.sol +++ b/test/libsolidity/smtCheckerTests/userTypes/fixedpoint.sol @@ -67,8 +67,9 @@ contract TestFixedMath { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (1886-1970): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nTestFixedMath.constructor()\nTestFixedMath.f()\n TestFixedMath.add(0, 0) -- internal call\n FixedMath.add(0, 0) -- internal call\n TestFixedMath.add(25, 45) -- internal call\n FixedMath.add(25, 45) -- internal call\n TestFixedMath.add(25, 45) -- internal call\n FixedMath.add(25, 45) -- internal call +// Warning 6328: (1886-1970): CHC: Assertion violation happens here. // Warning 6328: (2165-2266): CHC: Assertion violation happens here. -// Warning 6328: (2675-2791): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nTestFixedMath.constructor()\nTestFixedMath.h()\n TestFixedMath.floor(11579208923731619542357098500868790785326998665640564039457584007913129639930) -- internal call\n FixedMath.floor(11579208923731619542357098500868790785326998665640564039457584007913129639930) -- internal call\n TestFixedMath.floor(115792089237316195423570985008687907853269984665640564039457584007913129639935) -- internal call\n FixedMath.floor(115792089237316195423570985008687907853269984665640564039457584007913129639935) -- internal call\n TestFixedMath.floor(11579208923731619542357098500868790785326998665640564039457584007913129639930) -- internal call\n FixedMath.floor(11579208923731619542357098500868790785326998665640564039457584007913129639930) -- internal call -// Warning 6328: (3161-3212): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nTestFixedMath.constructor()\nTestFixedMath.i()\n TestFixedMath.toUFixed256x18(0) -- internal call\n FixedMath.toUFixed256x18(0) -- internal call\n TestFixedMath.toUFixed256x18(5) -- internal call\n FixedMath.toUFixed256x18(5) -- internal call\n TestFixedMath.toUFixed256x18(115792089237316195423570985008687907853269984665640564039457) -- internal call\n FixedMath.toUFixed256x18(115792089237316195423570985008687907853269984665640564039457) -- internal call\n TestFixedMath.toUFixed256x18(5) -- internal call\n FixedMath.toUFixed256x18(5) -- internal call +// Warning 6328: (2675-2791): CHC: Assertion violation happens here. +// Warning 6328: (3161-3212): CHC: Assertion violation happens here. diff --git a/test/libsolidity/syntaxTests/constantEvaluator/type_reference.sol b/test/libsolidity/syntaxTests/constantEvaluator/type_reference.sol index 585c005f6..086d89151 100644 --- a/test/libsolidity/syntaxTests/constantEvaluator/type_reference.sol +++ b/test/libsolidity/syntaxTests/constantEvaluator/type_reference.sol @@ -1,3 +1,4 @@ int[L] constant L = 6; // ---- // TypeError 5462: (4-5): Invalid array length, expected integer literal or constant expression. +// TypeError 9259: (0-21): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/constantEvaluator/type_reference_in_contract.sol b/test/libsolidity/syntaxTests/constantEvaluator/type_reference_in_contract.sol index 9073f6ac1..dfb81378b 100644 --- a/test/libsolidity/syntaxTests/constantEvaluator/type_reference_in_contract.sol +++ b/test/libsolidity/syntaxTests/constantEvaluator/type_reference_in_contract.sol @@ -3,3 +3,4 @@ contract C { } // ---- // TypeError 5462: (21-22): Invalid array length, expected integer literal or constant expression. +// TypeError 9259: (17-38): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/constants/mapping_constant.sol b/test/libsolidity/syntaxTests/constants/mapping_constant.sol index 4d80fb43e..e6b5b124a 100644 --- a/test/libsolidity/syntaxTests/constants/mapping_constant.sol +++ b/test/libsolidity/syntaxTests/constants/mapping_constant.sol @@ -1,3 +1,3 @@ mapping(uint => uint) constant b = b; // ---- -// DeclarationError 3530: (0-36): The type contains a (nested) mapping and therefore cannot be a constant. +// TypeError 9259: (0-36): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/constants/struct_constant.sol b/test/libsolidity/syntaxTests/constants/struct_constant.sol index 8d750beb6..9195dac0c 100644 --- a/test/libsolidity/syntaxTests/constants/struct_constant.sol +++ b/test/libsolidity/syntaxTests/constants/struct_constant.sol @@ -1,4 +1,4 @@ struct S { uint x; } S constant s; // ---- -// TypeError 9259: (21-33): Constants of non-value type not yet implemented. +// TypeError 9259: (21-33): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/modifier_declaration_fine.sol b/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/modifier_declaration_fine.sol index 6df4939cf..260ae6b94 100644 --- a/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/modifier_declaration_fine.sol +++ b/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/modifier_declaration_fine.sol @@ -1,5 +1,5 @@ contract C { - modifier revertIfNoReturn() { + modifier alwaysRevert() { _; revert(); } @@ -9,10 +9,10 @@ contract C { } struct S { uint a; } S s; - function f(bool flag) revertIfNoReturn() internal view { + function f(bool flag) alwaysRevert() internal view { if (flag) s; } - function g(bool flag) revertIfNoReturn() ifFlag(flag) internal view { + function g(bool flag) alwaysRevert() ifFlag(flag) internal view { s; } diff --git a/test/libsolidity/syntaxTests/controlFlow/modifiers/modifier_different_functions.sol b/test/libsolidity/syntaxTests/controlFlow/modifiers/modifier_different_functions.sol new file mode 100644 index 000000000..63d915369 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/modifiers/modifier_different_functions.sol @@ -0,0 +1,12 @@ +contract A { + function f() mod internal returns (uint[] storage) { + revert(); + } + function g() mod internal returns (uint[] storage) { + } + modifier mod() virtual { + _; + } +} +// ---- +// TypeError 3464: (118-132): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/modifiers/modifier_override.sol b/test/libsolidity/syntaxTests/controlFlow/modifiers/modifier_override.sol new file mode 100644 index 000000000..3c5eb80ee --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/modifiers/modifier_override.sol @@ -0,0 +1,17 @@ +contract A { + function f() mod internal returns (uint[] storage) { + } + modifier mod() virtual { + revert(); + _; + } +} +contract B is A { + modifier mod() override { _; } + function g() public { + f()[0] = 42; + } +} +// ---- +// Warning 5740: (65-69): Unreachable code. +// TypeError 3464: (49-63): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol index bf896b86a..5dee487d1 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol @@ -1,5 +1,5 @@ contract C { - modifier revertIfNoReturn() { + modifier callAndRevert() { _; revert(); } @@ -13,10 +13,10 @@ contract C { return s; } - function g(bool flag) ifFlag(flag) revertIfNoReturn() internal view returns(S storage) { + function g(bool flag) ifFlag(flag) callAndRevert() internal view returns(S storage) { return s; } } // ---- -// TypeError 3464: (249-258): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. -// TypeError 3464: (367-376): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError 3464: (246-255): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError 3464: (361-370): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_fine.sol index ee37f6d64..7b7b3ba0c 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_fine.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_fine.sol @@ -1,5 +1,5 @@ contract C { - modifier revertIfNoReturn() { + modifier callAndRevert() { _; revert(); } @@ -9,10 +9,10 @@ contract C { } struct S { uint a; } S s; - function f(bool flag) revertIfNoReturn() internal view returns(S storage) { + function f(bool flag) callAndRevert() internal view returns(S storage) { if (flag) return s; } - function g(bool flag) revertIfNoReturn() ifFlag(flag) internal view returns(S storage) { + function g(bool flag) callAndRevert() ifFlag(flag) internal view returns(S storage) { return s; } diff --git a/test/libsolidity/syntaxTests/functionTypes/comparison_operator_for_external_functions_with_extra_gas_slots.sol b/test/libsolidity/syntaxTests/functionTypes/comparison_operator_for_external_functions_with_extra_gas_slots.sol new file mode 100644 index 000000000..7b0713caf --- /dev/null +++ b/test/libsolidity/syntaxTests/functionTypes/comparison_operator_for_external_functions_with_extra_gas_slots.sol @@ -0,0 +1,12 @@ +contract C { + function external_test_function() external {} + function comparison_operator_for_external_function_with_extra_slots() external returns (bool) { + return ( + (this.external_test_function{gas: 4} == this.external_test_function) && + (this.external_test_function{gas: 4} == this.external_test_function{gas: 4}) + ); + } +} +// ---- +// TypeError 2271: (193-259): Operator == not compatible with types function () external and function () external +// TypeError 2271: (277-351): Operator == not compatible with types function () external and function () external diff --git a/test/libsolidity/syntaxTests/functionTypes/comparison_operators_between_internal_and_external_function_pointers.sol b/test/libsolidity/syntaxTests/functionTypes/comparison_operators_between_internal_and_external_function_pointers.sol new file mode 100644 index 000000000..19f44658f --- /dev/null +++ b/test/libsolidity/syntaxTests/functionTypes/comparison_operators_between_internal_and_external_function_pointers.sol @@ -0,0 +1,23 @@ +contract C { + function external_test_function() external {} + function internal_test_function() internal {} + + function comparison_operator_between_internal_and_external_function_pointers() external returns (bool) { + function () external external_function_pointer_local = this.external_test_function; + function () internal internal_function_pointer_local = internal_test_function; + + assert( + this.external_test_function == external_function_pointer_local && + internal_function_pointer_local == internal_test_function + ); + assert( + internal_function_pointer_local != external_function_pointer_local && + internal_test_function != this.external_test_function + ); + + return true; + } +} +// ---- +// TypeError 2271: (606-672): Operator != not compatible with types function () and function () external +// TypeError 2271: (688-741): Operator != not compatible with types function () and function () external diff --git a/test/libsolidity/syntaxTests/functionTypes/comparison_operators_external_functions_with_different_parameters.sol b/test/libsolidity/syntaxTests/functionTypes/comparison_operators_external_functions_with_different_parameters.sol new file mode 100644 index 000000000..39381f454 --- /dev/null +++ b/test/libsolidity/syntaxTests/functionTypes/comparison_operators_external_functions_with_different_parameters.sol @@ -0,0 +1,26 @@ +contract C { + function external_test_function1(uint num) external {} + function external_test_function2(bool val) external {} + + function comparison_operator_between_internal_and_external_function_pointers() external returns (bool) { + function () external external_function_pointer_local1 = this.external_test_function1; + function () external external_function_pointer_local2 = this.external_test_function2; + + assert( + this.external_test_function1 == external_function_pointer_local1 && + this.external_test_function2 == external_function_pointer_local2 + ); + assert( + external_function_pointer_local2 != external_function_pointer_local1 && + this.external_test_function2 != this.external_test_function1 + ); + + return true; + } +} +// ---- +// TypeError 9574: (249-333): Type function (uint256) external is not implicitly convertible to expected type function () external. +// TypeError 9574: (343-427): Type function (bool) external is not implicitly convertible to expected type function () external. +// TypeError 2271: (458-522): Operator == not compatible with types function (uint256) external and function () external +// TypeError 2271: (538-602): Operator == not compatible with types function (bool) external and function () external +// TypeError 2271: (726-786): Operator != not compatible with types function (bool) external and function (uint256) external diff --git a/test/libsolidity/syntaxTests/functionTypes/external_functions_with_variable_number_of_stack_slots.sol b/test/libsolidity/syntaxTests/functionTypes/external_functions_with_variable_number_of_stack_slots.sol new file mode 100644 index 000000000..8593bdbf4 --- /dev/null +++ b/test/libsolidity/syntaxTests/functionTypes/external_functions_with_variable_number_of_stack_slots.sol @@ -0,0 +1,8 @@ +contract C { + function f (address) external returns (bool) { + this.f{gas: 42}.address; + } +} +// ---- +// Warning 6321: (56-60): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable. +// Warning 2018: (17-102): Function state mutability can be restricted to view diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/const_struct_with_mapping.sol b/test/libsolidity/syntaxTests/iceRegressionTests/const_struct_with_mapping.sol index f8e0896c3..2983b0e71 100644 --- a/test/libsolidity/syntaxTests/iceRegressionTests/const_struct_with_mapping.sol +++ b/test/libsolidity/syntaxTests/iceRegressionTests/const_struct_with_mapping.sol @@ -5,4 +5,4 @@ contract C { S public constant e = 0x1212121212121212121212121212121212121212; } // ---- -// DeclarationError 3530: (71-135): The type contains a (nested) mapping and therefore cannot be a constant. +// TypeError 9259: (71-135): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/immutable/base_ctor_return_no_immutables.sol b/test/libsolidity/syntaxTests/immutable/base_ctor_return_no_immutables.sol new file mode 100644 index 000000000..e6e668bc0 --- /dev/null +++ b/test/libsolidity/syntaxTests/immutable/base_ctor_return_no_immutables.sol @@ -0,0 +1,10 @@ +contract Parent { + constructor() { + return; + } +} + +contract Child is Parent { + uint public immutable baked = 123; +} + diff --git a/test/libsolidity/syntaxTests/inlineAssembly/assignment_to_function_pointer.sol b/test/libsolidity/syntaxTests/inlineAssembly/assignment_to_function_pointer.sol new file mode 100644 index 000000000..38e88eeeb --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/assignment_to_function_pointer.sol @@ -0,0 +1,9 @@ +contract C { + function f() public pure { + function() external g; + assembly { + g.address := 0x42 + g.selector := 0x23 + } + } +} diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/105_constant_input_parameter.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/105_constant_input_parameter.sol index aaf6f620a..e2ef8518d 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/105_constant_input_parameter.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/105_constant_input_parameter.sol @@ -3,3 +3,4 @@ contract test { } // ---- // DeclarationError 1788: (31-55): The "constant" keyword can only be used for state variables or variables at file level. +// TypeError 9259: (31-55): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/171_assignment_to_const_array_vars.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/171_assignment_to_const_array_vars.sol index 81b9eab8a..590cec5a3 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/171_assignment_to_const_array_vars.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/171_assignment_to_const_array_vars.sol @@ -2,4 +2,4 @@ contract C { uint[3] constant x = [uint(1), 2, 3]; } // ---- -// TypeError 9259: (17-53): Constants of non-value type not yet implemented. +// TypeError 9259: (17-53): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/173_constant_struct.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/173_constant_struct.sol index d32dd2768..6b5e7a13c 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/173_constant_struct.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/173_constant_struct.sol @@ -3,4 +3,4 @@ contract C { S constant x = S(5, new uint[](4)); } // ---- -// TypeError 9259: (52-86): Constants of non-value type not yet implemented. +// TypeError 9259: (52-86): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_forward_reference_struct.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_forward_reference_struct.sol new file mode 100644 index 000000000..d845e735c --- /dev/null +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_forward_reference_struct.sol @@ -0,0 +1,4 @@ +S constant x; +struct S { int y; } +// ---- +// TypeError 9259: (0-12): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_mapping.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_mapping.sol index 7539a99cb..803cdad53 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_mapping.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_mapping.sol @@ -2,4 +2,4 @@ contract C { mapping(uint => uint) constant x; } // ---- -// DeclarationError 3530: (17-49): The type contains a (nested) mapping and therefore cannot be a constant. +// TypeError 9259: (17-49): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_nested_mapping.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_nested_mapping.sol index 81e6e6b15..2fed9d631 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_nested_mapping.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/constant_nested_mapping.sol @@ -5,4 +5,4 @@ contract C { S public constant c; } // ---- -// DeclarationError 3530: (71-90): The type contains a (nested) mapping and therefore cannot be a constant. +// TypeError 9259: (71-90): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/parsing/location_specifiers_for_params.sol b/test/libsolidity/syntaxTests/parsing/location_specifiers_for_params.sol index 7d3dd5640..c5761a944 100644 --- a/test/libsolidity/syntaxTests/parsing/location_specifiers_for_params.sol +++ b/test/libsolidity/syntaxTests/parsing/location_specifiers_for_params.sol @@ -3,3 +3,4 @@ contract Foo { } // ---- // DeclarationError 1788: (30-55): The "constant" keyword can only be used for state variables or variables at file level. +// TypeError 9259: (30-55): Only constants of value type and byte array type are implemented. diff --git a/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol b/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol index 41b8f16cc..6cf39cfae 100644 --- a/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol +++ b/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol @@ -2,81 +2,47 @@ interface I { function fExternal(uint256 p, string memory t) external; } -library L { - function fExternal(uint256 p, string memory t) external {} +contract Other { + function fExternal(uint) external pure {} + function fPublic(uint) public pure {} + function fInternal(uint) internal pure {} } -contract C { - using L for uint256; +library L { + function fExternal(uint256 p, string memory t) external {} + function fInternal(uint256 p, string memory t) internal {} +} +contract Base { + function baseFunctionExternal(uint) external pure {} +} + +contract C is Base { function f(int a) public {} function f2(int a, string memory b) public {} - function f3(int a, int b) public {} function f4() public {} - function fInternal(uint256 p, string memory t) internal {} - function failFunctionArgsWrongType() public returns(bytes memory) { - return abi.encodeCall(this.f, ("test")); - } - function failFunctionArgsTooMany() public returns(bytes memory) { - return abi.encodeCall(this.f, (1, 2)); - } - function failFunctionArgsTooFew0() public returns(bytes memory) { - return abi.encodeCall(this.f, ()); - } - function failFunctionArgsTooFew1() public returns(bytes memory) { - return abi.encodeCall(this.f); - } - function failFunctionPtrMissing() public returns(bytes memory) { - return abi.encodeCall(1, this.f); - } - function failFunctionPtrWrongType() public returns(bytes memory) { - return abi.encodeCall(abi.encodeCall, (1, 2, 3, "test")); - } - function failFunctionInternal() public returns(bytes memory) { - return abi.encodeCall(fInternal, (1, "123")); - } - function failFunctionInternalFromVariable() public returns(bytes memory) { - function(uint256, string memory) internal localFunctionPointer = fInternal; - return abi.encodeCall(localFunctionPointer, (1, "123")); - } - function failFunctionArgsArrayLiteral() public returns(bytes memory) { - return abi.encodeCall(this.f3, [1, 2]); - } - function failLibraryPointerCall() public returns (bytes memory) { - return abi.encodeCall(L.fExternal, (1, "123")); - } - function failBoundLibraryPointerCall() public returns (bytes memory) { - uint256 x = 1; - return abi.encodeCall(x.fExternal, (1, "123")); - } - function failInterfacePointerCall() public returns (bytes memory) { - return abi.encodeCall(I.fExternal, (1, "123")); - } - function successFunctionArgsIntLiteralTuple() public returns(bytes memory) { + function successFunctionArgsIntLiteralTuple() public view returns(bytes memory) { return abi.encodeCall(this.f, (1)); } - function successFunctionArgsIntLiteral() public returns(bytes memory) { + function successFunctionArgsIntLiteral() public view returns(bytes memory) { return abi.encodeCall(this.f, 1); } - function successFunctionArgsLiteralTuple() public returns(bytes memory) { + function successFunctionArgsLiteralTuple() public view returns(bytes memory) { return abi.encodeCall(this.f2, (1, "test")); } - function successFunctionArgsEmptyTuple() public returns(bytes memory) { + function successFunctionArgsEmptyTuple() public view returns(bytes memory) { return abi.encodeCall(this.f4, ()); } + function viaDeclaration() public pure returns (bytes memory) { + return bytes.concat( + abi.encodeCall(Other.fExternal, (1)), + abi.encodeCall(Other.fPublic, (1)), + abi.encodeCall(I.fExternal, (1, "123")) + ); + } + function viaBaseDeclaration() public pure returns (bytes memory) { + return abi.encodeCall(Base.baseFunctionExternal, (1)); + } } // ---- -// TypeError 5407: (486-494): Cannot implicitly convert component at position 0 from "literal_string "test"" to "int256". -// TypeError 7788: (576-606): Expected 1 instead of 2 components for the tuple parameter. -// TypeError 7788: (687-713): Expected 1 instead of 0 components for the tuple parameter. -// TypeError 6219: (794-816): Expected two arguments: a function pointer followed by a tuple. -// TypeError 5511: (911-912): Expected first argument to be a function pointer, not "int_const 1". -// TypeError 3509: (1018-1032): Function must be "public" or "external". -// TypeError 3509: (1145-1154): Function must be "public" or "external". Did you forget to prefix "this."? -// TypeError 3509: (1350-1370): Function must be "public" or "external". -// TypeError 7515: (1469-1500): Expected a tuple with 2 components instead of a single non-tuple parameter. -// TypeError 5407: (1493-1499): Cannot implicitly convert component at position 0 from "uint8[2]" to "int256". -// TypeError 3509: (1596-1607): Function must be "public" or "external". -// TypeError 3509: (1738-1749): Function must be "public" or "external". -// TypeError 3509: (1860-1871): Function must be "public" or "external". diff --git a/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol new file mode 100644 index 000000000..a1c20f9bd --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol @@ -0,0 +1,27 @@ +contract C { + function f(int a) public {} + function f3(int a, int b) public {} + + function failFunctionArgsWrongType() public returns(bytes memory) { + return abi.encodeCall(this.f, ("test")); + } + function failFunctionArgsTooMany() public returns(bytes memory) { + return abi.encodeCall(this.f, (1, 2)); + } + function failFunctionArgsTooFew0() public returns(bytes memory) { + return abi.encodeCall(this.f, ()); + } + function failFunctionArgsTooFew1() public returns(bytes memory) { + return abi.encodeCall(this.f); + } + function failFunctionArgsArrayLiteral() public returns(bytes memory) { + return abi.encodeCall(this.f3, [1, 2]); + } +} +// ---- +// TypeError 5407: (181-189): Cannot implicitly convert component at position 0 from "literal_string "test"" to "int256". +// TypeError 7788: (271-301): Expected 1 instead of 2 components for the tuple parameter. +// TypeError 7788: (382-408): Expected 1 instead of 0 components for the tuple parameter. +// TypeError 6219: (489-511): Expected two arguments: a function pointer followed by a tuple. +// TypeError 7515: (597-628): Expected a tuple with 2 components instead of a single non-tuple parameter. +// TypeError 5407: (621-627): Cannot implicitly convert component at position 0 from "uint8[2]" to "int256". diff --git a/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_funType.sol b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_funType.sol new file mode 100644 index 000000000..e2de48387 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_funType.sol @@ -0,0 +1,78 @@ +interface I { + function fExternal(uint256 p, string memory t) external; +} + +contract Other { + function fExternal(uint) external pure {} + function fPublic(uint) public pure {} + function fInternal(uint) internal pure {} +} + +library L { + function fExternal(uint256 p, string memory t) external {} + function fInternal(uint256 p, string memory t) internal {} +} + +contract Base { + function baseFunctionInternal(uint) internal pure {} + function baseFunctionPublic(uint) public pure {} +} + +function fileLevel(uint) pure {} + +contract C is Base { + using L for uint256; + + function fPublic(int a) public {} + function fInternal(uint256 p, string memory t) internal {} + + function failFunctionPtrMissing() public returns(bytes memory) { + return abi.encodeCall(1, this.fPublic); + } + function failFunctionPtrWrongType() public returns(bytes memory) { + return abi.encodeCall(abi.encodeCall, (1, 2, 3, "test")); + } + function failFunctionInternal() public returns(bytes memory) { + return abi.encodeCall(fInternal, (1, "123")); + } + function failFunctionInternalFromVariable() public returns(bytes memory) { + function(uint256, string memory) internal localFunctionPointer = fInternal; + return abi.encodeCall(localFunctionPointer, (1, "123")); + } + function failLibraryPointerCall() public { + abi.encodeCall(L.fInternal, (1, "123")); + abi.encodeCall(L.fExternal, (1, "123")); + } + function failBoundLibraryPointerCall() public returns (bytes memory) { + uint256 x = 1; + return abi.encodeCall(x.fExternal, (1, "123")); + } + function viaBaseDeclaration() public pure returns (bytes memory) { + return abi.encodeCall(C.fPublic, (2)); + } + function viaBaseDeclaration2() public pure returns (bytes memory) { + return bytes.concat( + abi.encodeCall(Base.baseFunctionPublic, (1)), + abi.encodeCall(Base.baseFunctionInternal, (1)) + ); + } + function fileLevelFunction() public pure returns (bytes memory) { + return abi.encodeCall(fileLevel, (2)); + } + function createFunction() public pure returns (bytes memory) { + return abi.encodeCall(new Other, (2)); + } +} +// ---- +// TypeError 5511: (742-743): Expected first argument to be a function pointer, not "int_const 1". +// TypeError 3509: (855-869): Expected regular external function type, or external view on public function. Cannot use special function. +// TypeError 3509: (982-991): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (1187-1207): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (1286-1297): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (1329-1340): Expected regular external function type, or external view on public function. Cannot use library functions for abi.encodeCall. +// TypeError 3509: (1471-1482): Expected regular external function type, or external view on public function. Cannot use library functions for abi.encodeCall. +// TypeError 3509: (1592-1601): Expected regular external function type, or external view on public function. Provided internal function. Did you forget to prefix "this."? +// TypeError 3509: (1722-1745): Expected regular external function type, or external view on public function. Provided internal function. Functions from base contracts have to be external. +// TypeError 3509: (1771-1796): Expected regular external function type, or external view on public function. Provided internal function. Functions from base contracts have to be external. +// TypeError 3509: (1902-1911): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (2010-2019): Expected regular external function type, or external view on public function. Provided creation function. diff --git a/test/libyul/EwasmTranslationTest.cpp b/test/libyul/EwasmTranslationTest.cpp index 9bd80f4b1..17b7f1868 100644 --- a/test/libyul/EwasmTranslationTest.cpp +++ b/test/libyul/EwasmTranslationTest.cpp @@ -108,13 +108,18 @@ string EwasmTranslationTest::interpret() state.maxExprNesting = 64; try { - Interpreter::run(state, WasmDialect{}, *m_object->code); + Interpreter::run( + state, + WasmDialect{}, + *m_object->code, + /*disableMemoryTracing=*/false + ); } catch (InterpreterTerminatedGeneric const&) { } stringstream result; - state.dumpTraceAndState(result); + state.dumpTraceAndState(result, false); return result.str(); } diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index ba47b4c08..3e7de4d17 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -94,13 +94,18 @@ string YulInterpreterTest::interpret() state.maxExprNesting = 64; try { - Interpreter::run(state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), *m_ast); + Interpreter::run( + state, + EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), + *m_ast, + /*disableMemoryTracing=*/false + ); } catch (InterpreterTerminatedGeneric const&) { } stringstream result; - state.dumpTraceAndState(result); + state.dumpTraceAndState(result, false); return result.str(); } diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index cc315c4b9..e7711f586 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -236,6 +237,12 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( ForLoopInitRewriter::run(*m_context, *m_ast); UnusedAssignEliminator::run(*m_context, *m_ast); }}, + {"equalStoreEliminator", [&]() { + disambiguate(); + FunctionHoister::run(*m_context, *m_ast); + ForLoopInitRewriter::run(*m_context, *m_ast); + EqualStoreEliminator::run(*m_context, *m_ast); + }}, {"ssaPlusCleanup", [&]() { disambiguate(); ForLoopInitRewriter::run(*m_context, *m_ast); diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/branching.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/branching.yul new file mode 100644 index 000000000..63f0d38e4 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/branching.yul @@ -0,0 +1,25 @@ +{ + let a := calldataload(0) + let b := 20 + sstore(a, b) + if calldataload(32) { + sstore(a, b) + pop(staticcall(0, 0, 0, 0, 0, 0)) + sstore(a, b) + } + sstore(a, b) +} +// ==== +// EVMVersion: >=byzantium +// ---- +// step: equalStoreEliminator +// +// { +// let a := calldataload(0) +// let b := 20 +// sstore(a, b) +// if calldataload(32) +// { +// pop(staticcall(0, 0, 0, 0, 0, 0)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/forloop.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/forloop.yul new file mode 100644 index 000000000..ccd7dd40b --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/forloop.yul @@ -0,0 +1,20 @@ +{ + let x := calldataload(0) + let y := calldataload(1) + + sstore(x, y) + for {let a := 1} lt(a, 10) {a := add(a, 1) } { + sstore(x, y) + } +} +// ---- +// step: equalStoreEliminator +// +// { +// let x := calldataload(0) +// let y := calldataload(1) +// sstore(x, y) +// let a := 1 +// for { } lt(a, 10) { a := add(a, 1) } +// { sstore(x, y) } +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/functionbody.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/functionbody.yul new file mode 100644 index 000000000..68839721c --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/functionbody.yul @@ -0,0 +1,56 @@ +{ + f(calldataload(0), calldataload(32)) + h(calldataload(64), calldataload(96)) + + function f(a, b) { + // gets removed + sstore(a, b) + g() + sstore(a, b) + } + + function g() { + pop(staticcall(0, 0, 0, 0, 0, 0)) + } + + function h(a_, b_) { + // cannot be removed + sstore(a_, b_) + i() + sstore(a_, b_) + } + + function i() { + pop(delegatecall(0, 0, 0, 0, 0, 0)) + } + + +} +// ==== +// EVMVersion: >=byzantium +// ---- +// step: equalStoreEliminator +// +// { +// f(calldataload(0), calldataload(32)) +// h(calldataload(64), calldataload(96)) +// function f(a, b) +// { +// sstore(a, b) +// g() +// } +// function g() +// { +// pop(staticcall(0, 0, 0, 0, 0, 0)) +// } +// function h(a_, b_) +// { +// sstore(a_, b_) +// i() +// sstore(a_, b_) +// } +// function i() +// { +// pop(delegatecall(0, 0, 0, 0, 0, 0)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/indirect_inferrence.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/indirect_inferrence.yul new file mode 100644 index 000000000..2b68e6310 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/indirect_inferrence.yul @@ -0,0 +1,24 @@ +{ + let x := calldataload(0) + let y := sload(x) + // both of these can be removed + sstore(x, y) + sstore(x, y) + + let a := x + let b := mload(a) + // both of these can be removed + mstore(a, b) + mstore(a, b) +} +// ==== +// EVMVersion: >=byzantium +// ---- +// step: equalStoreEliminator +// +// { +// let x := calldataload(0) +// let y := sload(x) +// let a := x +// let b := mload(a) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_with_keccak.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_with_keccak.yul new file mode 100644 index 000000000..395fea6d0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_with_keccak.yul @@ -0,0 +1,31 @@ +{ + let var_k := calldataload(0) + let _1 := 0x00 + let _2 := 0x20 + mstore(_1, var_k) + mstore(_2, _1) + sstore(keccak256(_1, 0x40), 0x01) + mstore(_1, var_k) + mstore(_2, _1) + sstore(add(keccak256(_1, 0x40), 0x01), 0x03) + mstore(_1, var_k) + mstore(_2, _1) + sstore(add(keccak256(_1, 0x40), 2), 0x04) + mstore(_1, var_k) + mstore(_2, _1) + sstore(add(keccak256(_1, 0x40), 0x03), 2) +} +// ---- +// step: equalStoreEliminator +// +// { +// let var_k := calldataload(0) +// let _1 := 0x00 +// let _2 := 0x20 +// mstore(_1, var_k) +// mstore(_2, _1) +// sstore(keccak256(_1, 0x40), 0x01) +// sstore(add(keccak256(_1, 0x40), 0x01), 0x03) +// sstore(add(keccak256(_1, 0x40), 2), 0x04) +// sstore(add(keccak256(_1, 0x40), 0x03), 2) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/value_change.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/value_change.yul new file mode 100644 index 000000000..bd1e08bcd --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/value_change.yul @@ -0,0 +1,18 @@ +{ + let x := calldataload(0) + let y := calldataload(32) + sstore(x, y) + y := calldataload(64) + // cannot be removed + sstore(x, y) +} +// ---- +// step: equalStoreEliminator +// +// { +// let x := calldataload(0) +// let y := calldataload(32) +// sstore(x, y) +// y := calldataload(64) +// sstore(x, y) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/verbatim.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/verbatim.yul new file mode 100644 index 000000000..fcaddd121 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/verbatim.yul @@ -0,0 +1,27 @@ +{ + let a := calldataload(0) + let b := 20 + sstore(a, b) + if calldataload(32) { + sstore(a, b) + pop(staticcall(0, 0, 0, 0, 0, 0)) + verbatim_0i_0o("xyz") + } + sstore(a, b) +} +// ==== +// EVMVersion: >=byzantium +// ---- +// step: equalStoreEliminator +// +// { +// let a := calldataload(0) +// let b := 20 +// sstore(a, b) +// if calldataload(32) +// { +// pop(staticcall(0, 0, 0, 0, 0, 0)) +// verbatim_0i_0o("xyz") +// } +// sstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/stack_compressor_msize.yul b/test/libyul/yulOptimizerTests/fullSuite/stack_compressor_msize.yul index d670f002f..13683e8e6 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/stack_compressor_msize.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/stack_compressor_msize.yul @@ -55,7 +55,6 @@ // sstore(0, 0) // sstore(2, _1) // extcodecopy(_1, msize(), _1, _1) -// sstore(0, 0) // sstore(3, _1) // } // function gcd(_a, _b) -> out diff --git a/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_loop.yul b/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_loop.yul index b531842db..ee648f80f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_loop.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_loop.yul @@ -20,9 +20,7 @@ // f() // sstore(0, 1) // f() -// sstore(0, 1) // f() -// sstore(0, 1) // } // function f() // { diff --git a/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_simple.yul b/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_simple.yul index 531b1a6ce..68620cc96 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_simple.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/unusedFunctionParameterPruner_simple.yul @@ -18,9 +18,7 @@ // f() // sstore(0, 1) // f() -// sstore(0, 1) // f() -// sstore(0, 1) // } // function f() // { diff --git a/test/lsp.py b/test/lsp.py index d558c20d9..f03d9bc09 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -5,6 +5,7 @@ import fnmatch import json import os import subprocess +import sys import traceback from typing import Any, List, Optional, Tuple, Union @@ -49,7 +50,7 @@ class JsonRpcProcess: # Note, we should make use of timeout to avoid infinite blocking if nothing is received. CONTENT_LENGTH_HEADER = "Content-Length: " CONTENT_TYPE_HEADER = "Content-Type: " - if self.process.stdout == None: + if self.process.stdout is None: return None message_size = None while True: @@ -83,7 +84,7 @@ class JsonRpcProcess: return json_object def send_message(self, method_name: str, params: Optional[dict]) -> None: - if self.process.stdin == None: + if self.process.stdin is None: return message = { 'jsonrpc': '2.0', @@ -245,7 +246,7 @@ class SolidityLSPTestSuite: # {{{ } } } - if expose_project_root == False: + if not expose_project_root: params['rootUri'] = None lsp.call_method('initialize', params) lsp.send_notification('initialized') @@ -871,4 +872,4 @@ class SolidityLSPTestSuite: # {{{ if __name__ == "__main__": suite = SolidityLSPTestSuite() exit_code = suite.main() - exit(exit_code) + sys.exit(exit_code) diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 5ae1a3f99..ae2ce6e24 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -119,7 +119,8 @@ public: TestCreator _testCaseCreator, TestOptions const& _options, fs::path const& _basepath, - fs::path const& _path + fs::path const& _path, + solidity::test::Batcher& _batcher ); private: enum class Request @@ -269,7 +270,8 @@ TestStats TestTool::processPath( TestCreator _testCaseCreator, TestOptions const& _options, fs::path const& _basepath, - fs::path const& _path + fs::path const& _path, + solidity::test::Batcher& _batcher ) { std::queue paths; @@ -298,6 +300,11 @@ TestStats TestTool::processPath( ++testCount; paths.pop(); } + else if (!_batcher.checkAndAdvance()) + { + paths.pop(); + ++skippedCount; + } else { ++testCount; @@ -373,7 +380,8 @@ std::optional runTestSuite( TestOptions const& _options, fs::path const& _basePath, fs::path const& _subdirectory, - string const& _name + string const& _name, + solidity::test::Batcher& _batcher ) { fs::path testPath{_basePath / _subdirectory}; @@ -389,7 +397,8 @@ std::optional runTestSuite( _testCaseCreator, _options, _basePath, - _subdirectory + _subdirectory, + _batcher ); if (stats.skippedCount != stats.testCount) @@ -415,21 +424,23 @@ std::optional runTestSuite( int main(int argc, char const *argv[]) { + using namespace solidity::test; + try { setupTerminal(); { - auto options = std::make_unique(); + auto options = std::make_unique(); if (!options->parse(argc, argv)) return -1; options->validate(); - solidity::test::CommonOptions::setSingleton(std::move(options)); + CommonOptions::setSingleton(std::move(options)); } - auto& options = dynamic_cast(solidity::test::CommonOptions::get()); + auto& options = dynamic_cast(CommonOptions::get()); if (!solidity::test::loadVMs(options)) return 1; @@ -437,9 +448,16 @@ int main(int argc, char const *argv[]) if (options.disableSemanticTests) cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl; + if (!options.enforceGasTest) + cout << "WARNING :: Gas Cost Expectations are not being enforced" << endl << endl; + TestStats global_stats{0, 0}; cout << "Running tests..." << endl << endl; + Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches); + if (CommonOptions::get().batches > 1) + cout << "Batch " << CommonOptions::get().selectedBatch << " out of " << CommonOptions::get().batches << endl; + // Actually run the tests. // Interactive tests are added in InteractiveTests.h for (auto const& ts: g_interactiveTestsuites) @@ -455,7 +473,8 @@ int main(int argc, char const *argv[]) options, options.testPath / ts.path, ts.subpath, - ts.title + ts.title, + batcher ); if (stats) global_stats += *stats; diff --git a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp index a3a789bc9..c6d52a62e 100644 --- a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp @@ -81,10 +81,15 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) ostringstream os1; ostringstream os2; + // Disable memory tracing to avoid false positive reports + // such as unused write to memory e.g., + // { mstore(0, 1) } + // that would be removed by the redundant store eliminator. yulFuzzerUtil::TerminationReason termReason = yulFuzzerUtil::interpret( os1, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()) + EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()), + /*disableMemoryTracing=*/true ); if (yulFuzzerUtil::resourceLimitsExceeded(termReason)) return 0; @@ -93,7 +98,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) termReason = yulFuzzerUtil::interpret( os2, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()) + EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()), + /*disableMemoryTracing=*/true ); if (yulFuzzerUtil::resourceLimitsExceeded(termReason)) diff --git a/test/tools/ossfuzz/yulFuzzerCommon.cpp b/test/tools/ossfuzz/yulFuzzerCommon.cpp index 890aad3b8..cfbbbaa48 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.cpp +++ b/test/tools/ossfuzz/yulFuzzerCommon.cpp @@ -26,6 +26,7 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( ostream& _os, shared_ptr _ast, Dialect const& _dialect, + bool _disableMemoryTracing, bool _outputStorageOnly, size_t _maxSteps, size_t _maxTraceSize, @@ -52,7 +53,7 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( TerminationReason reason = TerminationReason::None; try { - Interpreter::run(state, _dialect, *_ast); + Interpreter::run(state, _dialect, *_ast, _disableMemoryTracing); } catch (StepLimitReached const&) { @@ -74,7 +75,7 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( if (_outputStorageOnly) state.dumpStorage(_os); else - state.dumpTraceAndState(_os); + state.dumpTraceAndState(_os, _disableMemoryTracing); return reason; } diff --git a/test/tools/ossfuzz/yulFuzzerCommon.h b/test/tools/ossfuzz/yulFuzzerCommon.h index 71411a708..4c8f665f6 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.h +++ b/test/tools/ossfuzz/yulFuzzerCommon.h @@ -32,10 +32,17 @@ struct yulFuzzerUtil None }; + /// Interprets the Yul AST pointed to by @param _ast. Flag @param _outputStorageOnly + /// (unset by default) outputs an execution trace of both memory and storage; + /// if set, only storage contents are output as part of the execution trace. The + /// latter avoids false positives that will be produced by the fuzzer when certain + /// optimizer steps are activated e.g., Redundant store eliminator, Equal store + /// eliminator. static TerminationReason interpret( std::ostream& _os, std::shared_ptr _ast, Dialect const& _dialect, + bool _disableMemoryTracing = false, bool _outputStorageOnly = false, size_t _maxSteps = maxSteps, size_t _maxTraceSize = maxTraceSize, diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index 22f67e1cf..4cafccb28 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -88,10 +88,15 @@ DEFINE_PROTO_FUZZER(Program const& _input) ostringstream os1; ostringstream os2; + // Disable memory tracing to avoid false positive reports + // such as unused write to memory e.g., + // { mstore(0, 1) } + // that would be removed by the redundant store eliminator. yulFuzzerUtil::TerminationReason termReason = yulFuzzerUtil::interpret( os1, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(version) + EVMDialect::strictAssemblyForEVMObjects(version), + /*disableMemoryTracing=*/true ); if (yulFuzzerUtil::resourceLimitsExceeded(termReason)) @@ -107,12 +112,18 @@ DEFINE_PROTO_FUZZER(Program const& _input) termReason = yulFuzzerUtil::interpret( os2, astBlock, - EVMDialect::strictAssemblyForEVMObjects(version) + EVMDialect::strictAssemblyForEVMObjects(version), + true ); if (yulFuzzerUtil::resourceLimitsExceeded(termReason)) return; bool isTraceEq = (os1.str() == os2.str()); - yulAssert(isTraceEq, "Interpreted traces for optimized and unoptimized code differ."); + if (!isTraceEq) + { + cout << os1.str() << endl; + cout << os2.str() << endl; + yulAssert(false, "Interpreted traces for optimized and unoptimized code differ."); + } return; } diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 1e030d098..810cf9d18 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -35,6 +36,7 @@ using namespace std; using namespace solidity; +using namespace solidity::evmasm; using namespace solidity::yul; using namespace solidity::yul::test; @@ -99,6 +101,7 @@ u256 EVMInstructionInterpreter::eval( switch (_instruction) { case Instruction::STOP: + logTrace(_instruction); BOOST_THROW_EXCEPTION(ExplicitlyTerminated()); // --------------- arithmetic --------------- case Instruction::ADD: @@ -204,6 +207,7 @@ u256 EVMInstructionInterpreter::eval( case Instruction::CALLDATASIZE: return m_state.calldata.size(); case Instruction::CALLDATACOPY: + logTrace(_instruction, arg); if (accessMemory(arg[0], arg[2])) copyZeroExtended( m_state.memory, m_state.calldata, @@ -213,6 +217,7 @@ u256 EVMInstructionInterpreter::eval( case Instruction::CODESIZE: return m_state.code.size(); case Instruction::CODECOPY: + logTrace(_instruction, arg); if (accessMemory(arg[0], arg[2])) copyZeroExtended( m_state.memory, m_state.code, @@ -339,12 +344,18 @@ u256 EVMInstructionInterpreter::eval( case Instruction::REVERT: accessMemory(arg[0], arg[1]); logTrace(_instruction, arg); + m_state.storage.clear(); + m_state.trace.clear(); BOOST_THROW_EXCEPTION(ExplicitlyTerminated()); case Instruction::INVALID: logTrace(_instruction); + m_state.storage.clear(); + m_state.trace.clear(); BOOST_THROW_EXCEPTION(ExplicitlyTerminated()); case Instruction::SELFDESTRUCT: logTrace(_instruction, arg); + m_state.storage.clear(); + m_state.trace.clear(); BOOST_THROW_EXCEPTION(ExplicitlyTerminated()); case Instruction::POP: break; @@ -476,7 +487,9 @@ bool EVMInstructionInterpreter::accessMemory(u256 const& _offset, u256 const& _s { u256 newSize = (_offset + _size + 0x1f) & ~u256(0x1f); m_state.msize = max(m_state.msize, newSize); - return _size <= 0xffff; + // We only record accesses to contiguous memory chunks that are at most 0xffff bytes + // in size and at an offset of at most numeric_limits::max() - 0xffff + return _size <= 0xffff && _offset <= u256(numeric_limits::max() - 0xffff); } else m_state.msize = u256(-1); @@ -505,23 +518,40 @@ void EVMInstructionInterpreter::writeMemoryWord(u256 const& _offset, u256 const& } -void EVMInstructionInterpreter::logTrace(evmasm::Instruction _instruction, std::vector const& _arguments, bytes const& _data) +void EVMInstructionInterpreter::logTrace( + evmasm::Instruction _instruction, + std::vector const& _arguments, + bytes const& _data +) { - logTrace(evmasm::instructionInfo(_instruction).name, _arguments, _data); + logTrace( + evmasm::instructionInfo(_instruction).name, + SemanticInformation::memory(_instruction) == SemanticInformation::Effect::Write, + _arguments, + _data + ); } -void EVMInstructionInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments, bytes const& _data) +void EVMInstructionInterpreter::logTrace( + std::string const& _pseudoInstruction, + bool _writesToMemory, + std::vector const& _arguments, + bytes const& _data +) { - string message = _pseudoInstruction + "("; - for (size_t i = 0; i < _arguments.size(); ++i) - message += (i > 0 ? ", " : "") + formatNumber(_arguments[i]); - message += ")"; - if (!_data.empty()) - message += " [" + util::toHex(_data) + "]"; - m_state.trace.emplace_back(std::move(message)); - if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize) + if (!(_writesToMemory && memWriteTracingDisabled())) { - m_state.trace.emplace_back("Trace size limit reached."); - BOOST_THROW_EXCEPTION(TraceLimitReached()); + string message = _pseudoInstruction + "("; + for (size_t i = 0; i < _arguments.size(); ++i) + message += (i > 0 ? ", " : "") + formatNumber(_arguments[i]); + message += ")"; + if (!_data.empty()) + message += " [" + util::toHex(_data) + "]"; + m_state.trace.emplace_back(std::move(message)); + if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize) + { + m_state.trace.emplace_back("Trace size limit reached."); + BOOST_THROW_EXCEPTION(TraceLimitReached()); + } } } diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.h b/test/tools/yulInterpreter/EVMInstructionInterpreter.h index 2da5b45e5..c05af8c1a 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.h +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.h @@ -66,8 +66,9 @@ struct InterpreterState; class EVMInstructionInterpreter { public: - explicit EVMInstructionInterpreter(InterpreterState& _state): - m_state(_state) + explicit EVMInstructionInterpreter(InterpreterState& _state, bool _disableMemWriteTrace): + m_state(_state), + m_disableMemoryWriteInstructions(_disableMemWriteTrace) {} /// Evaluate instruction u256 eval(evmasm::Instruction _instruction, std::vector const& _arguments); @@ -93,12 +94,29 @@ private: /// Does not adjust msize, use @a accessMemory for that void writeMemoryWord(u256 const& _offset, u256 const& _value); - void logTrace(evmasm::Instruction _instruction, std::vector const& _arguments = {}, bytes const& _data = {}); + void logTrace( + evmasm::Instruction _instruction, + std::vector const& _arguments = {}, + 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 = {}, bytes const& _data = {}); + /// with arguments and auxiliary data (if nonempty). Flag @param _writesToMemory indicates + /// whether the instruction writes to (true) or does not write to (false) memory. + void logTrace( + std::string const& _pseudoInstruction, + bool _writesToMemory, + std::vector const& _arguments = {}, + bytes const& _data = {} + ); + /// @returns disable trace flag. + bool memWriteTracingDisabled() + { + return m_disableMemoryWriteInstructions; + } InterpreterState& m_state; + /// Flag to disable trace of instructions that write to memory. + bool m_disableMemoryWriteInstructions; }; } // solidity::yul::test diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 08c6aafd6..bf0ad820b 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -55,26 +55,34 @@ void InterpreterState::dumpStorage(ostream& _out) const _out << " " << slot.first.hex() << ": " << slot.second.hex() << endl; } -void InterpreterState::dumpTraceAndState(ostream& _out) const +void InterpreterState::dumpTraceAndState(ostream& _out, bool _disableMemoryTrace) const { _out << "Trace:" << endl; for (auto const& line: trace) _out << " " << line << endl; - _out << "Memory dump:\n"; - map words; - for (auto const& [offset, value]: memory) - words[(offset / 0x20) * 0x20] |= u256(uint32_t(value)) << (256 - 8 - 8 * static_cast(offset % 0x20)); - for (auto const& [offset, value]: words) - if (value != 0) - _out << " " << std::uppercase << std::hex << std::setw(4) << offset << ": " << h256(value).hex() << endl; + if (!_disableMemoryTrace) + { + _out << "Memory dump:\n"; + map words; + for (auto const& [offset, value]: memory) + words[(offset / 0x20) * 0x20] |= u256(uint32_t(value)) << (256 - 8 - 8 * static_cast(offset % 0x20)); + for (auto const& [offset, value]: words) + if (value != 0) + _out << " " << std::uppercase << std::hex << std::setw(4) << offset << ": " << h256(value).hex() << endl; + } _out << "Storage dump:" << endl; dumpStorage(_out); } -void Interpreter::run(InterpreterState& _state, Dialect const& _dialect, Block const& _ast) +void Interpreter::run( + InterpreterState& _state, + Dialect const& _dialect, + Block const& _ast, + bool _disableMemoryTrace +) { Scope scope; - Interpreter{_state, _dialect, scope}(_ast); + Interpreter{_state, _dialect, scope, _disableMemoryTrace}(_ast); } void Interpreter::operator()(ExpressionStatement const& _expressionStatement) @@ -209,14 +217,14 @@ void Interpreter::operator()(Block const& _block) u256 Interpreter::evaluate(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables); + ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableMemoryTrace); ev.visit(_expression); return ev.value(); } vector Interpreter::evaluateMulti(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables); + ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableMemoryTrace); ev.visit(_expression); return ev.values(); } @@ -279,7 +287,7 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) { if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) { - EVMInstructionInterpreter interpreter(m_state); + EVMInstructionInterpreter interpreter(m_state, m_disableMemoryTrace); setValue(interpreter.evalBuiltin(*fun, _funCall.arguments, values())); return; } @@ -308,7 +316,7 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) variables[fun->returnVariables.at(i).name] = 0; m_state.controlFlowState = ControlFlowState::Default; - Interpreter interpreter(m_state, m_dialect, *scope, std::move(variables)); + Interpreter interpreter(m_state, m_dialect, *scope, m_disableMemoryTrace, std::move(variables)); interpreter(fun->body); m_state.controlFlowState = ControlFlowState::Default; diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 621305422..cdc454401 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -102,7 +102,10 @@ struct InterpreterState ControlFlowState controlFlowState = ControlFlowState::Default; /// Prints execution trace and non-zero storage to @param _out. - void dumpTraceAndState(std::ostream& _out) const; + /// Flag @param _disableMemoryTrace, if set, does not produce a memory dump. This + /// avoids false positives reports by the fuzzer when certain optimizer steps are + /// activated e.g., Redundant store eliminator, Equal store eliminator. + void dumpTraceAndState(std::ostream& _out, bool _disableMemoryTrace) const; /// Prints non-zero storage to @param _out. void dumpStorage(std::ostream& _out) const; }; @@ -124,18 +127,29 @@ struct Scope class Interpreter: public ASTWalker { public: - static void run(InterpreterState& _state, Dialect const& _dialect, Block const& _ast); + /// Executes the Yul interpreter. Flag @param _disableMemoryTracing if set ensures that + /// instructions that write to memory do not affect @param _state. This + /// avoids false positives reports by the fuzzer when certain optimizer steps are + /// activated e.g., Redundant store eliminator, Equal store eliminator. + static void run( + InterpreterState& _state, + Dialect const& _dialect, + Block const& _ast, + bool _disableMemoryTracing + ); Interpreter( InterpreterState& _state, Dialect const& _dialect, Scope& _scope, + bool _disableMemoryTracing, std::map _variables = {} ): m_dialect(_dialect), m_state(_state), m_variables(std::move(_variables)), - m_scope(&_scope) + m_scope(&_scope), + m_disableMemoryTrace(_disableMemoryTracing) { } @@ -173,6 +187,7 @@ private: /// Values of variables. std::map m_variables; Scope* m_scope; + bool m_disableMemoryTrace; }; /** @@ -185,12 +200,14 @@ public: InterpreterState& _state, Dialect const& _dialect, Scope& _scope, - std::map const& _variables + std::map const& _variables, + bool _disableMemoryTrace ): m_state(_state), m_dialect(_dialect), m_variables(_variables), - m_scope(_scope) + m_scope(_scope), + m_disableMemoryTrace(_disableMemoryTrace) {} void operator()(Literal const&) override; @@ -226,6 +243,8 @@ private: std::vector m_values; /// Current expression nesting level unsigned m_nestingLevel = 0; + /// Flag to disable memory tracing + bool m_disableMemoryTrace; }; } diff --git a/test/tools/yulrun.cpp b/test/tools/yulrun.cpp index cb90a3f43..570299768 100644 --- a/test/tools/yulrun.cpp +++ b/test/tools/yulrun.cpp @@ -87,13 +87,13 @@ void interpret(string const& _source) try { Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{})); - Interpreter::run(state, dialect, *ast); + Interpreter::run(state, dialect, *ast, /*disableMemoryTracing=*/false); } catch (InterpreterTerminatedGeneric const&) { } - state.dumpTraceAndState(cout); + state.dumpTraceAndState(cout, /*disableMemoryTracing=*/false); } } diff --git a/test/yulPhaser/Chromosome.cpp b/test/yulPhaser/Chromosome.cpp index 19616cde0..1b1e8bed5 100644 --- a/test/yulPhaser/Chromosome.cpp +++ b/test/yulPhaser/Chromosome.cpp @@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(output_operator_should_create_concise_and_unambiguous_strin BOOST_TEST(chromosome.length() == allSteps.size()); BOOST_TEST(chromosome.optimisationSteps() == allSteps); - BOOST_TEST(toString(chromosome) == "flcCUnDvejsxIOoighFTLMRmVatrpud"); + BOOST_TEST(toString(chromosome) == "flcCUnDEvejsxIOoighFTLMRmVatrpud"); } BOOST_AUTO_TEST_CASE(optimisationSteps_should_translate_chromosomes_genes_to_optimisation_step_names) diff --git a/test/yulPhaser/GeneticAlgorithms.cpp b/test/yulPhaser/GeneticAlgorithms.cpp index 16d5cc654..29c9cfa2e 100644 --- a/test/yulPhaser/GeneticAlgorithms.cpp +++ b/test/yulPhaser/GeneticAlgorithms.cpp @@ -217,6 +217,9 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_individuals_with_probability_ { constexpr double relativeTolerance = 0.1; constexpr size_t populationSize = 1000; + + SimulationRNG::reset(1); + assert(populationSize % 4 == 0 && "Choose a number divisible by 4 for this test"); auto population = diff --git a/test/yulPhaser/Mutations.cpp b/test/yulPhaser/Mutations.cpp index c42ff4e35..8e1d0238b 100644 --- a/test/yulPhaser/Mutations.cpp +++ b/test/yulPhaser/Mutations.cpp @@ -644,6 +644,8 @@ BOOST_AUTO_TEST_CASE(uniformCrossover_should_swap_tail_with_uniform_probability) Chromosome chromosome1("aaaaa"); Chromosome chromosome2("cccccccccc"); + SimulationRNG::reset(1); + vector bernoulliTrials; for (size_t i = 0; i < operationCount; ++i) {