diff --git a/.circleci/config.yml b/.circleci/config.yml index 0620cd667..15f122e48 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -173,6 +173,18 @@ defaults: - store_test_results: *store_test_results - store_artifacts: *artifacts_test_results + - test_asan_clang: &test_asan_clang + <<: *test_ubuntu2004_clang + steps: + - checkout + - attach_workspace: + at: build + - run: + <<: *run_soltest + no_output_timeout: 30m + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + # -------------------------------------------------------------------------- # Workflow Templates @@ -216,6 +228,11 @@ defaults: requires: - b_ubu_asan + - workflow_ubuntu2004_asan_clang: &workflow_ubuntu2004_asan_clang + <<: *workflow_trigger_on_tags + requires: + - b_ubu_asan_clang + - workflow_emscripten: &workflow_emscripten <<: *workflow_trigger_on_tags requires: @@ -380,6 +397,20 @@ jobs: - store_artifacts: *artifacts_solc - persist_to_workspace: *artifacts_executables + + b_ubu_asan_clang: &build_ubuntu2004_clang + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu2004-clang-<< pipeline.parameters.ubuntu-2004-clang-docker-image-rev >> + environment: + CC: clang + CXX: clang++ + CMAKE_OPTIONS: -DSANITIZE=address + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + b_ubu: &build_ubuntu2004 docker: - image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >> @@ -558,27 +589,15 @@ jobs: b_ems: docker: - - image: trzeci/emscripten:sdk-tag-1.39.3-64bit + - image: ethereum/solidity-buildpack-deps:emsdk-1.39.15-1 environment: TERM: xterm steps: - checkout - - restore_cache: - name: Restore Boost build - key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/build_emscripten.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }} - - run: - name: Bootstrap Boost - command: | - scripts/travis-emscripten/install_deps.sh - run: name: Build command: | scripts/travis-emscripten/build_emscripten.sh - - save_cache: - name: Save Boost build - key: *boost-cache-key - paths: - - boost_1_70_0_install - store_artifacts: path: emscripten_build/libsolc/soljson.js destination: soljson.js @@ -684,6 +703,14 @@ jobs: SOLTEST_FLAGS: --no-smt ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2 + t_ubu_asan_constantinople_clang: + <<: *test_asan_clang + environment: + EVM: constantinople + OPTIMIZE: 0 + SOLTEST_FLAGS: --no-smt + ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2 + t_ems_solcjs: docker: - image: buildpack-deps:latest @@ -848,7 +875,9 @@ workflows: # ASan build and tests - b_ubu_asan: *workflow_trigger_on_tags + - b_ubu_asan_clang: *workflow_trigger_on_tags - t_ubu_asan_constantinople: *workflow_ubuntu2004_asan + - t_ubu_asan_constantinople_clang: *workflow_ubuntu2004_asan_clang - t_ubu_asan_cli: *workflow_ubuntu2004_asan # Emscripten build and selected tests diff --git a/.circleci/docker/Dockerfile.emscripten b/.circleci/docker/Dockerfile.emscripten new file mode 100644 index 000000000..67432f6c5 --- /dev/null +++ b/.circleci/docker/Dockerfile.emscripten @@ -0,0 +1,66 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Emscripten +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# 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) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +# +# The Emscripten SDK at https://github.com/emscripten-core/emsdk/ +# contains a Makefile in the docker/ subdirectory that can be used to create the +# required base image using: +# +# make version=1.39.15-fastcomp build +# +# TODO: switch to the upstream backend by removing "-fastcomp". +# +FROM emscripten/emsdk:1.39.15-fastcomp AS base + +ADD emscripten.jam /usr/src +RUN set -ex; \ + cd /usr/src; \ + git clone https://github.com/Z3Prover/z3.git -b z3-4.8.8 --depth 1 ; \ + cd z3; \ + mkdir build; \ + cd build; \ + emcmake cmake \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DCMAKE_INSTALL_PREFIX=/emsdk/emscripten/sdk/system/ \ + -DZ3_BUILD_LIBZ3_SHARED=OFF \ + -DZ3_ENABLE_EXAMPLE_TARGETS=OFF \ + -DZ3_BUILD_TEST_EXECUTABLES=OFF \ + -DZ3_BUILD_EXECUTABLE=OFF \ + -DZ3_SINGLE_THREADED=ON \ + ..; \ + make; make install; \ + rm -r /usr/src/z3; \ + cd /usr/src; \ + wget -q 'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2' -O boost.tar.bz2; \ + test "$(sha256sum boost.tar.bz2)" = "4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402 boost.tar.bz2"; \ + tar -xf boost.tar.bz2; \ + rm boost.tar.bz2; \ + cd boost_1_73_0; \ + mv ../emscripten.jam .; \ + ./bootstrap.sh; \ + echo "using emscripten : : em++ ;" >> project-config.jam ; \ + ./b2 toolset=emscripten link=static variant=release threading=single runtime-link=static \ + --with-system --with-filesystem --with-test --with-program_options \ + cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \ + --prefix=/emsdk/emscripten/sdk/system install; \ + rm -r /usr/src/boost_1_73_0 diff --git a/scripts/travis-emscripten/emscripten.jam b/.circleci/docker/emscripten.jam similarity index 96% rename from scripts/travis-emscripten/emscripten.jam rename to .circleci/docker/emscripten.jam index 0cc92bd56..2d0ad524c 100644 --- a/scripts/travis-emscripten/emscripten.jam +++ b/.circleci/docker/emscripten.jam @@ -79,10 +79,10 @@ rule init ( version ? : command * : options * ) # @todo this seems to be the right way, but this is a list somehow toolset.add-requirements emscripten:node ; - toolset.flags emscripten.compile STDHDRS $(condition) : /emsdk_portable/emscripten/sdk/system/include ; - toolset.flags emscripten.link STDLIBPATH $(condition) : /emsdk_portable/emscripten/sdk/system/lib ; - toolset.flags emscripten AR $(condition) : /emsdk_portable/emscripten/sdk/emar ; - toolset.flags emscripten RANLIB $(condition) : /emsdk_portable/emscripten/sdk/emranlib ; + toolset.flags emscripten.compile STDHDRS $(condition) : /emsdk/emscripten/sdk/system/include ; + toolset.flags emscripten.link STDLIBPATH $(condition) : /emsdk/emscripten/sdk/system/lib ; + toolset.flags emscripten AR $(condition) : /emsdk/emscripten/sdk/emar ; + toolset.flags emscripten RANLIB $(condition) : /emsdk/emscripten/sdk/emranlib ; } type.set-generated-target-suffix EXE : emscripten : js ; diff --git a/.travis.yml b/.travis.yml index 38d59fa93..36d3943df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,7 +110,7 @@ matrix: before_install: - nvm install 8 - nvm use 8 - - docker pull trzeci/emscripten:sdk-tag-1.39.3-64bit + - docker pull ethereum/solidity-buildpack-deps:emsdk-1.39.15-1 env: - SOLC_EMSCRIPTEN=On - SOLC_INSTALL_DEPS_TRAVIS=Off diff --git a/Changelog.md b/Changelog.md index 3d0b4e26f..7e2eb67ab 100644 --- a/Changelog.md +++ b/Changelog.md @@ -21,10 +21,14 @@ Language Features: Compiler Features: + * Build system: Update the soljson.js build to emscripten 1.39.15 and boost 1.73.0 and include Z3 for integrated SMTChecker support without the callback mechanism. + * SMTChecker: Support array ``length``. + * SMTChecker: Support array ``push`` and ``pop``. Bugfixes: - + * Optimizer: Fixed a bug in BlockDeDuplicator. + * Type Checker: Disallow assignments to storage variables of type ``mapping``. ### 0.6.8 (2020-05-14) diff --git a/cmake/toolchains/ossfuzz.cmake b/cmake/toolchains/ossfuzz.cmake index 113b6b453..fb7b8d0b3 100644 --- a/cmake/toolchains/ossfuzz.cmake +++ b/cmake/toolchains/ossfuzz.cmake @@ -1,7 +1,8 @@ # Inherit default options include("${CMAKE_CURRENT_LIST_DIR}/default.cmake") -# Disable CVC4. +# Disable CVC4 and Z3. set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE) +set(USE_Z3 OFF CACHE BOOL "Disable Z3" FORCE) # Enable fuzzers set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE) set(LIB_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE} CACHE STRING "Use fuzzer back-end defined by environment variable" FORCE) diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index fa78b882a..57fb81e41 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -657,3 +657,14 @@ Notice that we do not clear knowledge about ``array`` and ``d`` because they are located in storage, even though they also have type ``uint[]``. However, if ``d`` was assigned, we would need to clear knowledge about ``array`` and vice-versa. + +Real World Assumptions +====================== + +Some scenarios can be expressed in Solidity and the EVM, but are expected to +never occur in practice. +One of such cases is the length of a dynamic storage array overflowing during a +push: If the ``push`` operation is applied to an array of length 2^256 - 1, its +length silently overflows. +However, this is unlikely to happen in practice, since the operations required +to grow the array to that point would take billions of years to execute. diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index e9e3630a8..27d825268 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -52,6 +52,7 @@ public: AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } + size_t numSubs() const { return m_subs.size(); } AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem newPushLibraryAddress(std::string const& _identifier); AssemblyItem newPushImmutable(std::string const& _identifier); diff --git a/libevmasm/BlockDeduplicator.cpp b/libevmasm/BlockDeduplicator.cpp index 9a26db8ca..decfee650 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -113,6 +113,10 @@ bool BlockDeduplicator::applyTagReplacement( if (subId != _subId) continue; auto it = _replacements.find(tagId); + // Recursively look for the element replaced by tagId + for (auto _it = it; _it != _replacements.end(); _it = _replacements.find(_it->second)) + it = _it; + if (it != _replacements.end()) { changed = true; diff --git a/liblangutil/ErrorReporter.h b/liblangutil/ErrorReporter.h index a52e978af..fed3430f2 100644 --- a/liblangutil/ErrorReporter.h +++ b/liblangutil/ErrorReporter.h @@ -143,6 +143,28 @@ public: // @returns true if the maximum error count has been reached. bool hasExcessiveErrors() const; + class ErrorWatcher + { + public: + ErrorWatcher(ErrorReporter const& _errorReporter): + m_errorReporter(_errorReporter), + m_initialErrorCount(_errorReporter.errorCount()) + {} + bool ok() const + { + solAssert(m_initialErrorCount <= m_errorReporter.errorCount(), "Unexpected error count."); + return m_initialErrorCount == m_errorReporter.errorCount(); + } + private: + ErrorReporter const& m_errorReporter; + unsigned const m_initialErrorCount; + }; + + ErrorWatcher errorWatcher() const + { + return ErrorWatcher(*this); + } + private: void error( ErrorId _error, diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 96ccff9e4..7b66e533e 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -79,6 +79,8 @@ set(sources codegen/ReturnInfo.cpp codegen/YulUtilFunctions.h codegen/YulUtilFunctions.cpp + codegen/ir/Common.cpp + codegen/ir/Common.h codegen/ir/IRGenerator.cpp codegen/ir/IRGenerator.h codegen/ir/IRGeneratorForStatements.cpp diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d78b74486..c8a476eec 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -455,7 +455,15 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (contractType->contractDefinition().isLibrary()) m_errorReporter.typeError(1273_error, _variable.location(), "The type of a variable cannot be a library."); if (_variable.value()) - expectType(*_variable.value(), *varType); + { + if (_variable.isStateVariable() && dynamic_cast(varType)) + { + m_errorReporter.typeError(6280_error, _variable.location(), "Mappings cannot be assigned to."); + _variable.value()->accept(*this); + } + else + expectType(*_variable.value(), *varType); + } if (_variable.isConstant()) { if (!_variable.type()->isValueType()) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 373a5a284..e553e45bd 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -153,10 +153,10 @@ FunctionDefinition const* ContractDefinition::receiveFunction() const vector const& ContractDefinition::interfaceEvents() const { - if (!m_interfaceEvents) - { + return m_interfaceEvents.init([&]{ set eventsSeen; - m_interfaceEvents = make_unique>(); + vector interfaceEvents; + for (ContractDefinition const* contract: annotation().linearizedBaseContracts) for (EventDefinition const* e: contract->events()) { @@ -169,19 +169,20 @@ vector const& ContractDefinition::interfaceEvents() cons if (eventsSeen.count(eventSignature) == 0) { eventsSeen.insert(eventSignature); - m_interfaceEvents->push_back(e); + interfaceEvents.push_back(e); } } - } - return *m_interfaceEvents; + + return interfaceEvents; + }); } vector, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const { - if (!m_interfaceFunctionList[_includeInheritedFunctions]) - { + return m_interfaceFunctionList[_includeInheritedFunctions].init([&]{ set signaturesSeen; - m_interfaceFunctionList[_includeInheritedFunctions] = make_unique, FunctionTypePointer>>>(); + vector, FunctionTypePointer>> interfaceFunctionList; + for (ContractDefinition const* contract: annotation().linearizedBaseContracts) { if (_includeInheritedFunctions == false && contract != this) @@ -203,12 +204,13 @@ vector, FunctionTypePointer>> const& ContractDefinition: { signaturesSeen.insert(functionSignature); util::FixedHash<4> hash(util::keccak256(functionSignature)); - m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun); + interfaceFunctionList.emplace_back(hash, fun); } } } - } - return *m_interfaceFunctionList[_includeInheritedFunctions]; + + return interfaceFunctionList; + }); } TypePointer ContractDefinition::type() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 0c4e181e8..055c0298b 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -536,8 +537,8 @@ private: ContractKind m_contractKind; bool m_abstract{false}; - mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList[2]; - mutable std::unique_ptr> m_interfaceEvents; + util::LazyInit, FunctionTypePointer>>> m_interfaceFunctionList[2]; + util::LazyInit> m_interfaceEvents; }; class InheritanceSpecifier: public ASTNode diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index b19ab4c4b..c25226379 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -207,26 +207,31 @@ void MemberList::combine(MemberList const & _other) pair const* MemberList::memberStorageOffset(string const& _name) const { - if (!m_storageOffsets) - { - TypePointers memberTypes; - memberTypes.reserve(m_memberTypes.size()); - for (auto const& member: m_memberTypes) - memberTypes.push_back(member.type); - m_storageOffsets = std::make_unique(); - m_storageOffsets->computeOffsets(memberTypes); - } + StorageOffsets const& offsets = storageOffsets(); + for (size_t index = 0; index < m_memberTypes.size(); ++index) if (m_memberTypes[index].name == _name) - return m_storageOffsets->offset(index); + return offsets.offset(index); return nullptr; } u256 const& MemberList::storageSize() const { - // trigger lazy computation - memberStorageOffset(""); - return m_storageOffsets->storageSize(); + return storageOffsets().storageSize(); +} + +StorageOffsets const& MemberList::storageOffsets() const { + return m_storageOffsets.init([&]{ + TypePointers memberTypes; + memberTypes.reserve(m_memberTypes.size()); + for (auto const& member: m_memberTypes) + memberTypes.push_back(member.type); + + StorageOffsets storageOffsets; + storageOffsets.computeOffsets(memberTypes); + + return storageOffsets; + }); } /// Helper functions for type identifier diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 11e948f26..120b1a42f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -139,8 +140,10 @@ public: MemberMap::const_iterator end() const { return m_memberTypes.end(); } private: + StorageOffsets const& storageOffsets() const; + MemberMap m_memberTypes; - mutable std::unique_ptr m_storageOffsets; + util::LazyInit m_storageOffsets; }; static_assert(std::is_nothrow_move_constructible::value, "MemberList should be noexcept move constructible"); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 34b2e1279..64639efb1 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -602,6 +602,25 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type) }); } +string YulUtilFunctions::extractByteArrayLengthFunction() +{ + string functionName = "extract_byte_array_length"; + return m_functionCollector.createFunction(functionName, [&]() { + Whiskers w(R"( + function (data) -> length { + // Retrieve length both for in-place strings and off-place strings: + // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 + // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it + // computes (x & (-1)) / 2, which is equivalent to just x / 2. + let mask := sub(mul(0x100, iszero(and(data, 1))), 1) + length := div(and(data, mask), 2) + } + )"); + w("functionName", functionName); + return w.render(); + }); +} + string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) { string functionName = "array_length_" + _type.identifier(); @@ -615,12 +634,7 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) length := sload(value) - // Retrieve length both for in-place strings and off-place strings: - // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 - // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it - // computes (x & (-1)) / 2, which is equivalent to just x / 2. - let mask := sub(mul(0x100, iszero(and(length, 1))), 1) - length := div(and(length, mask), 2) + length := (length) @@ -634,7 +648,12 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) w("length", toCompactHexWithPrefix(_type.length())); w("memory", _type.location() == DataLocation::Memory); w("storage", _type.location() == DataLocation::Storage); - w("byteArray", _type.isByteArray()); + if (_type.location() == DataLocation::Storage) + { + w("byteArray", _type.isByteArray()); + if (_type.isByteArray()) + w("extractByteArrayLength", extractByteArrayLengthFunction()); + } if (_type.isDynamicallySized()) solAssert( _type.location() != DataLocation::CallData, @@ -689,8 +708,9 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type) { solAssert(_type.location() == DataLocation::Storage, ""); solAssert(_type.isDynamicallySized(), ""); - solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); + if (_type.isByteArray()) + return storageByteArrayPopFunction(_type); string functionName = "array_pop_" + _type.identifier(); return m_functionCollector.createFunction(functionName, [&]() { @@ -699,10 +719,8 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type) let oldLen := (array) if iszero(oldLen) { invalid() } let newLen := sub(oldLen, 1) - let slot, offset := (array, newLen) (slot, offset) - sstore(array, newLen) })") ("functionName", functionName) @@ -713,29 +731,115 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type) }); } +string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + solAssert(_type.isByteArray(), ""); + + string functionName = "byte_array_pop_" + _type.identifier(); + return m_functionCollector.createFunction(functionName, [&]() { + return Whiskers(R"( + function (array) { + let data := sload(array) + let oldLen := (data) + if iszero(oldLen) { invalid() } + + switch eq(oldLen, 32) + case 1 { + // Here we have a special case where array transitions to shorter than 32 + // So we need to copy data + let copyFromSlot := (array) + data := sload(copyFromSlot) + sstore(copyFromSlot, 0) + // New length is 31, encoded to 31 * 2 = 62 + data := or(and(data, not(0xff)), 62) + } + default { + data := sub(data, 2) + let newLen := sub(oldLen, 1) + switch lt(oldLen, 32) + case 1 { + // set last element to zero + let mask := not((mul(8, sub(31, newLen)), 0xff)) + data := and(data, mask) + } + default { + let slot, offset := (array, newLen) + (slot, offset) + } + } + sstore(array, data) + })") + ("functionName", functionName) + ("extractByteArrayLength", extractByteArrayLengthFunction()) + ("dataAreaFunction", arrayDataAreaFunction(_type)) + ("indexAccess", storageArrayIndexAccessFunction(_type)) + ("setToZero", storageSetToZeroFunction(*_type.baseType())) + ("shl", shiftLeftFunctionDynamic()) + .render(); + }); +} + string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type) { solAssert(_type.location() == DataLocation::Storage, ""); solAssert(_type.isDynamicallySized(), ""); - solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); string functionName = "array_push_" + _type.identifier(); return m_functionCollector.createFunction(functionName, [&]() { return Whiskers(R"( function (array, value) { - let oldLen := (array) - if iszero(lt(oldLen, )) { invalid() } - sstore(array, add(oldLen, 1)) + + let data := sload(array) + let oldLen := (data) + if iszero(lt(oldLen, )) { invalid() } - let slot, offset := (array, oldLen) - (slot, offset, value) + switch gt(oldLen, 31) + case 0 { + value := byte(0, value) + switch oldLen + case 31 { + // Here we have special case when array switches from short array to long array + // We need to copy data + let dataArea := (array) + data := and(data, not(0xff)) + sstore(dataArea, or(and(0xff, value), data)) + // New length is 32, encoded as (32 * 2 + 1) + sstore(array, 65) + } + default { + data := add(data, 2) + let shiftBits := mul(8, sub(31, oldLen)) + let valueShifted := (shiftBits, and(0xff, value)) + let mask := (shiftBits, 0xff) + data := or(and(data, not(mask)), valueShifted) + sstore(array, data) + } + } + default { + sstore(array, add(data, 2)) + let slot, offset := (array, oldLen) + (slot, offset, value) + } + + let oldLen := sload(array) + if iszero(lt(oldLen, )) { invalid() } + sstore(array, add(oldLen, 1)) + let slot, offset := (array, oldLen) + (slot, offset, value) + })") ("functionName", functionName) - ("fetchLength", arrayLengthFunction(_type)) + ("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "") + ("dataAreaFunction", arrayDataAreaFunction(_type)) + ("isByteArray", _type.isByteArray()) ("indexAccess", storageArrayIndexAccessFunction(_type)) ("storeValue", updateStorageValueFunction(*_type.baseType())) ("maxArrayLength", (u256(1) << 64).str()) + ("shl", shiftLeftFunctionDynamic()) + ("shr", shiftRightFunction(248)) .render(); }); } @@ -947,21 +1051,33 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type) { - solUnimplementedAssert(_type.baseType()->storageBytes() > 16, ""); - string functionName = "storage_array_index_access_" + _type.identifier(); return m_functionCollector.createFunction(functionName, [&]() { return Whiskers(R"( function (array, index) -> slot, offset { - if iszero(lt(index, (array))) { - invalid() - } + let arrayLength := (array) + if iszero(lt(index, arrayLength)) { invalid() } - let data := (array) - + + offset := sub(31, mod(index, 0x20)) + switch lt(arrayLength, 0x20) + case 0 { + let dataArea := (array) + slot := add(dataArea, div(index, 0x20)) + } + default { + slot := array + } + + let itemsPerSlot := div(0x20, ) + let dataArea := (array) + slot := add(dataArea, div(index, itemsPerSlot)) + offset := mod(index, itemsPerSlot) + - slot := add(data, mul(index, )) + let dataArea := (array) + slot := add(dataArea, mul(index, )) offset := 0 } @@ -970,7 +1086,9 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type) ("arrayLen", arrayLengthFunction(_type)) ("dataAreaFunc", arrayDataAreaFunction(_type)) ("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16) + ("isBytesArray", _type.isByteArray()) ("storageSize", _type.baseType()->storageSize().str()) + ("storageBytes", toString(_type.baseType()->storageBytes())) .render(); }); } diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index e72d6d293..c23a7d735 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -364,6 +364,14 @@ private: /// use exactly one variable to hold the value. std::string conversionFunctionSpecial(Type const& _from, Type const& _to); + /// @returns function name that extracts and returns byte array length + /// signature: (data) -> length + std::string extractByteArrayLengthFunction(); + + /// @returns the name of a function that reduces the size of a storage byte array by one element + /// signature: (byteArray) + std::string storageByteArrayPopFunction(ArrayType const& _type); + std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata); langutil::EVMVersion m_evmVersion; diff --git a/libsolidity/codegen/ir/Common.cpp b/libsolidity/codegen/ir/Common.cpp new file mode 100644 index 000000000..8b895596a --- /dev/null +++ b/libsolidity/codegen/ir/Common.cpp @@ -0,0 +1,86 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include + +using namespace std; +using namespace solidity::util; +using namespace solidity::frontend; + +string IRNames::function(FunctionDefinition const& _function) +{ + // @TODO previously, we had to distinguish creation context and runtime context, + // but since we do not work with jump positions anymore, this should not be a problem, right? + return "fun_" + _function.name() + "_" + to_string(_function.id()); +} + +string IRNames::function(VariableDeclaration const& _varDecl) +{ + return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id()); +} + +string IRNames::creationObject(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + toString(_contract.id()); +} + +string IRNames::runtimeObject(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + toString(_contract.id()) + "_deployed"; +} + +string IRNames::implicitConstructor(ContractDefinition const& _contract) +{ + return "constructor_" + _contract.name() + "_" + to_string(_contract.id()); +} + +string IRNames::constantValueFunction(VariableDeclaration const& _constant) +{ + solAssert(_constant.isConstant(), ""); + return "constant_" + _constant.name() + "_" + to_string(_constant.id()); +} + +string IRNames::localVariable(VariableDeclaration const& _declaration) +{ + return "vloc_" + _declaration.name() + '_' + std::to_string(_declaration.id()); +} + +string IRNames::localVariable(Expression const& _expression) +{ + return "expr_" + to_string(_expression.id()); +} + +string IRNames::trySuccessConditionVariable(Expression const& _expression) +{ + auto annotation = dynamic_cast(&_expression.annotation()); + solAssert(annotation, ""); + solAssert(annotation->tryCall, "Parameter must be a FunctionCall with tryCall-annotation set."); + + return "trySuccessCondition_" + to_string(_expression.id()); +} + +string IRNames::tupleComponent(size_t _i) +{ + return "component_" + to_string(_i + 1); +} + +string IRNames::zeroValue(Type const& _type, string const& _variableName) +{ + return "zero_value_for_type_" + _type.identifier() + _variableName; +} diff --git a/libsolidity/codegen/ir/Common.h b/libsolidity/codegen/ir/Common.h new file mode 100644 index 000000000..771dd0ab4 --- /dev/null +++ b/libsolidity/codegen/ir/Common.h @@ -0,0 +1,47 @@ +/* + 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 . +*/ +/** + * Miscellaneous utilities for use in IR generator. + */ + +#pragma once + +#include + +#include + +namespace solidity::frontend +{ + +struct IRNames +{ + static std::string function(FunctionDefinition const& _function); + static std::string function(VariableDeclaration const& _varDecl); + static std::string creationObject(ContractDefinition const& _contract); + static std::string runtimeObject(ContractDefinition const& _contract); + static std::string implicitConstructor(ContractDefinition const& _contract); + static std::string constantValueFunction(VariableDeclaration const& _constant); + static std::string localVariable(VariableDeclaration const& _declaration); + static std::string localVariable(Expression const& _expression); + /// @returns the variable name that can be used to inspect the success or failure of an external + /// function call that was invoked as part of the try statement. + static std::string trySuccessConditionVariable(Expression const& _expression); + static std::string tupleComponent(size_t _i); + static std::string zeroValue(Type const& _type, std::string const& _variableName); +}; + +} diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index ed3b989d0..ee477792f 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -36,7 +36,7 @@ using namespace solidity::frontend; string IRGenerationContext::enqueueFunctionForCodeGeneration(FunctionDefinition const& _function) { - string name = functionName(_function); + string name = IRNames::function(_function); if (!m_functions.contains(name)) m_functionGenerationQueue.insert(&_function); @@ -116,43 +116,11 @@ void IRGenerationContext::addStateVariable( m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset); } -string IRGenerationContext::functionName(FunctionDefinition const& _function) -{ - // @TODO previously, we had to distinguish creation context and runtime context, - // but since we do not work with jump positions anymore, this should not be a problem, right? - return "fun_" + _function.name() + "_" + to_string(_function.id()); -} - -string IRGenerationContext::functionName(VariableDeclaration const& _varDecl) -{ - return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id()); -} - -string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const -{ - return _contract.name() + "_" + toString(_contract.id()); -} -string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const -{ - return _contract.name() + "_" + toString(_contract.id()) + "_deployed"; -} - string IRGenerationContext::newYulVariable() { return "_" + to_string(++m_varCounter); } -string IRGenerationContext::trySuccessConditionVariable(Expression const& _expression) const -{ - // NB: The TypeChecker already ensured that the Expression is of type FunctionCall. - solAssert( - static_cast(_expression.annotation()).tryCall, - "Parameter must be a FunctionCall with tryCall-annotation set." - ); - - return "trySuccessCondition_" + to_string(_expression.id()); -} - string IRGenerationContext::internalDispatch(size_t _in, size_t _out) { string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out); @@ -196,7 +164,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) functions.emplace_back(map { { "funID", to_string(function->id()) }, - { "name", functionName(*function)} + { "name", IRNames::function(*function)} }); enqueueFunctionForCodeGeneration(*function); diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index a7eab7ceb..ef6df190b 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -26,6 +26,7 @@ #include #include +#include #include @@ -99,12 +100,6 @@ public: return m_stateVariables.at(&_varDecl); } - std::string functionName(FunctionDefinition const& _function); - std::string functionName(VariableDeclaration const& _varDecl); - - std::string creationObjectName(ContractDefinition const& _contract) const; - std::string runtimeObjectName(ContractDefinition const& _contract) const; - std::string newYulVariable(); std::string internalDispatch(size_t _in, size_t _out); @@ -122,10 +117,6 @@ public: RevertStrings revertStrings() const { return m_revertStrings; } - /// @returns the variable name that can be used to inspect the success or failure of an external - /// function call that was invoked as part of the try statement. - std::string trySuccessConditionVariable(Expression const& _expression) const; - std::set& subObjectsCreated() { return m_subObjects; } private: diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index be878e739..69aad0af6 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -114,7 +114,7 @@ string IRGenerator::generate( for (VariableDeclaration const* var: ContractType(_contract).immutableVariables()) m_context.registerImmutableVariable(*var); - t("CreationObject", m_context.creationObjectName(_contract)); + t("CreationObject", IRNames::creationObject(_contract)); t("memoryInit", memoryInit()); t("notLibrary", !_contract.isLibrary()); @@ -127,12 +127,12 @@ string IRGenerator::generate( constructorParams.emplace_back(m_context.newYulVariable()); t( "copyConstructorArguments", - m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract)) + m_utils.copyConstructorArgumentsToMemoryFunction(_contract, IRNames::creationObject(_contract)) ); } t("constructorParams", joinHumanReadable(constructorParams)); t("constructorHasParams", !constructorParams.empty()); - t("implicitConstructor", implicitConstructorName(_contract)); + t("implicitConstructor", IRNames::implicitConstructor(_contract)); t("deploy", deployCode(_contract)); generateImplicitConstructors(_contract); @@ -142,7 +142,7 @@ string IRGenerator::generate( resetContext(_contract); // Do not register immutables to avoid assignment. - t("RuntimeObject", m_context.runtimeObjectName(_contract)); + t("RuntimeObject", IRNames::runtimeObject(_contract)); t("dispatch", dispatchRoutine(_contract)); generateQueuedFunctions(); t("runtimeFunctions", m_context.functionCollector().requestedFunctions()); @@ -166,7 +166,7 @@ void IRGenerator::generateQueuedFunctions() string IRGenerator::generateFunction(FunctionDefinition const& _function) { - string functionName = m_context.functionName(_function); + string functionName = IRNames::function(_function); return m_context.functionCollector().createFunction(functionName, [&]() { Whiskers t(R"( function () -> { @@ -195,7 +195,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function) string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) { - string functionName = m_context.functionName(_varDecl); + string functionName = IRNames::function(_varDecl); Type const* type = _varDecl.annotation().type; @@ -378,7 +378,7 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i]; baseConstructorParams.erase(contract); - m_context.functionCollector().createFunction(implicitConstructorName(*contract), [&]() { + m_context.functionCollector().createFunction(IRNames::implicitConstructor(*contract), [&]() { Whiskers t(R"( function () { @@ -395,7 +395,7 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra vector baseParams = listAllParams(baseConstructorParams); t("baseParams", joinHumanReadable(baseParams)); t("comma", !params.empty() && !baseParams.empty() ? ", " : ""); - t("functionName", implicitConstructorName(*contract)); + t("functionName", IRNames::implicitConstructor(*contract)); pair>> evaluatedArgs = evaluateConstructorArguments(*contract); baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end()); t("evalBaseArguments", evaluatedArgs.first); @@ -403,7 +403,7 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra { t("hasNextConstructor", true); ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1]; - t("nextConstructor", implicitConstructorName(*nextContract)); + t("nextConstructor", IRNames::implicitConstructor(*nextContract)); t("nextParams", joinHumanReadable(listAllParams(baseConstructorParams))); } else @@ -431,7 +431,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract) return(0, datasize("")) )X"); - t("object", m_context.runtimeObjectName(_contract)); + t("object", IRNames::runtimeObject(_contract)); vector> loadImmutables; vector> storeImmutables; @@ -462,11 +462,6 @@ string IRGenerator::callValueCheck() return "if callvalue() { revert(0, 0) }"; } -string IRGenerator::implicitConstructorName(ContractDefinition const& _contract) -{ - return "constructor_" + _contract.name() + "_" + to_string(_contract.id()); -} - string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) { Whiskers t(R"X( diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index 642cfcdfb..bff0c0739 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -92,8 +92,6 @@ private: std::string deployCode(ContractDefinition const& _contract); std::string callValueCheck(); - std::string implicitConstructorName(ContractDefinition const& _contract); - std::string dispatchRoutine(ContractDefinition const& _contract); std::string memoryInit(); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 553f618cd..58cd606ba 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -181,7 +181,7 @@ IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expre string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const& _constant) { - string functionName = "constant_" + _constant.name() + "_" + to_string(_constant.id()); + string functionName = IRNames::constantValueFunction(_constant); return m_context.functionCollector().createFunction(functionName, [&] { Whiskers templ(R"( function () -> { @@ -952,14 +952,6 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) "))\n"; break; } - case FunctionType::Kind::ECRecover: - case FunctionType::Kind::SHA256: - case FunctionType::Kind::RIPEMD160: - { - solAssert(!_functionCall.annotation().tryCall, ""); - appendExternalFunctionCall(_functionCall, arguments); - break; - } case FunctionType::Kind::ArrayPop: { auto const& memberAccessExpression = dynamic_cast(_functionCall.expression()).expression(); @@ -971,10 +963,12 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) ")\n"; break; } + case FunctionType::Kind::ByteArrayPush: case FunctionType::Kind::ArrayPush: { auto const& memberAccessExpression = dynamic_cast(_functionCall.expression()).expression(); ArrayType const& arrayType = dynamic_cast(*memberAccessExpression.annotation().type); + if (arguments.empty()) { auto slotName = m_context.newYulVariable(); @@ -1111,7 +1105,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) t("memEnd", m_context.newYulVariable()); t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction()); t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction()); - t("object", m_context.creationObjectName(*contract)); + t("object", IRNames::creationObject(*contract)); t("abiEncode", m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(), false) ); @@ -1153,6 +1147,69 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) break; } + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::SHA256: + { + solAssert(!_functionCall.annotation().tryCall, ""); + solAssert(!functionType->valueSet(), ""); + solAssert(!functionType->gasSet(), ""); + solAssert(!functionType->bound(), ""); + + static map> precompiles = { + {FunctionType::Kind::ECRecover, std::make_tuple(1, 0)}, + {FunctionType::Kind::SHA256, std::make_tuple(2, 0)}, + {FunctionType::Kind::RIPEMD160, std::make_tuple(3, 12)}, + }; + auto [ address, offset ] = precompiles[functionType->kind()]; + TypePointers argumentTypes; + vector argumentStrings; + for (auto const& arg: arguments) + { + argumentTypes.emplace_back(&type(*arg)); + argumentStrings += IRVariable(*arg).stackSlots(); + } + Whiskers templ(R"( + let := () + let := ( ) + + mstore(0, 0) + + let := (,
, 0, , sub(, ), 0, 32) + if iszero() { () } + let := (mload(0)) + )"); + templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call"); + templ("isCall", !m_context.evmVersion().hasStaticCall()); + templ("shl", m_utils.shiftLeftFunction(offset * 8)); + templ("allocateTemporary", m_utils.allocationTemporaryMemoryFunction()); + templ("pos", m_context.newYulVariable()); + templ("end", m_context.newYulVariable()); + templ("isECRecover", FunctionType::Kind::ECRecover == functionType->kind()); + if (FunctionType::Kind::ECRecover == functionType->kind()) + templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, parameterTypes)); + else + templ("encodeArgs", m_context.abiFunctions().tupleEncoderPacked(argumentTypes, parameterTypes)); + templ("argumentString", joinHumanReadablePrefixed(argumentStrings)); + templ("address", toString(address)); + templ("success", m_context.newYulVariable()); + templ("retVars", IRVariable(_functionCall).commaSeparatedList()); + templ("forwardingRevert", m_utils.forwardingRevertFunction()); + if (m_context.evmVersion().canOverchargeGasForCall()) + // Send all gas (requires tangerine whistle EVM) + templ("gas", "gas()"); + else + { + // @todo The value 10 is not exact and this could be fine-tuned, + // but this has worked for years in the old code generator. + u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10 + evmasm::GasCosts::callNewAccountGas; + templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); + } + + m_code << templ.render(); + + break; + } default: solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented"); } @@ -1316,7 +1373,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) )") ("allocationFunction", m_utils.allocationFunction()) ("size", m_context.newYulVariable()) - ("objectName", m_context.creationObjectName(contract)) + ("objectName", IRNames::creationObject(contract)) ("result", IRVariable(_memberAccess).commaSeparatedList()).render(); } else if (member == "name") @@ -1835,7 +1892,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( templ("pos", m_context.newYulVariable()); templ("end", m_context.newYulVariable()); if (_functionCall.annotation().tryCall) - templ("success", m_context.trySuccessConditionVariable(_functionCall)); + templ("success", IRNames::trySuccessConditionVariable(_functionCall)); else templ("success", m_context.newYulVariable()); templ("freeMemory", freeMemory()); @@ -2070,10 +2127,7 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes) { - IRVariable irVar{ - "zero_value_for_type_" + _type.identifier() + m_context.newYulVariable(), - _type - }; + IRVariable irVar{IRNames::zeroValue(_type, m_context.newYulVariable()), _type}; define(irVar) << m_utils.zeroValueFunction(_type, _splitFunctionTypes) << "()\n"; return irVar; } @@ -2392,7 +2446,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement) Expression const& externalCall = _tryStatement.externalCall(); externalCall.accept(*this); - m_code << "switch iszero(" << m_context.trySuccessConditionVariable(externalCall) << ")\n"; + m_code << "switch iszero(" << IRNames::trySuccessConditionVariable(externalCall) << ")\n"; m_code << "case 0 { // success case\n"; TryCatchClause const& successClause = *_tryStatement.clauses().front(); diff --git a/libsolidity/codegen/ir/IRVariable.cpp b/libsolidity/codegen/ir/IRVariable.cpp index 1c5ed42bf..8cb1896a6 100644 --- a/libsolidity/codegen/ir/IRVariable.cpp +++ b/libsolidity/codegen/ir/IRVariable.cpp @@ -14,6 +14,7 @@ You should have received a copy of the GNU General Public License along with solidity. If not, see . */ +#include #include #include #include @@ -30,19 +31,13 @@ IRVariable::IRVariable(std::string _baseName, Type const& _type): } IRVariable::IRVariable(VariableDeclaration const& _declaration): - IRVariable( - "vloc_" + _declaration.name() + '_' + std::to_string(_declaration.id()), - *_declaration.annotation().type - ) + IRVariable(IRNames::localVariable(_declaration), *_declaration.annotation().type) { solAssert(!_declaration.isStateVariable(), ""); } IRVariable::IRVariable(Expression const& _expression): - IRVariable( - "expr_" + to_string(_expression.id()), - *_expression.annotation().type - ) + IRVariable(IRNames::localVariable(_expression), *_expression.annotation().type) { } @@ -99,7 +94,7 @@ IRVariable IRVariable::tupleComponent(size_t _i) const m_type.category() == Type::Category::Tuple, "Requested tuple component of non-tuple IR variable." ); - return part("component_" + std::to_string(_i + 1)); + return part(IRNames::tupleComponent(_i)); } string IRVariable::suffixedName(string const& _suffix) const diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp index 1453df34f..f30959972 100644 --- a/libsolidity/formal/CHC.cpp +++ b/libsolidity/formal/CHC.cpp @@ -98,17 +98,45 @@ void CHC::analyze(SourceUnit const& _source) for (auto const& [scope, target]: m_verificationTargets) { - auto assertions = transactionAssertions(scope); - for (auto const* assertion: assertions) + if (target.type == VerificationTarget::Type::Assert) { + auto assertions = transactionAssertions(scope); + for (auto const* assertion: assertions) + { + createErrorBlock(); + connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id())); + auto [result, model] = query(error(), assertion->location()); + // This should be fine but it's a bug in the old compiler + (void)model; + if (result == smt::CheckResult::UNSATISFIABLE) + m_safeAssertions.insert(assertion); + } + } + else if (target.type == VerificationTarget::Type::PopEmptyArray) + { + solAssert(dynamic_cast(scope), ""); createErrorBlock(); - connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id())); - auto [result, model] = query(error(), assertion->location()); + connectBlocks(target.value, error(), target.constraints && (target.errorId == scope->id())); + auto [result, model] = query(error(), scope->location()); // This should be fine but it's a bug in the old compiler (void)model; - if (result == smt::CheckResult::UNSATISFIABLE) - m_safeAssertions.insert(assertion); + if (result != smt::CheckResult::UNSATISFIABLE) + { + string msg = "Empty array \"pop\" "; + if (result == smt::CheckResult::SATISFIABLE) + msg += "detected here."; + else + msg += "might happen here."; + m_unsafeTargets.insert(scope); + m_outerErrorReporter.warning( + 2529_error, + scope->location(), + msg + ); + } } + else + solAssert(false, ""); } } @@ -161,7 +189,7 @@ void CHC::endVisit(ContractDefinition const& _contract) auto stateExprs = vector{m_error.currentValue()} + currentStateVariables(); setCurrentBlock(*m_constructorSummaryPredicate, &stateExprs); - addVerificationTarget(m_currentContract, m_currentBlock, smt::Expression(true), m_error.currentValue()); + addAssertVerificationTarget(m_currentContract, m_currentBlock, smt::Expression(true), m_error.currentValue()); connectBlocks(m_currentBlock, interface(), m_error.currentValue() == 0); SMTEncoder::endVisit(_contract); @@ -256,7 +284,7 @@ void CHC::endVisit(FunctionDefinition const& _function) if (_function.isPublic()) { - addVerificationTarget(&_function, m_currentBlock, sum, assertionError); + addAssertVerificationTarget(&_function, m_currentBlock, sum, assertionError); connectBlocks(m_currentBlock, iface, sum && (assertionError == 0)); } } @@ -556,10 +584,34 @@ void CHC::unknownFunctionCall(FunctionCall const&) m_unknownFunctionCallSeen = true; } +void CHC::makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) +{ + FunctionType const& funType = dynamic_cast(*_arrayPop.expression().annotation().type); + solAssert(funType.kind() == FunctionType::Kind::ArrayPop, ""); + + auto memberAccess = dynamic_cast(&_arrayPop.expression()); + solAssert(memberAccess, ""); + auto symbArray = dynamic_pointer_cast(m_context.expression(memberAccess->expression())); + solAssert(symbArray, ""); + + auto previousError = m_error.currentValue(); + m_error.increaseIndex(); + + addArrayPopVerificationTarget(&_arrayPop, m_error.currentValue()); + connectBlocks( + m_currentBlock, + m_currentFunction->isConstructor() ? summary(*m_currentContract) : summary(*m_currentFunction), + currentPathConditions() && symbArray->length() <= 0 && m_error.currentValue() == _arrayPop.id() + ); + + m_context.addAssertion(m_error.currentValue() == previousError); +} + void CHC::resetSourceAnalysis() { m_verificationTargets.clear(); m_safeAssertions.clear(); + m_unsafeTargets.clear(); m_functionAssertions.clear(); m_callGraph.clear(); m_summaries.clear(); @@ -974,9 +1026,35 @@ pair> CHC::query(smt::Expression const& _query, return {result, values}; } -void CHC::addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId) +void CHC::addVerificationTarget( + ASTNode const* _scope, + VerificationTarget::Type _type, + smt::Expression _from, + smt::Expression _constraints, + smt::Expression _errorId +) { - m_verificationTargets.emplace(_scope, CHCVerificationTarget{{VerificationTarget::Type::Assert, _from, _constraints}, _errorId}); + m_verificationTargets.emplace(_scope, CHCVerificationTarget{{_type, _from, _constraints}, _errorId}); +} + +void CHC::addAssertVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId) +{ + addVerificationTarget(_scope, VerificationTarget::Type::Assert, _from, _constraints, _errorId); +} + +void CHC::addArrayPopVerificationTarget(ASTNode const* _scope, smt::Expression _errorId) +{ + solAssert(m_currentContract, ""); + solAssert(m_currentFunction, ""); + + if (m_currentFunction->isConstructor()) + addVerificationTarget(_scope, VerificationTarget::Type::PopEmptyArray, summary(*m_currentContract), smt::Expression(true), _errorId); + else + { + auto iface = (*m_interfaces.at(m_currentContract))(initialStateVariables()); + auto sum = summary(*m_currentFunction); + addVerificationTarget(_scope, VerificationTarget::Type::PopEmptyArray, iface, sum, _errorId); + } } string CHC::uniquePrefix() diff --git a/libsolidity/formal/CHC.h b/libsolidity/formal/CHC.h index 7fc752451..87f902732 100644 --- a/libsolidity/formal/CHC.h +++ b/libsolidity/formal/CHC.h @@ -78,6 +78,7 @@ private: void visitAssert(FunctionCall const& _funCall); void internalFunctionCall(FunctionCall const& _funCall); void unknownFunctionCall(FunctionCall const& _funCall); + void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override; //@} struct IdCompare @@ -182,7 +183,9 @@ private: /// @returns otherwise. std::pair> query(smt::Expression const& _query, langutil::SourceLocation const& _location); - void addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId); + void addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId); + void addAssertVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId); + void addArrayPopVerificationTarget(ASTNode const* _scope, smt::Expression _errorId); //@} /// Misc. @@ -245,6 +248,8 @@ private: /// Assertions proven safe. std::set m_safeAssertions; + /// Targets proven unsafe. + std::set m_unsafeTargets; //@} /// Control-flow. diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 067786061..86688e0b1 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -206,6 +206,15 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) CVC4::Expr s = dt[0][index].getSelector(); return m_context.mkExpr(CVC4::kind::APPLY_SELECTOR, s, arguments[0]); } + else if (n == "tuple_constructor") + { + shared_ptr tupleSort = std::dynamic_pointer_cast(_expr.sort); + solAssert(tupleSort, ""); + CVC4::DatatypeType tt = m_context.mkTupleType(cvc4Sort(tupleSort->components)); + CVC4::Datatype const& dt = tt.getDatatype(); + CVC4::Expr c = dt[0].getConstructor(); + return m_context.mkExpr(CVC4::kind::APPLY_CONSTRUCTOR, c, arguments); + } solAssert(false, ""); } diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 8760c7b6b..44bec4d85 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -646,6 +646,12 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) m_context.state().transfer(m_context.state().thisAddress(), expr(address), expr(*value)); break; } + case FunctionType::Kind::ArrayPush: + arrayPush(_funCall); + break; + case FunctionType::Kind::ArrayPop: + arrayPop(_funCall); + break; default: m_errorReporter.warning( 4588_error, @@ -890,6 +896,23 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess) return false; } } + else if (exprType->category() == Type::Category::Array) + { + _memberAccess.expression().accept(*this); + if (_memberAccess.memberName() == "length") + { + auto symbArray = dynamic_pointer_cast(m_context.expression(_memberAccess.expression())); + solAssert(symbArray, ""); + defineExpr(_memberAccess, symbArray->length()); + m_uninterpretedTerms.insert(&_memberAccess); + setSymbolicUnknownValue( + expr(_memberAccess), + _memberAccess.annotation().type, + m_context + ); + } + return false; + } else m_errorReporter.warning( 7650_error, @@ -939,9 +962,10 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess) return; } - solAssert(array, ""); + auto arrayVar = dynamic_pointer_cast(array); + solAssert(arrayVar, ""); defineExpr(_indexAccess, smt::Expression::select( - array->currentValue(), + arrayVar->elements(), expr(*_indexAccess.indexExpression()) )); setSymbolicUnknownValue( @@ -1013,16 +1037,20 @@ void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smt::Expression c return false; }); + auto symbArray = dynamic_pointer_cast(m_context.variable(*varDecl)); smt::Expression store = smt::Expression::store( - m_context.variable(*varDecl)->currentValue(), + symbArray->elements(), expr(*indexAccess->indexExpression()), toStore ); - m_context.addAssertion(m_context.newValue(*varDecl) == store); + auto oldLength = symbArray->length(); + symbArray->increaseIndex(); + m_context.addAssertion(symbArray->elements() == store); + m_context.addAssertion(symbArray->length() == oldLength); // Update the SMT select value after the assignment, // necessary for sound models. defineExpr(*indexAccess, smt::Expression::select( - m_context.variable(*varDecl)->currentValue(), + symbArray->elements(), expr(*indexAccess->indexExpression()) )); @@ -1030,7 +1058,12 @@ void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smt::Expression c } else if (auto base = dynamic_cast(&indexAccess->baseExpression())) { - toStore = smt::Expression::store(expr(*base), expr(*indexAccess->indexExpression()), toStore); + auto symbArray = dynamic_pointer_cast(m_context.expression(*base)); + solAssert(symbArray, ""); + toStore = smt::Expression::tuple_constructor( + smt::Expression(base->annotation().type), + {smt::Expression::store(symbArray->elements(), expr(*indexAccess->indexExpression()), toStore), symbArray->length()} + ); indexAccess = base; } else @@ -1045,6 +1078,76 @@ void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smt::Expression c } } +void SMTEncoder::arrayPush(FunctionCall const& _funCall) +{ + auto memberAccess = dynamic_cast(&_funCall.expression()); + solAssert(memberAccess, ""); + auto symbArray = dynamic_pointer_cast(m_context.expression(memberAccess->expression())); + solAssert(symbArray, ""); + auto oldLength = symbArray->length(); + m_context.addAssertion(oldLength >= 0); + // Real world assumption: the array length is assumed to not overflow. + // This assertion guarantees that both the current and updated lengths have the above property. + m_context.addAssertion(oldLength + 1 < (smt::maxValue(*TypeProvider::uint256()) - 1)); + + auto const& arguments = _funCall.arguments(); + smt::Expression element = arguments.empty() ? + smt::zeroValue(_funCall.annotation().type) : + expr(*arguments.front()); + smt::Expression store = smt::Expression::store( + symbArray->elements(), + oldLength, + element + ); + symbArray->increaseIndex(); + m_context.addAssertion(symbArray->elements() == store); + m_context.addAssertion(symbArray->length() == oldLength + 1); + + if (arguments.empty()) + defineExpr(_funCall, element); + + arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue()); +} + +void SMTEncoder::arrayPop(FunctionCall const& _funCall) +{ + auto memberAccess = dynamic_cast(&_funCall.expression()); + solAssert(memberAccess, ""); + auto symbArray = dynamic_pointer_cast(m_context.expression(memberAccess->expression())); + solAssert(symbArray, ""); + + makeArrayPopVerificationTarget(_funCall); + + auto oldElements = symbArray->elements(); + auto oldLength = symbArray->length(); + m_context.addAssertion(oldLength > 0); + + symbArray->increaseIndex(); + m_context.addAssertion(symbArray->elements() == oldElements); + auto newLength = smt::Expression::ite( + oldLength == 0, + smt::maxValue(*TypeProvider::uint256()), + oldLength - 1 + ); + m_context.addAssertion(symbArray->length() == oldLength - 1); + + arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue()); +} + +void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smt::Expression const& _array) +{ + if (auto const* id = dynamic_cast(&_expr)) + { + auto varDecl = identifierToVariable(*id); + solAssert(varDecl, ""); + m_context.addAssertion(m_context.newValue(*varDecl) == _array); + } + else if (auto const* indexAccess = dynamic_cast(&_expr)) + arrayIndexAssignment(*indexAccess, _array); + else + solAssert(false, ""); +} + void SMTEncoder::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex) { if (!m_context.knownGlobalSymbol(_name)) @@ -1326,7 +1429,14 @@ smt::Expression SMTEncoder::compoundAssignment(Assignment const& _assignment) void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression const& _value) { - assignment(_variable, expr(_value, _variable.type())); + // In general, at this point, the SMT sorts of _variable and _value are the same, + // even if there is implicit conversion. + // This is a special case where the SMT sorts are different. + // For now we are unaware of other cases where this happens, but if they do appear + // we should extract this into an `implicitConversion` function. + if (_variable.type()->category() != Type::Category::Array || _value.annotation().type->category() != Type::Category::StringLiteral) + assignment(_variable, expr(_value, _variable.type())); + // TODO else { store each string literal byte into the array } } void SMTEncoder::assignment(VariableDeclaration const& _variable, smt::Expression const& _value) @@ -1616,7 +1726,7 @@ SMTEncoder::VariableIndices SMTEncoder::copyVariableIndices() void SMTEncoder::resetVariableIndices(VariableIndices const& _indices) { for (auto const& var: _indices) - m_context.variable(*var.first)->index() = var.second; + m_context.variable(*var.first)->setIndex(var.second); } void SMTEncoder::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function) diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h index 21bf094c0..f62f18558 100644 --- a/libsolidity/formal/SMTEncoder.h +++ b/libsolidity/formal/SMTEncoder.h @@ -137,6 +137,13 @@ protected: /// Handles assignment to SMT array index. void arrayIndexAssignment(Expression const& _expr, smt::Expression const& _rightHandSide); + void arrayPush(FunctionCall const& _funCall); + void arrayPop(FunctionCall const& _funCall); + void arrayPushPopAssign(Expression const& _expr, smt::Expression const& _array); + /// Allows BMC and CHC to create verification targets for popping + /// an empty array. + virtual void makeArrayPopVerificationTarget(FunctionCall const&) {} + /// Division expression in the given type. Requires special treatment because /// of rounding for signed division. smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type); @@ -240,7 +247,7 @@ protected: struct VerificationTarget { - enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert } type; + enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert, PopEmptyArray } type; smt::Expression value; smt::Expression constraints; }; diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index 339a0a8ad..97ccc2c91 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -153,7 +153,15 @@ string SMTLib2Interface::toSExpr(smt::Expression const& _expr) auto tupleSort = dynamic_pointer_cast(_expr.arguments.at(0).sort); unsigned index = std::stoi(_expr.arguments.at(1).name); solAssert(index < tupleSort->members.size(), ""); - sexpr += tupleSort->members.at(index) + " " + toSExpr(_expr.arguments.at(0)); + sexpr += "|" + tupleSort->members.at(index) + "| " + toSExpr(_expr.arguments.at(0)); + } + else if (_expr.name == "tuple_constructor") + { + auto tupleSort = dynamic_pointer_cast(_expr.sort); + solAssert(tupleSort, ""); + sexpr += "|" + tupleSort->name + "|"; + for (auto const& arg: _expr.arguments) + sexpr += " " + toSExpr(arg); } else { @@ -182,18 +190,19 @@ string SMTLib2Interface::toSmtLibSort(Sort const& _sort) case Kind::Tuple: { auto const& tupleSort = dynamic_cast(_sort); - if (!m_userSorts.count(tupleSort.name)) + string tupleName = "|" + tupleSort.name + "|"; + if (!m_userSorts.count(tupleName)) { - m_userSorts.insert(tupleSort.name); - string decl("(declare-datatypes ((" + tupleSort.name + " 0)) (((" + tupleSort.name); + m_userSorts.insert(tupleName); + string decl("(declare-datatypes ((" + tupleName + " 0)) (((" + tupleName); solAssert(tupleSort.members.size() == tupleSort.components.size(), ""); for (unsigned i = 0; i < tupleSort.members.size(); ++i) - decl += " (" + tupleSort.members.at(i) + " " + toSmtLibSort(*tupleSort.components.at(i)) + ")"; + decl += " (|" + tupleSort.members.at(i) + "| " + toSmtLibSort(*tupleSort.components.at(i)) + ")"; decl += "))))"; write(decl); } - return tupleSort.name; + return tupleName; } default: solAssert(false, "Invalid SMT sort"); diff --git a/libsolidity/formal/SSAVariable.cpp b/libsolidity/formal/SSAVariable.cpp index 90e77a050..5569d8f36 100644 --- a/libsolidity/formal/SSAVariable.cpp +++ b/libsolidity/formal/SSAVariable.cpp @@ -29,5 +29,12 @@ SSAVariable::SSAVariable() void SSAVariable::resetIndex() { m_currentIndex = 0; - m_nextFreeIndex = make_unique(1); + m_nextFreeIndex = 1; +} + +void SSAVariable::setIndex(unsigned _index) +{ + m_currentIndex = _index; + if (m_nextFreeIndex <= _index) + m_nextFreeIndex = _index + 1; } diff --git a/libsolidity/formal/SSAVariable.h b/libsolidity/formal/SSAVariable.h index 9c86af40b..bad6fa80c 100644 --- a/libsolidity/formal/SSAVariable.h +++ b/libsolidity/formal/SSAVariable.h @@ -29,7 +29,10 @@ class SSAVariable { public: SSAVariable(); + /// Resets index to 0 and next index to 1. void resetIndex(); + /// Sets index to _index and only adjusts next if next <= _index. + void setIndex(unsigned _index); /// This function returns the current index of this SSA variable. unsigned index() const { return m_currentIndex; } @@ -37,12 +40,12 @@ public: unsigned operator++() { - return m_currentIndex = (*m_nextFreeIndex)++; + return m_currentIndex = m_nextFreeIndex++; } private: unsigned m_currentIndex; - std::unique_ptr m_nextFreeIndex; + unsigned m_nextFreeIndex; }; } diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index cac412c4b..4532c1b3e 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -63,7 +64,8 @@ class Expression friend class SolverInterface; public: explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {} - explicit Expression(frontend::TypePointer _type): Expression(_type->toString(), {}, std::make_shared(smtSort(*_type))) {} + explicit Expression(frontend::TypePointer _type): Expression(_type->toString(true), {}, std::make_shared(smtSort(*_type))) {} + explicit Expression(std::shared_ptr _sort): Expression("", {}, _sort) {} Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {} Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {} Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {} @@ -76,6 +78,13 @@ public: bool hasCorrectArity() const { + if (name == "tuple_constructor") + { + auto tupleSort = std::dynamic_pointer_cast(sort); + solAssert(tupleSort, ""); + return arguments.size() == tupleSort->components.size(); + } + static std::map const operatorsArity{ {"ite", 3}, {"not", 1}, @@ -138,8 +147,7 @@ public: /// The function is pure and returns the modified array. static Expression store(Expression _array, Expression _index, Expression _element) { - solAssert(_array.sort->kind == Kind::Array, ""); - std::shared_ptr arraySort = std::dynamic_pointer_cast(_array.sort); + auto arraySort = std::dynamic_pointer_cast(_array.sort); solAssert(arraySort, ""); solAssert(_index.sort, ""); solAssert(_element.sort, ""); @@ -180,6 +188,20 @@ public: ); } + static Expression tuple_constructor(Expression _tuple, std::vector _arguments) + { + solAssert(_tuple.sort->kind == Kind::Sort, ""); + auto sortSort = std::dynamic_pointer_cast(_tuple.sort); + auto tupleSort = std::dynamic_pointer_cast(sortSort->inner); + solAssert(tupleSort, ""); + solAssert(_arguments.size() == tupleSort->components.size(), ""); + return Expression( + "tuple_constructor", + std::move(_arguments), + tupleSort + ); + } + friend Expression operator!(Expression _a) { return Expression("not", std::move(_a), Kind::Bool); diff --git a/libsolidity/formal/SymbolicState.cpp b/libsolidity/formal/SymbolicState.cpp index d38e79c54..5ae8922fb 100644 --- a/libsolidity/formal/SymbolicState.cpp +++ b/libsolidity/formal/SymbolicState.cpp @@ -47,7 +47,7 @@ Expression SymbolicState::balance() Expression SymbolicState::balance(Expression _address) { - return Expression::select(m_balances.currentValue(), move(_address)); + return Expression::select(m_balances.elements(), move(_address)); } void SymbolicState::transfer(Expression _from, Expression _to, Expression _value) @@ -72,11 +72,13 @@ void SymbolicState::transfer(Expression _from, Expression _to, Expression _value void SymbolicState::addBalance(Expression _address, Expression _value) { auto newBalances = Expression::store( - m_balances.currentValue(), + m_balances.elements(), _address, balance(_address) + move(_value) ); + auto oldLength = m_balances.length(); m_balances.increaseIndex(); - m_context.addAssertion(newBalances == m_balances.currentValue()); + m_context.addAssertion(m_balances.elements() == newBalances); + m_context.addAssertion(m_balances.length() == oldLength); } diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index b8da5da0c..be6cc1b37 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -21,6 +21,7 @@ #include #include #include +#include using namespace std; @@ -55,17 +56,18 @@ SortPointer smtSort(frontend::Type const& _type) } case Kind::Array: { + shared_ptr array; if (isMapping(_type.category())) { auto mapType = dynamic_cast(&_type); solAssert(mapType, ""); - return make_shared(smtSortAbstractFunction(*mapType->keyType()), smtSortAbstractFunction(*mapType->valueType())); + array = make_shared(smtSortAbstractFunction(*mapType->keyType()), smtSortAbstractFunction(*mapType->valueType())); } else if (isStringLiteral(_type.category())) { auto stringLitType = dynamic_cast(&_type); solAssert(stringLitType, ""); - return make_shared(SortProvider::intSort, SortProvider::intSort); + array = make_shared(SortProvider::intSort, SortProvider::intSort); } else { @@ -78,8 +80,25 @@ SortPointer smtSort(frontend::Type const& _type) solAssert(false, ""); solAssert(arrayType, ""); - return make_shared(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType())); + array = make_shared(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType())); } + + string tupleName; + if ( + auto arrayType = dynamic_cast(&_type); + (arrayType && arrayType->isString()) || + _type.category() == frontend::Type::Category::ArraySlice || + _type.category() == frontend::Type::Category::StringLiteral + ) + tupleName = "bytes_tuple"; + else + tupleName = _type.toString(true) + "_tuple"; + + return make_shared( + tupleName, + vector{tupleName + "_accessor_array", tupleName + "_accessor_length"}, + vector{array, SortProvider::intSort} + ); } case Kind::Tuple: { @@ -219,9 +238,7 @@ pair> newSymbolicVariable( else var = make_shared(type, type, _uniqueName, _context); } - else if (isMapping(_type.category())) - var = make_shared(type, _uniqueName, _context); - else if (isArray(_type.category())) + else if (isMapping(_type.category()) || isArray(_type.category())) var = make_shared(type, type, _uniqueName, _context); else if (isTuple(_type.category())) var = make_shared(type, _uniqueName, _context); @@ -349,11 +366,26 @@ Expression zeroValue(frontend::TypePointer const& _type) return Expression(false); if (isArray(_type->category()) || isMapping(_type->category())) { + auto tupleSort = dynamic_pointer_cast(smtSort(*_type)); + solAssert(tupleSort, ""); + auto sortSort = make_shared(tupleSort->components.front()); + + std::optional zeroArray; + auto length = bigint(0); if (auto arrayType = dynamic_cast(_type)) - return Expression::const_array(Expression(arrayType), zeroValue(arrayType->baseType())); - auto mappingType = dynamic_cast(_type); - solAssert(mappingType, ""); - return Expression::const_array(Expression(mappingType), zeroValue(mappingType->valueType())); + { + zeroArray = Expression::const_array(Expression(sortSort), zeroValue(arrayType->baseType())); + if (!arrayType->isDynamicallySized()) + length = bigint(arrayType->length()); + } + else if (auto mappingType = dynamic_cast(_type)) + zeroArray = Expression::const_array(Expression(sortSort), zeroValue(mappingType->valueType())); + else + solAssert(false, ""); + + solAssert(zeroArray, ""); + return Expression::tuple_constructor(Expression(_type), vector{*zeroArray, length}); + } solAssert(false, ""); } diff --git a/libsolidity/formal/SymbolicVariables.cpp b/libsolidity/formal/SymbolicVariables.cpp index 35cd4f9c2..519588abd 100644 --- a/libsolidity/formal/SymbolicVariables.cpp +++ b/libsolidity/formal/SymbolicVariables.cpp @@ -86,6 +86,12 @@ smt::Expression SymbolicVariable::resetIndex() return currentValue(); } +smt::Expression SymbolicVariable::setIndex(unsigned _index) +{ + m_ssa->setIndex(_index); + return currentValue(); +} + smt::Expression SymbolicVariable::increaseIndex() { ++(*m_ssa); @@ -179,6 +185,12 @@ smt::Expression SymbolicFunctionVariable::resetIndex() return m_abstract.resetIndex(); } +smt::Expression SymbolicFunctionVariable::setIndex(unsigned _index) +{ + SymbolicVariable::setIndex(_index); + return m_abstract.setIndex(_index); +} + smt::Expression SymbolicFunctionVariable::increaseIndex() { ++(*m_ssa); @@ -197,46 +209,6 @@ void SymbolicFunctionVariable::resetDeclaration() m_declaration = m_context.newVariable(currentName(), m_sort); } -SymbolicMappingVariable::SymbolicMappingVariable( - frontend::TypePointer _type, - string _uniqueName, - EncodingContext& _context -): - SymbolicVariable(_type, _type, move(_uniqueName), _context) -{ - solAssert(isMapping(m_type->category()), ""); -} - -SymbolicArrayVariable::SymbolicArrayVariable( - frontend::TypePointer _type, - frontend::TypePointer _originalType, - string _uniqueName, - EncodingContext& _context -): - SymbolicVariable(_type, _originalType, move(_uniqueName), _context) -{ - solAssert(isArray(m_type->category()), ""); -} - -SymbolicArrayVariable::SymbolicArrayVariable( - SortPointer _sort, - string _uniqueName, - EncodingContext& _context -): - SymbolicVariable(move(_sort), move(_uniqueName), _context) -{ - solAssert(m_sort->kind == Kind::Array, ""); -} - -smt::Expression SymbolicArrayVariable::currentValue(frontend::TypePointer const& _targetType) const -{ - optional conversion = symbolicTypeConversion(m_originalType, _targetType); - if (conversion) - return *conversion; - - return SymbolicVariable::currentValue(_targetType); -} - SymbolicEnumVariable::SymbolicEnumVariable( frontend::TypePointer _type, string _uniqueName, @@ -286,3 +258,62 @@ smt::Expression SymbolicTupleVariable::component( return smt::Expression::tuple_get(currentValue(), _index); } + +SymbolicArrayVariable::SymbolicArrayVariable( + frontend::TypePointer _type, + frontend::TypePointer _originalType, + string _uniqueName, + EncodingContext& _context +): + SymbolicVariable(_type, _originalType, move(_uniqueName), _context), + m_pair( + smtSort(*_type), + m_uniqueName + "_length_pair", + m_context + ) +{ + solAssert(isArray(m_type->category()) || isMapping(m_type->category()), ""); +} + +SymbolicArrayVariable::SymbolicArrayVariable( + SortPointer _sort, + string _uniqueName, + EncodingContext& _context +): + SymbolicVariable(move(_sort), move(_uniqueName), _context), + m_pair( + std::make_shared( + "array_length_pair", + std::vector{"array", "length"}, + std::vector{m_sort, SortProvider::intSort} + ), + m_uniqueName + "_array_length_pair", + m_context + ) +{ + solAssert(m_sort->kind == Kind::Array, ""); +} + +smt::Expression SymbolicArrayVariable::currentValue(frontend::TypePointer const& _targetType) const +{ + optional conversion = symbolicTypeConversion(m_originalType, _targetType); + if (conversion) + return *conversion; + + return m_pair.currentValue(); +} + +smt::Expression SymbolicArrayVariable::valueAtIndex(int _index) const +{ + return m_pair.valueAtIndex(_index); +} + +smt::Expression SymbolicArrayVariable::elements() +{ + return m_pair.component(0); +} + +smt::Expression SymbolicArrayVariable::length() +{ + return m_pair.component(1); +} diff --git a/libsolidity/formal/SymbolicVariables.h b/libsolidity/formal/SymbolicVariables.h index 6f7ad6ec4..3f5379d9b 100644 --- a/libsolidity/formal/SymbolicVariables.h +++ b/libsolidity/formal/SymbolicVariables.h @@ -56,6 +56,7 @@ public: virtual Expression valueAtIndex(int _index) const; virtual std::string nameAtIndex(int _index) const; virtual Expression resetIndex(); + virtual Expression setIndex(unsigned _index); virtual Expression increaseIndex(); virtual Expression operator()(std::vector /*_arguments*/) const { @@ -169,6 +170,7 @@ public: Expression functionValueAtIndex(int _index) const; Expression resetIndex() override; + Expression setIndex(unsigned _index) override; Expression increaseIndex() override; Expression operator()(std::vector _arguments) const override; @@ -189,42 +191,6 @@ private: }; }; -/** - * Specialization of SymbolicVariable for Mapping - */ -class SymbolicMappingVariable: public SymbolicVariable -{ -public: - SymbolicMappingVariable( - frontend::TypePointer _type, - std::string _uniqueName, - EncodingContext& _context - ); -}; - -/** - * Specialization of SymbolicVariable for Array - */ -class SymbolicArrayVariable: public SymbolicVariable -{ -public: - SymbolicArrayVariable( - frontend::TypePointer _type, - frontend::TypePointer _originalTtype, - std::string _uniqueName, - EncodingContext& _context - ); - SymbolicArrayVariable( - SortPointer _sort, - std::string _uniqueName, - EncodingContext& _context - ); - - SymbolicArrayVariable(SymbolicArrayVariable&&) = default; - - Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const override; -}; - /** * Specialization of SymbolicVariable for Enum */ @@ -263,4 +229,38 @@ public: ); }; +/** + * Specialization of SymbolicVariable for Array + */ +class SymbolicArrayVariable: public SymbolicVariable +{ +public: + SymbolicArrayVariable( + frontend::TypePointer _type, + frontend::TypePointer _originalTtype, + std::string _uniqueName, + EncodingContext& _context + ); + SymbolicArrayVariable( + SortPointer _sort, + std::string _uniqueName, + EncodingContext& _context + ); + + SymbolicArrayVariable(SymbolicArrayVariable&&) = default; + + Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const override; + Expression valueAtIndex(int _index) const override; + Expression resetIndex() override { SymbolicVariable::resetIndex(); return m_pair.resetIndex(); } + Expression setIndex(unsigned _index) override { SymbolicVariable::setIndex(_index); return m_pair.setIndex(_index); } + Expression increaseIndex() override { SymbolicVariable::increaseIndex(); return m_pair.increaseIndex(); } + Expression elements(); + Expression length(); + + SortPointer tupleSort() { return m_pair.sort(); } + +private: + SymbolicTupleVariable m_pair; +}; + } diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index a84f39bb1..d49314b6c 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -198,6 +198,15 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) size_t index = std::stoi(_expr.arguments[1].name); return z3::func_decl(m_context, Z3_get_tuple_sort_field_decl(m_context, z3Sort(*_expr.arguments[0].sort), index))(arguments[0]); } + else if (n == "tuple_constructor") + { + auto constructor = z3::func_decl(m_context, Z3_get_tuple_sort_mk_decl(m_context, z3Sort(*_expr.sort))); + solAssert(constructor.arity() == arguments.size(), ""); + z3::expr_vector args(m_context); + for (auto const& arg: arguments) + args.push_back(arg); + return constructor(args); + } solAssert(false, ""); } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 5b79aa0c6..558c262d0 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -733,11 +733,7 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const solAssert(_contract.contract, ""); - // caches the result - if (!_contract.abi) - _contract.abi = make_unique(ABI::generate(*_contract.contract)); - - return *_contract.abi; + return _contract.abi.init([&]{ return ABI::generate(*_contract.contract); }); } Json::Value const& CompilerStack::storageLayout(string const& _contractName) const @@ -755,11 +751,7 @@ Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const solAssert(_contract.contract, ""); - // caches the result - if (!_contract.storageLayout) - _contract.storageLayout = make_unique(StorageLayout().generate(*_contract.contract)); - - return *_contract.storageLayout; + return _contract.storageLayout.init([&]{ return StorageLayout().generate(*_contract.contract); }); } Json::Value const& CompilerStack::natspecUser(string const& _contractName) const @@ -777,11 +769,7 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const solAssert(_contract.contract, ""); - // caches the result - if (!_contract.userDocumentation) - _contract.userDocumentation = make_unique(Natspec::userDocumentation(*_contract.contract)); - - return *_contract.userDocumentation; + return _contract.userDocumentation.init([&]{ return Natspec::userDocumentation(*_contract.contract); }); } Json::Value const& CompilerStack::natspecDev(string const& _contractName) const @@ -799,11 +787,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const solAssert(_contract.contract, ""); - // caches the result - if (!_contract.devDocumentation) - _contract.devDocumentation = make_unique(Natspec::devDocumentation(*_contract.contract)); - - return *_contract.devDocumentation; + return _contract.devDocumentation.init([&]{ return Natspec::devDocumentation(*_contract.contract); }); } Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const @@ -832,11 +816,7 @@ string const& CompilerStack::metadata(Contract const& _contract) const solAssert(_contract.contract, ""); - // cache the result - if (!_contract.metadata) - _contract.metadata = make_unique(createMetadata(_contract)); - - return *_contract.metadata; + return _contract.metadata.init([&]{ return createMetadata(_contract); }); } Scanner const& CompilerStack::scanner(string const& _sourceName) const diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index d66f003b7..96dbc4f04 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -342,11 +343,11 @@ private: std::string yulIROptimized; ///< Optimized experimental Yul IR code. std::string ewasm; ///< Experimental Ewasm text representation evmasm::LinkerObject ewasmObject; ///< Experimental Ewasm code - mutable std::unique_ptr metadata; ///< The metadata json that will be hashed into the chain. - mutable std::unique_ptr abi; - mutable std::unique_ptr storageLayout; - mutable std::unique_ptr userDocumentation; - mutable std::unique_ptr devDocumentation; + util::LazyInit metadata; ///< The metadata json that will be hashed into the chain. + util::LazyInit abi; + util::LazyInit storageLayout; + util::LazyInit userDocumentation; + util::LazyInit devDocumentation; mutable std::unique_ptr sourceMapping; mutable std::unique_ptr runtimeSourceMapping; }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 0abc867e8..6cf13c309 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -1127,16 +1128,29 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) stack.optimize(); - MachineAssemblyObject object = stack.assemble(AssemblyStack::Machine::EVM); + MachineAssemblyObject object; + MachineAssemblyObject runtimeObject; + tie(object, runtimeObject) = stack.assembleAndGuessRuntime(); - if (isArtifactRequested( - _inputsAndSettings.outputSelection, - sourceName, - contractName, - { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }, - wildcardMatchesExperimental - )) - output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get(), false); + for (string const& objectKind: vector{"bytecode", "deployedBytecode"}) + { + auto artifacts = util::applyMap( + vector{"", ".object", ".opcodes", ".sourceMap", ".linkReferences"}, + [&](auto const& _s) { return "evm." + objectKind + _s; } + ); + if (isArtifactRequested( + _inputsAndSettings.outputSelection, + sourceName, + contractName, + artifacts, + wildcardMatchesExperimental + )) + { + MachineAssemblyObject const& o = objectKind == "bytecode" ? object : runtimeObject; + if (o.bytecode) + output["contracts"][sourceName][contractName]["evm"][objectKind] = collectEVMObject(*o.bytecode, o.sourceMappings.get(), false); + } + } if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental)) output["contracts"][sourceName][contractName]["irOptimized"] = stack.print(); diff --git a/libsolutil/CMakeLists.txt b/libsolutil/CMakeLists.txt index 2c6b6b26f..3672657e8 100644 --- a/libsolutil/CMakeLists.txt +++ b/libsolutil/CMakeLists.txt @@ -19,6 +19,7 @@ set(sources JSON.h Keccak256.cpp Keccak256.h + LazyInit.h picosha2.h Result.h StringUtils.cpp diff --git a/libsolutil/LazyInit.h b/libsolutil/LazyInit.h new file mode 100644 index 000000000..fe2f6a916 --- /dev/null +++ b/libsolutil/LazyInit.h @@ -0,0 +1,94 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace solidity::util +{ + +DEV_SIMPLE_EXCEPTION(BadLazyInitAccess); + +/** + * A value that is initialized at some point after construction of the LazyInit. The stored value can only be accessed + * while calling "init", which initializes the stored value (if it has not already been initialized). + * + * @tparam T the type of the stored value; may not be a function, reference, array, or void type; may be const-qualified. + */ +template +class LazyInit +{ +public: + using value_type = T; + + static_assert(std::is_object_v, "Function, reference, and void types are not supported"); + static_assert(!std::is_array_v, "Array types are not supported."); + static_assert(!std::is_volatile_v, "Volatile-qualified types are not supported."); + + LazyInit() = default; + + LazyInit(LazyInit const&) = delete; + LazyInit& operator=(LazyInit const&) = delete; + + // Move constructor must be overridden to ensure that moved-from object is left empty. + constexpr LazyInit(LazyInit&& _other) noexcept: + m_value(std::move(_other.m_value)) + { + _other.m_value.reset(); + } + + LazyInit& operator=(LazyInit&& _other) noexcept + { + this->m_value.swap(_other.m_value); + _other.m_value.reset(); + } + + template + value_type& init(F&& _fun) + { + doInit(std::forward(_fun)); + return m_value.value(); + } + + template + value_type const& init(F&& _fun) const + { + doInit(std::forward(_fun)); + return m_value.value(); + } + +private: + /// Although not quite logically const, this is marked const for pragmatic reasons. It doesn't change the platonic + /// value of the object (which is something that is initialized to some computed value on first use). + template + void doInit(F&& _fun) const + { + if (!m_value.has_value()) + m_value.emplace(std::forward(_fun)()); + } + + mutable std::optional m_value; +}; + +} diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 2edd4dc38..80c90650d 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -45,22 +45,20 @@ using namespace solidity::langutil; bool AsmAnalyzer::analyze(Block const& _block) { - m_success = true; + auto watcher = m_errorReporter.errorWatcher(); try { if (!(ScopeFiller(m_info, m_errorReporter))(_block)) return false; (*this)(_block); - if (!m_success) - yulAssert(m_errorReporter.hasErrors(), "No success but no error."); } catch (FatalError const&) { // This FatalError con occur if the errorReporter has too many errors. - yulAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); + yulAssert(!watcher.ok(), "Fatal error detected, but no error is reported."); } - return m_success && !m_errorReporter.hasErrors(); + return watcher.ok(); } AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object) @@ -105,7 +103,7 @@ vector AsmAnalyzer::operator()(Literal const& _literal) vector AsmAnalyzer::operator()(Identifier const& _identifier) { yulAssert(!_identifier.name.empty(), ""); - size_t numErrorsBefore = m_errorReporter.errors().size(); + auto watcher = m_errorReporter.errorWatcher(); YulString type = m_dialect.defaultType; if (m_currentScope->lookup(_identifier.name, GenericVisitor{ @@ -141,13 +139,9 @@ vector AsmAnalyzer::operator()(Identifier const& _identifier) yulAssert(stackSize == 1, "Invalid stack size of external reference."); } } - if (!found) - { + if (!found && watcher.ok()) // Only add an error message if the callback did not do it. - if (numErrorsBefore == m_errorReporter.errors().size()) - declarationError(_identifier.location, "Identifier not found."); - m_success = false; - } + declarationError(_identifier.location, "Identifier not found."); } return {type}; @@ -155,8 +149,9 @@ vector AsmAnalyzer::operator()(Identifier const& _identifier) void AsmAnalyzer::operator()(ExpressionStatement const& _statement) { + auto watcher = m_errorReporter.errorWatcher(); vector types = std::visit(*this, _statement.expression); - if (m_success && !types.empty()) + if (watcher.ok() && !types.empty()) typeError(_statement.location, "Top-level expressions are not supposed to return values (this expression returns " + to_string(types.size()) + @@ -253,6 +248,7 @@ void AsmAnalyzer::operator()(FunctionDefinition const& _funDef) vector AsmAnalyzer::operator()(FunctionCall const& _funCall) { yulAssert(!_funCall.functionName.name.empty(), ""); + auto watcher = m_errorReporter.errorWatcher(); vector const* parameterTypes = nullptr; vector const* returnTypes = nullptr; vector const* needsLiteralArguments = nullptr; @@ -281,7 +277,7 @@ vector AsmAnalyzer::operator()(FunctionCall const& _funCall) { if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location)) declarationError(_funCall.functionName.location, "Function not found."); - m_success = false; + yulAssert(!watcher.ok(), "Expected a reported error."); } if (parameterTypes && _funCall.arguments.size() != parameterTypes->size()) typeError( @@ -323,7 +319,7 @@ vector AsmAnalyzer::operator()(FunctionCall const& _funCall) for (size_t i = 0; i < parameterTypes->size(); ++i) expectType((*parameterTypes)[i], argTypes[i], locationOf(_funCall.arguments[i])); - if (m_success) + if (watcher.ok()) { yulAssert(parameterTypes && parameterTypes->size() == argTypes.size(), ""); yulAssert(returnTypes, ""); @@ -353,6 +349,8 @@ void AsmAnalyzer::operator()(Switch const& _switch) { if (_case.value) { + auto watcher = m_errorReporter.errorWatcher(); + expectType(valueType, _case.value->type, _case.value->location); // We cannot use "expectExpression" here because *_case.value is not an @@ -360,7 +358,7 @@ void AsmAnalyzer::operator()(Switch const& _switch) (*this)(*_case.value); /// Note: the parser ensures there is only one default case - if (m_success && !cases.insert(valueOfLiteral(*_case.value)).second) + if (watcher.ok() && !cases.insert(valueOfLiteral(*_case.value)).second) declarationError(_case.location, "Duplicate case defined."); } @@ -432,7 +430,7 @@ void AsmAnalyzer::expectBoolExpression(Expression const& _expr) void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueType) { yulAssert(!_variable.name.empty(), ""); - size_t numErrorsBefore = m_errorReporter.errors().size(); + auto watcher = m_errorReporter.errorWatcher(); YulString const* variableType = nullptr; bool found = false; if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) @@ -461,13 +459,9 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueT } } - if (!found) - { - m_success = false; + if (!found && watcher.ok()) // Only add message if the callback did not. - if (numErrorsBefore == m_errorReporter.errors().size()) - declarationError(_variable.location, "Variable not found or variable not lvalue."); - } + declarationError(_variable.location, "Variable not found or variable not lvalue."); if (variableType && *variableType != _valueType) typeError(_variable.location, "Assigning a value of type \"" + @@ -477,8 +471,7 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueT "\"." ); - if (m_success) - yulAssert(variableType, ""); + yulAssert(!watcher.ok() || variableType, ""); } Scope& AsmAnalyzer::scope(Block const* _block) @@ -545,43 +538,28 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation _instr == evmasm::Instruction::RETURNDATACOPY || _instr == evmasm::Instruction::RETURNDATASIZE ) && !m_evmVersion.supportsReturndata()) - { errorForVM("only available for Byzantium-compatible"); - } else if (_instr == evmasm::Instruction::STATICCALL && !m_evmVersion.hasStaticCall()) - { errorForVM("only available for Byzantium-compatible"); - } else if (( _instr == evmasm::Instruction::SHL || _instr == evmasm::Instruction::SHR || _instr == evmasm::Instruction::SAR ) && !m_evmVersion.hasBitwiseShifting()) - { errorForVM("only available for Constantinople-compatible"); - } else if (_instr == evmasm::Instruction::CREATE2 && !m_evmVersion.hasCreate2()) - { errorForVM("only available for Constantinople-compatible"); - } else if (_instr == evmasm::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) - { errorForVM("only available for Constantinople-compatible"); - } else if (_instr == evmasm::Instruction::CHAINID && !m_evmVersion.hasChainID()) - { errorForVM("only available for Istanbul-compatible"); - } else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance()) - { errorForVM("only available for Istanbul-compatible"); - } else if ( _instr == evmasm::Instruction::JUMP || _instr == evmasm::Instruction::JUMPI || _instr == evmasm::Instruction::JUMPDEST ) - { m_errorReporter.error( 4316_error, Error::Type::SyntaxError, @@ -590,8 +568,6 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation "incorrect stack access. Because of that they are disallowed in strict assembly. " "Use functions, \"switch\", \"if\" or \"for\" statements instead." ); - m_success = false; - } else return false; @@ -601,12 +577,9 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation void AsmAnalyzer::typeError(SourceLocation const& _location, string const& _description) { m_errorReporter.typeError(7569_error, _location, _description); - m_success = false; } void AsmAnalyzer::declarationError(SourceLocation const& _location, string const& _description) { m_errorReporter.declarationError(9595_error, _location, _description); - m_success = false; } - diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 09ca73ee2..bbc37bedb 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -115,8 +115,6 @@ private: void typeError(langutil::SourceLocation const& _location, std::string const& _description); void declarationError(langutil::SourceLocation const& _location, std::string const& _description); - /// Success-flag, can be set to false at any time. - bool m_success = true; yul::ExternalIdentifierAccess::Resolver m_resolver; Scope* m_currentScope = nullptr; /// Variables that are active at the current point in assembly (as opposed to diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index 3988bc3fd..e0ff93939 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -198,22 +198,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const switch (_machine) { case Machine::EVM: - { - MachineAssemblyObject object; - evmasm::Assembly assembly; - EthAssemblyAdapter adapter(assembly); - compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation); - object.bytecode = make_shared(assembly.assemble()); - yulAssert(object.bytecode->immutableReferences.empty(), "Leftover immutables."); - object.assembly = assembly.assemblyString(); - object.sourceMappings = make_unique( - evmasm::AssemblyItem::computeSourceMapping( - assembly.items(), - {{scanner().charStream() ? scanner().charStream()->name() : "", 0}} - ) - ); - return object; - } + return assembleAndGuessRuntime().first; case Machine::EVM15: { MachineAssemblyObject object; @@ -240,6 +225,46 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const return MachineAssemblyObject(); } +pair AssemblyStack::assembleAndGuessRuntime() const +{ + yulAssert(m_analysisSuccessful, ""); + yulAssert(m_parserResult, ""); + yulAssert(m_parserResult->code, ""); + yulAssert(m_parserResult->analysisInfo, ""); + + evmasm::Assembly assembly; + EthAssemblyAdapter adapter(assembly); + compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation); + + MachineAssemblyObject creationObject; + creationObject.bytecode = make_shared(assembly.assemble()); + yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables."); + creationObject.assembly = assembly.assemblyString(); + creationObject.sourceMappings = make_unique( + evmasm::AssemblyItem::computeSourceMapping( + assembly.items(), + {{scanner().charStream() ? scanner().charStream()->name() : "", 0}} + ) + ); + + MachineAssemblyObject runtimeObject; + // Heuristic: If there is a single sub-assembly, this is likely the runtime object. + if (assembly.numSubs() == 1) + { + evmasm::Assembly& runtimeAssembly = assembly.sub(0); + runtimeObject.bytecode = make_shared(runtimeAssembly.assemble()); + runtimeObject.assembly = runtimeAssembly.assemblyString(); + runtimeObject.sourceMappings = make_unique( + evmasm::AssemblyItem::computeSourceMapping( + runtimeAssembly.items(), + {{scanner().charStream() ? scanner().charStream()->name() : "", 0}} + ) + ); + } + return {std::move(creationObject), std::move(runtimeObject)}; + +} + string AssemblyStack::print() const { yulAssert(m_parserResult, ""); diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index 60efd9674..f9d235505 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -88,6 +88,12 @@ public: /// Run the assembly step (should only be called after parseAndAnalyze). MachineAssemblyObject assemble(Machine _machine) const; + /// Run the assembly step (should only be called after parseAndAnalyze). + /// In addition to the value returned by @a assemble, returns + /// a second object that is guessed to be the runtime code. + /// Only available for EVM. + std::pair assembleAndGuessRuntime() const; + /// @returns the errors generated during parsing, analysis (and potentially assembly). langutil::ErrorList const& errors() const { return m_errors; } diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index cf6cbdd79..3d6e17210 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -34,7 +34,5 @@ else BUILD_DIR="$1" fi -docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.39.3-64bit \ - ./scripts/travis-emscripten/install_deps.sh -docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.39.3-64bit \ +docker run -v $(pwd):/root/project -w /root/project ethereum/solidity-buildpack-deps:emsdk-1.39.15-1 \ ./scripts/travis-emscripten/build_emscripten.sh $BUILD_DIR diff --git a/scripts/correct_error_ids.py b/scripts/correct_error_ids.py index 9fd3547f0..c5885fac6 100644 --- a/scripts/correct_error_ids.py +++ b/scripts/correct_error_ids.py @@ -149,7 +149,7 @@ def main(): print("No incorrect IDs found") else: fix_ids(used_ids, source_file_names) - print("Fixing compteted") + print("Fixing completed") if __name__ == "__main__": diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 7d3253515..c338a1977 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -40,37 +40,8 @@ else BUILD_DIR="$1" fi -if ! type git &>/dev/null; then - # We need git for extracting the commit hash - apt-get update - apt-get -y install git-core -fi - -if ! type wget &>/dev/null; then - # We need wget to install cmake - apt-get update - apt-get -y install wget -fi - WORKSPACE=/root/project -# Boost -echo -en 'travis_fold:start:compiling_boost\\r' -test -e "$WORKSPACE"/boost_1_70_0_install/include/boost/version.hpp || ( -cd "$WORKSPACE"/boost_1_70_0 -./b2 toolset=emscripten link=static variant=release threading=single runtime-link=static \ - --with-system --with-filesystem --with-test --with-program_options cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \ - --prefix="$WORKSPACE"/boost_1_70_0_install install -) -ln -sf "$WORKSPACE"/boost_1_70_0_install/lib/* /emsdk_portable/emscripten/sdk/system/lib -ln -sf "$WORKSPACE"/boost_1_70_0_install/include/* /emsdk_portable/emscripten/sdk/system/include -echo -en 'travis_fold:end:compiling_boost\\r' - -echo -en 'travis_fold:start:install_cmake.sh\\r' -source $WORKSPACE/scripts/install_cmake.sh -echo -en 'travis_fold:end:install_cmake.sh\\r' - -# Build dependent components and solidity itself echo -en 'travis_fold:start:compiling_solidity\\r' cd $WORKSPACE mkdir -p $BUILD_DIR @@ -82,7 +53,7 @@ cmake \ -DBoost_USE_STATIC_RUNTIME=1 \ -DTESTS=0 \ .. -make -j 4 +make -j 4 soljson cd .. mkdir -p upload diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh deleted file mode 100755 index 7544da4e2..000000000 --- a/scripts/travis-emscripten/install_deps.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -#------------------------------------------------------------------------------ -# Bash script for installing pre-requisite packages for building solidity -# using Emscripten on Ubuntu Trusty. -# -# The documentation for solidity is hosted at: -# -# http://solidity.readthedocs.io/ -# -# ------------------------------------------------------------------------------ -# 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) 2016 solidity contributors. -#------------------------------------------------------------------------------ - -set -ev - -SCRIPT_DIR="$(realpath $(dirname $0))" - -echo -en 'travis_fold:start:installing_dependencies\\r' -test -e boost_1_70_0_install/include/boost/version.hpp || ( -rm -rf boost_1_70_0 -rm -f boost.tar.gz -wget -q 'https://sourceforge.net/projects/boost/files/boost/1.70.0/boost_1_70_0.tar.gz/download'\ - -O boost.tar.gz -test "$(shasum boost.tar.gz)" = "7804c782deb00f36ac80b1000b71a3707eadb620 boost.tar.gz" -tar -xzf boost.tar.gz -rm boost.tar.gz -cd boost_1_70_0 -./bootstrap.sh -cp "${SCRIPT_DIR}/emscripten.jam" . -echo "using emscripten : : em++ ;" >> project-config.jam -) -cd .. -echo -en 'travis_fold:end:installing_dependencies\\r' diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ff545faf3..1834d3b30 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,6 +34,7 @@ set(libsolutil_sources libsolutil/IterateReplacing.cpp libsolutil/JSON.cpp libsolutil/Keccak256.cpp + libsolutil/LazyInit.cpp libsolutil/StringUtils.cpp libsolutil/SwarmHash.cpp libsolutil/UTF8.cpp diff --git a/test/cmdlineTests/optimizer_BlockDeDuplicator/args b/test/cmdlineTests/optimizer_BlockDeDuplicator/args new file mode 100644 index 000000000..8942fcc35 --- /dev/null +++ b/test/cmdlineTests/optimizer_BlockDeDuplicator/args @@ -0,0 +1 @@ +--optimize --asm --metadata-hash none diff --git a/test/cmdlineTests/optimizer_BlockDeDuplicator/err b/test/cmdlineTests/optimizer_BlockDeDuplicator/err new file mode 100644 index 000000000..e29fc4fc3 --- /dev/null +++ b/test/cmdlineTests/optimizer_BlockDeDuplicator/err @@ -0,0 +1,11 @@ +Warning: Statement has no effect. + --> optimizer_BlockDeDuplicator/input.sol:7:27: + | +7 | function f() public { true ? 1 : 3;} + | ^^^^^^^^^^^^ + +Warning: Function state mutability can be restricted to pure + --> optimizer_BlockDeDuplicator/input.sol:7:5: + | +7 | function f() public { true ? 1 : 3;} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/test/cmdlineTests/optimizer_BlockDeDuplicator/input.sol b/test/cmdlineTests/optimizer_BlockDeDuplicator/input.sol new file mode 100644 index 000000000..3f75fa240 --- /dev/null +++ b/test/cmdlineTests/optimizer_BlockDeDuplicator/input.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function fun_x() public {} + function fun_() public {} + function f() public { true ? 1 : 3;} + function() r = true ? fun_x : f; +} diff --git a/test/cmdlineTests/optimizer_BlockDeDuplicator/output b/test/cmdlineTests/optimizer_BlockDeDuplicator/output new file mode 100644 index 000000000..6f11f97a4 --- /dev/null +++ b/test/cmdlineTests/optimizer_BlockDeDuplicator/output @@ -0,0 +1,107 @@ + +======= optimizer_BlockDeDuplicator/input.sol:C ======= +EVM assembly: + /* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */ + mstore(0x40, 0x80) + /* "optimizer_BlockDeDuplicator/input.sol":179:210 function() r = true ? fun_x : f */ + 0x00 + dup1 + sload + not(sub(shl(0x40, 0x01), 0x01)) + and + /* "optimizer_BlockDeDuplicator/input.sol":201:206 fun_x */ + or(tag_0_7, shl(0x20, tag_4)) + sub(shl(0x40, 0x01), 0x01) + /* "optimizer_BlockDeDuplicator/input.sol":179:210 function() r = true ? fun_x : f */ + and + or + swap1 + sstore + /* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */ + callvalue + /* "--CODEGEN--":2:4 */ + dup1 + iszero + tag_5 + jumpi + /* "--CODEGEN--":27:28 */ + 0x00 + /* "--CODEGEN--":24:25 */ + dup1 + /* "--CODEGEN--":17:29 */ + revert + /* "--CODEGEN--":2:4 */ +tag_5: + /* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */ + pop + jump(tag_6) + /* "optimizer_BlockDeDuplicator/input.sol":77:103 function fun_x() public {} */ +tag_4: + jump // out + /* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */ +tag_6: + dataSize(sub_0) + dup1 + dataOffset(sub_0) + 0x00 + codecopy + 0x00 + return +stop + +sub_0: assembly { + /* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */ + mstore(0x40, 0x80) + callvalue + /* "--CODEGEN--":5:14 */ + dup1 + /* "--CODEGEN--":2:4 */ + iszero + tag_1 + jumpi + /* "--CODEGEN--":27:28 */ + 0x00 + /* "--CODEGEN--":24:25 */ + dup1 + /* "--CODEGEN--":17:29 */ + revert + /* "--CODEGEN--":2:4 */ + tag_1: + /* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */ + pop + jumpi(tag_2, lt(calldatasize, 0x04)) + shr(0xe0, calldataload(0x00)) + dup1 + 0x26121ff0 + eq + tag_3 + jumpi + dup1 + 0x2e1fb2bc + eq + tag_3 + jumpi + dup1 + 0x4753a67d + eq + tag_3 + jumpi + tag_2: + /* "--CODEGEN--":12:13 */ + 0x00 + /* "--CODEGEN--":9:10 */ + dup1 + /* "--CODEGEN--":2:14 */ + revert + /* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */ + tag_3: + tag_6 + tag_7 + jump // in + tag_6: + stop + tag_7: + jump // out + + auxdata: AUXDATA REMOVED +} diff --git a/test/cmdlineTests/standard_yul_object_name/output.json b/test/cmdlineTests/standard_yul_object_name/output.json index 2fdd9bc11..58d78b3a9 100644 --- a/test/cmdlineTests/standard_yul_object_name/output.json +++ b/test/cmdlineTests/standard_yul_object_name/output.json @@ -22,7 +22,7 @@ sub_0: assembly { /* \"A\":137:149 */ revert } -","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" { +","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"},"deployedBytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" { code { let x := dataoffset(\"DataName\") sstore(add(x, 0), 0) diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index 5eb780ee3..29c734f20 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -24,9 +24,12 @@ #include #include +#include + #include #include +#include using namespace std; using namespace solidity; @@ -211,17 +214,18 @@ contract GlobalRegistrar is Registrar, AuctionSystem { } )DELIMITER"; -static unique_ptr s_compiledRegistrar; +static LazyInit s_compiledRegistrar; class AuctionRegistrarTestFramework: public SolidityExecutionFramework { protected: void deployRegistrar() { - if (!s_compiledRegistrar) - s_compiledRegistrar = make_unique(compileContract(registrarCode, "GlobalRegistrar")); + bytes const& compiled = s_compiledRegistrar.init([&]{ + return compileContract(registrarCode, "GlobalRegistrar"); + }); - sendMessage(*s_compiledRegistrar, true); + sendMessage(compiled, true); BOOST_REQUIRE(m_transactionSuccessful); BOOST_REQUIRE(!m_output.empty()); } diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index 0a201108a..219397048 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -20,8 +20,11 @@ * Tests for a fixed fee registrar contract. */ +#include + #include #include +#include #if defined(_MSC_VER) #pragma warning(push) @@ -122,17 +125,18 @@ contract FixedFeeRegistrar is Registrar { } )DELIMITER"; -static unique_ptr s_compiledRegistrar; +static LazyInit s_compiledRegistrar; class RegistrarTestFramework: public SolidityExecutionFramework { protected: void deployRegistrar() { - if (!s_compiledRegistrar) - s_compiledRegistrar = make_unique(compileContract(registrarCode, "FixedFeeRegistrar")); + bytes const& compiled = s_compiledRegistrar.init([&]{ + return compileContract(registrarCode, "FixedFeeRegistrar"); + }); - sendMessage(*s_compiledRegistrar, true); + sendMessage(compiled, true); BOOST_REQUIRE(m_transactionSuccessful); BOOST_REQUIRE(!m_output.empty()); } diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index 163f0755a..5f353b0bc 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -20,8 +20,11 @@ * Tests for a (comparatively) complex multisig wallet contract. */ +#include + #include #include +#include #if defined(_MSC_VER) #pragma warning(push) @@ -434,7 +437,7 @@ contract Wallet is multisig, multiowned, daylimit { } )DELIMITER"; -static unique_ptr s_compiledWallet; +static LazyInit s_compiledWallet; class WalletTestFramework: public SolidityExecutionFramework { @@ -446,11 +449,12 @@ protected: u256 _dailyLimit = 0 ) { - if (!s_compiledWallet) - s_compiledWallet = make_unique(compileContract(walletCode, "Wallet")); + bytes const& compiled = s_compiledWallet.init([&]{ + return compileContract(walletCode, "Wallet"); + }); bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); - sendMessage(*s_compiledWallet + args, true, _value); + sendMessage(compiled + args, true, _value); BOOST_REQUIRE(m_transactionSuccessful); BOOST_REQUIRE(!m_output.empty()); } diff --git a/test/libsolidity/ASTJSON/documentation.json b/test/libsolidity/ASTJSON/documentation.json index 782409abf..b4305969e 100644 --- a/test/libsolidity/ASTJSON/documentation.json +++ b/test/libsolidity/ASTJSON/documentation.json @@ -1,3 +1,4 @@ +[ { "absolutePath": "a", "exportedSymbols": @@ -218,3 +219,4 @@ ], "src": "0:160:3" } +] diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index fd65d98f8..3cc8b3ab3 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -140,6 +140,9 @@ TestCase::TestResult ASTJSONTest::run(ostream& _stream, string const& _linePrefi return TestResult::FatalError; } + if (m_sources.size() > 1) + m_result += "[\n"; + for (size_t i = 0; i < m_sources.size(); i++) { ostringstream result; @@ -150,6 +153,9 @@ TestCase::TestResult ASTJSONTest::run(ostream& _stream, string const& _linePrefi m_result += "\n"; } + if (m_sources.size() > 1) + m_result += "]\n"; + bool resultsMatch = true; replaceTagWithVersion(m_expectation); diff --git a/test/libsolidity/semanticTests/array/byte_array_pop.sol b/test/libsolidity/semanticTests/array/byte_array_pop.sol index 5ed849702..3f0e05b34 100644 --- a/test/libsolidity/semanticTests/array/byte_array_pop.sol +++ b/test/libsolidity/semanticTests/array/byte_array_pop.sol @@ -12,6 +12,7 @@ contract c { l = data.length; } } - +// ==== +// compileViaYul: also // ---- // test() -> 2, 1, 1 diff --git a/test/libsolidity/semanticTests/array/byte_array_pop_empty_exception.sol b/test/libsolidity/semanticTests/array/byte_array_pop_empty_exception.sol index 08aea4254..abe8c737b 100644 --- a/test/libsolidity/semanticTests/array/byte_array_pop_empty_exception.sol +++ b/test/libsolidity/semanticTests/array/byte_array_pop_empty_exception.sol @@ -9,5 +9,7 @@ contract c { return true; } } +// ==== +// compileViaYul: also // ---- // test() -> FAILURE diff --git a/test/libsolidity/semanticTests/array/byte_array_push.sol b/test/libsolidity/semanticTests/array/byte_array_push.sol index 67ed87b69..1a9aa3cda 100644 --- a/test/libsolidity/semanticTests/array/byte_array_push.sol +++ b/test/libsolidity/semanticTests/array/byte_array_push.sol @@ -13,6 +13,7 @@ contract c { if (l != 0x03) return true; } } - +// ==== +// compileViaYul: also // ---- // test() -> false diff --git a/test/libsolidity/semanticTests/array/byte_array_push_transition.sol b/test/libsolidity/semanticTests/array/byte_array_push_transition.sol index ceecf6726..f0733a7f5 100644 --- a/test/libsolidity/semanticTests/array/byte_array_push_transition.sol +++ b/test/libsolidity/semanticTests/array/byte_array_push_transition.sol @@ -13,6 +13,7 @@ contract c { return 0; } } - +// ==== +// compileViaYul: also // ---- // test() -> 0 diff --git a/test/libsolidity/semanticTests/array/byte_array_storage_layout.sol b/test/libsolidity/semanticTests/array/byte_array_storage_layout.sol new file mode 100644 index 000000000..b04ad9e46 --- /dev/null +++ b/test/libsolidity/semanticTests/array/byte_array_storage_layout.sol @@ -0,0 +1,45 @@ +contract c { + bytes data; + function test_short() public returns (uint256 r) { + assembly { + sstore(data_slot, 0) + } + for (uint8 i = 0; i < 15; i++) { + data.push(bytes1(i)); + } + assembly { + r := sload(data_slot) + } + } + + function test_long() public returns (uint256 r) { + assembly { + sstore(data_slot, 0) + } + for (uint8 i = 0; i < 33; i++) { + data.push(bytes1(i)); + } + assembly { + r := sload(data_slot) + } + } + + function test_pop() public returns (uint256 r) { + assembly { + sstore(data_slot, 0) + } + for (uint8 i = 0; i < 32; i++) { + data.push(bytes1(i)); + } + data.pop(); + assembly { + r := sload(data_slot) + } + } +} +// ==== +// compileViaYul: also +// ---- +// test_short() -> 1780731860627700044960722568376587075150542249149356309979516913770823710 +// test_long() -> 67 +// test_pop() -> 1780731860627700044960722568376592200742329637303199754547598369979440702 diff --git a/test/libsolidity/semanticTests/array/byte_array_transitional_2.sol b/test/libsolidity/semanticTests/array/byte_array_transitional_2.sol new file mode 100644 index 000000000..46ed1f728 --- /dev/null +++ b/test/libsolidity/semanticTests/array/byte_array_transitional_2.sol @@ -0,0 +1,21 @@ +// Tests transition between short and long encoding both ways +contract c { + bytes data; + + function test() public returns (uint256) { + for (uint8 i = 0; i < 33; i++) { + data.push(bytes1(i)); + } + for (uint8 i = 0; i < data.length; i++) + if (data[i] != bytes1(i)) return i; + data.pop(); + data.pop(); + for (uint8 i = 0; i < data.length; i++) + if (data[i] != bytes1(i)) return i; + return 0; + } +} +// ==== +// compileViaYul: also +// ---- +// test() -> 0 diff --git a/test/libsolidity/semanticTests/builtinFunctions/ripemd160_empty.sol b/test/libsolidity/semanticTests/builtinFunctions/ripemd160_empty.sol index c79625d3c..ea0fda845 100644 --- a/test/libsolidity/semanticTests/builtinFunctions/ripemd160_empty.sol +++ b/test/libsolidity/semanticTests/builtinFunctions/ripemd160_empty.sol @@ -4,5 +4,7 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // f() -> 0x9c1185a5c5e9fc54612808977ee8f548b2258d31000000000000000000000000 diff --git a/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol b/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol index bb9fc3616..ededa2fab 100644 --- a/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol +++ b/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol @@ -3,5 +3,7 @@ contract C { return sha256(""); } } +// ==== +// compileViaYul: also // ---- // f() -> 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/test/libsolidity/semanticTests/ecrecover/ecrecover.sol b/test/libsolidity/semanticTests/ecrecover/ecrecover.sol index 1beb451de..1f462752b 100644 --- a/test/libsolidity/semanticTests/ecrecover/ecrecover.sol +++ b/test/libsolidity/semanticTests/ecrecover/ecrecover.sol @@ -3,6 +3,8 @@ contract test { return ecrecover(h, v, r, s); } } +// ==== +// compileViaYul: also // ---- // a(bytes32,uint8,bytes32,bytes32): // 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c, diff --git a/test/libsolidity/semanticTests/ecrecover/ecrecover_abiV2.sol b/test/libsolidity/semanticTests/ecrecover/ecrecover_abiV2.sol index fa3adbff8..158a85a5e 100644 --- a/test/libsolidity/semanticTests/ecrecover/ecrecover_abiV2.sol +++ b/test/libsolidity/semanticTests/ecrecover/ecrecover_abiV2.sol @@ -4,6 +4,8 @@ contract test { return ecrecover(h, v, r, s); } } +// ==== +// compileViaYul: also // ---- // a(bytes32,uint8,bytes32,bytes32): // 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c, diff --git a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input.sol b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input.sol index fe0cbf412..6560baf6a 100644 --- a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input.sol +++ b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input.sol @@ -6,5 +6,7 @@ contract C { return ecrecover(bytes32(uint(-1)), 1, bytes32(uint(2)), bytes32(uint(3))); } } +// ==== +// compileViaYul: also // ---- // f() -> 0 diff --git a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_asm.sol b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_asm.sol index 5e8c3cdbf..607f3ec1a 100644 --- a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_asm.sol +++ b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_asm.sol @@ -11,5 +11,7 @@ contract C { ); } } +// ==== +// compileViaYul: also // ---- // f() -> 0 diff --git a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_proper.sol b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_proper.sol index 511bd4e41..4fa1ec514 100644 --- a/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_proper.sol +++ b/test/libsolidity/semanticTests/ecrecover/failing_ecrecover_invalid_input_proper.sol @@ -16,5 +16,7 @@ contract C { return ecrecover(hash, v, r, s); } } +// ==== +// compileViaYul: also // ---- // f() -> 0 diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_assignment_2d_memory_to_memory.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_assignment_2d_memory_to_memory.sol new file mode 100644 index 000000000..2e3ef86fd --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_assignment_2d_memory_to_memory.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; +pragma experimental ABIEncoderV2; + +contract C { + function f(uint[][] memory arr) public pure { + uint[][] memory arr2 = arr; + assert(arr2[0].length == arr[0].length); + assert(arr.length == arr2.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_assignment_2d_storage_to_storage.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_assignment_2d_storage_to_storage.sol new file mode 100644 index 000000000..acf6a1e1d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_assignment_2d_storage_to_storage.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; +pragma experimental ABIEncoderV2; + +contract C { + uint[][] arr; + uint[][] arr2; + function f() public view { + assert(arr2[0].length == arr[0].length); + assert(arr2.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_copy_2d_memory_to_storage.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_copy_2d_memory_to_storage.sol new file mode 100644 index 000000000..ce0d9ef5d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_copy_2d_memory_to_storage.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; +pragma experimental ABIEncoderV2; + +contract C { + uint[][] arr; + function f(uint[][] memory arr2) public { + arr = arr2; + assert(arr2[0].length == arr[0].length); + assert(arr2.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_copy_2d_storage_to_memory.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_copy_2d_storage_to_memory.sol new file mode 100644 index 000000000..4aa03c222 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_copy_2d_storage_to_memory.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; +pragma experimental ABIEncoderV2; + +contract C { + uint[][] arr; + function f() public view { + uint[][] memory arr2 = arr; + assert(arr2[0].length == arr[0].length); + assert(arr2.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_1.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_1.sol new file mode 100644 index 000000000..dbce0665c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_1.sol @@ -0,0 +1,8 @@ +pragma experimental SMTChecker; + +contract C { + mapping (uint => uint[]) map; + function f() public view { + assert(map[0].length == map[1].length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_2.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_2.sol new file mode 100644 index 000000000..dc2305f79 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_2.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + mapping (uint => uint[]) map; + function f(uint x, uint y) public view { + require(x == y); + assert(map[x].length == map[y].length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_2d_1.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_2d_1.sol new file mode 100644 index 000000000..bdc24df30 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_mapping_array_2d_1.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + mapping (uint => uint[][]) map; + function f(uint x, uint y) public view { + require(x == y); + assert(map[x][0].length == map[y][0].length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_struct_array_1.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_struct_array_1.sol new file mode 100644 index 000000000..68be993a8 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_struct_array_1.sol @@ -0,0 +1,20 @@ +pragma experimental SMTChecker; + +contract C { + struct S { + uint[] arr; + } + S s1; + S s2; + function f() public view { + assert(s1.arr.length == s2.arr.length); + } +} +// ---- +// Warning: (76-80): Assertion checker does not yet support the type of this variable. +// Warning: (83-87): Assertion checker does not yet support the type of this variable. +// Warning: (126-132): Assertion checker does not yet support this expression. +// Warning: (126-128): Assertion checker does not yet implement type struct C.S storage ref +// Warning: (143-149): Assertion checker does not yet support this expression. +// Warning: (143-145): Assertion checker does not yet implement type struct C.S storage ref +// Warning: (119-157): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/length_1d_struct_array_2d_1.sol b/test/libsolidity/smtCheckerTests/array_members/length_1d_struct_array_2d_1.sol new file mode 100644 index 000000000..bb248c43e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_1d_struct_array_2d_1.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract C { + struct S { + uint[][] arr; + } + S s1; + S s2; + function f() public view { + assert(s1.arr[0].length == s2.arr[0].length); + } +} +// ---- +// Warning: (78-82): Assertion checker does not yet support the type of this variable. +// Warning: (85-89): Assertion checker does not yet support the type of this variable. +// Warning: (128-134): Assertion checker does not yet support this expression. +// Warning: (128-130): Assertion checker does not yet implement type struct C.S storage ref +// Warning: (128-137): Assertion checker does not yet implement this expression. +// Warning: (148-154): Assertion checker does not yet support this expression. +// Warning: (148-150): Assertion checker does not yet implement type struct C.S storage ref +// Warning: (148-157): Assertion checker does not yet implement this expression. +// Warning: (121-165): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/length_assignment_2d_memory_to_memory.sol b/test/libsolidity/smtCheckerTests/array_members/length_assignment_2d_memory_to_memory.sol new file mode 100644 index 000000000..d85f01104 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_assignment_2d_memory_to_memory.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; +pragma experimental ABIEncoderV2; + +contract C { + function f(uint[][] memory arr) public pure { + uint[][] memory arr2 = arr; + assert(arr2.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_assignment_memory_to_memory.sol b/test/libsolidity/smtCheckerTests/array_members/length_assignment_memory_to_memory.sol new file mode 100644 index 000000000..66f6d1067 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_assignment_memory_to_memory.sol @@ -0,0 +1,8 @@ +pragma experimental SMTChecker; + +contract C { + function f(uint[] memory arr) public pure { + uint[] memory arr2 = arr; + assert(arr2.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_assignment_storage_to_storage.sol b/test/libsolidity/smtCheckerTests/array_members/length_assignment_storage_to_storage.sol new file mode 100644 index 000000000..c379f3381 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_assignment_storage_to_storage.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[] arr; + uint[] arr2; + function f() public { + arr2 = arr; + assert(arr2.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_basic.sol b/test/libsolidity/smtCheckerTests/array_members/length_basic.sol new file mode 100644 index 000000000..cd1041f7c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_basic.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C { + uint[] arr; + function f() public view { + uint x = arr.length; + uint y = x; + assert(arr.length == y); + assert(arr.length != y); + } +} +// ---- +// Warning: (153-176): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/length_copy_memory_to_storage.sol b/test/libsolidity/smtCheckerTests/array_members/length_copy_memory_to_storage.sol new file mode 100644 index 000000000..ec6c2a2f2 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_copy_memory_to_storage.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + uint[] arr; + function f(uint[] memory marr) public { + arr = marr; + assert(marr.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_copy_storage_to_memory.sol b/test/libsolidity/smtCheckerTests/array_members/length_copy_storage_to_memory.sol new file mode 100644 index 000000000..20e83c908 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_copy_storage_to_memory.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + uint[] arr; + function f() public view { + uint[] memory marr = arr; + assert(marr.length == arr.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_function_call.sol b/test/libsolidity/smtCheckerTests/array_members/length_function_call.sol new file mode 100644 index 000000000..250c88666 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_function_call.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[] arr; + function f() public view { + assert(arr.length == g().length); + } + function g() internal pure returns (uint[] memory) { + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment.sol b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment.sol new file mode 100644 index 000000000..e0d13b35a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[] arr; + function f() public view { + uint[] memory arr2 = arr; + arr2[2] = 3; + assert(arr.length == arr2.length); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_2.sol b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_2.sol new file mode 100644 index 000000000..9665c0317 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_2.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] arr; + uint[][] arr2; + function f() public { + uint x = arr[2].length; + uint y = arr[3].length; + uint z = arr.length; + arr[2][333] = 444; + assert(arr[2].length == x); + assert(arr[3].length == y); + assert(arr.length == z); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_2_fail.sol b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_2_fail.sol new file mode 100644 index 000000000..e5356d240 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_2_fail.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] arr; + uint[][] arr2; + function f() public { + uint x = arr[2].length; + uint y = arr[3].length; + uint z = arr.length; + arr[2][333] = 444; + assert(arr[2].length != x); + assert(arr[3].length != y); + assert(arr.length != z); + } +} +// ---- +// Warning: (198-224): Assertion violation happens here +// Warning: (228-254): Assertion violation happens here +// Warning: (258-281): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_3.sol b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_3.sol new file mode 100644 index 000000000..c36d55e76 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_3.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] arr; + uint[][] arr2; + function f() public { + uint x = arr[2].length; + uint y = arr[3].length; + uint z = arr.length; + uint t = arr[5].length; + arr[5] = arr[8]; + assert(arr[2].length == x); + assert(arr[3].length == y); + assert(arr.length == z); + assert(arr[5].length == t); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_3_fail.sol b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_3_fail.sol new file mode 100644 index 000000000..92da0cf61 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/length_same_after_assignment_3_fail.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] arr; + uint[][] arr2; + function f() public { + uint x = arr[2].length; + uint y = arr[3].length; + uint z = arr.length; + uint t = arr[5].length; + arr[5] = arr[8]; + assert(arr[2].length != x); + assert(arr[3].length != y); + assert(arr.length != z); + assert(arr[5].length != t); + } +} +// ---- +// Warning: (222-248): Assertion violation happens here +// Warning: (252-278): Assertion violation happens here +// Warning: (282-305): Assertion violation happens here +// Warning: (309-335): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_1_safe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_1_safe.sol new file mode 100644 index 000000000..34663d931 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_1_safe.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + function f() public { + a.push(); + a.pop(); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_1_unsafe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_1_unsafe.sol new file mode 100644 index 000000000..e6f2273f8 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_1_unsafe.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + function f() public { + a.pop(); + } +} +// ---- +// Warning: (82-89): Empty array "pop" detected here. diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_2d_safe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_2d_safe.sol new file mode 100644 index 000000000..c67e91959 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_2d_safe.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f() public { + a.push(); + a[0].push(); + a[0].pop(); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_2d_unsafe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_2d_unsafe.sol new file mode 100644 index 000000000..1161ab92e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_2d_unsafe.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f() public { + a.push(); + a[0].push(); + a[1].pop(); + } +} +// ---- +// Warning: (111-121): Empty array "pop" detected here. diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_constructor_safe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_constructor_safe.sol new file mode 100644 index 000000000..4842ee56f --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_constructor_safe.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + constructor() public { + a.push(); + a.pop(); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_constructor_unsafe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_constructor_unsafe.sol new file mode 100644 index 000000000..a86d1a3f3 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_constructor_unsafe.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + constructor() public { + a.pop(); + } +} +// ---- +// Warning: (83-90): Empty array "pop" detected here. diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_loop_safe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_loop_safe.sol new file mode 100644 index 000000000..25986da7b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_loop_safe.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + function f(uint l) public { + for (uint i = 0; i < l; ++i) { + a.push(); + a.pop(); + } + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/pop_loop_unsafe.sol b/test/libsolidity/smtCheckerTests/array_members/pop_loop_unsafe.sol new file mode 100644 index 000000000..5d4241984 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/pop_loop_unsafe.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + function f(uint l) public { + for (uint i = 0; i < l; ++i) { + a.push(); + a.pop(); + } + a.pop(); + } +} +// ---- +// Warning: (150-157): Empty array "pop" detected here. diff --git a/test/libsolidity/smtCheckerTests/array_members/push_2d_arg_1_safe.sol b/test/libsolidity/smtCheckerTests/array_members/push_2d_arg_1_safe.sol new file mode 100644 index 000000000..34a1ae680 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_2d_arg_1_safe.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f(uint[] memory x, uint y) public { + a.push(x); + a[0].push(y); + assert(a[0][a[0].length - 1] == y); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_2d_arg_1_unsafe.sol b/test/libsolidity/smtCheckerTests/array_members/push_2d_arg_1_unsafe.sol new file mode 100644 index 000000000..d5bd19bb1 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_2d_arg_1_unsafe.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f(uint[] memory x, uint y) public { + a.push(x); + a[0].push(y); + a[0].pop(); + assert(a[0][a[0].length - 1] == y); + } +} +// ---- +// Warning: (162-177): Underflow (resulting value less than 0) happens here +// Warning: (150-184): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/push_arg_1.sol b/test/libsolidity/smtCheckerTests/array_members/push_arg_1.sol new file mode 100644 index 000000000..e36895554 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_arg_1.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + function f(uint x) public { + a.push(x); + assert(a[a.length - 1] == x); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_overflow_1_safe.sol b/test/libsolidity/smtCheckerTests/array_members/push_overflow_1_safe.sol new file mode 100644 index 000000000..f563504f4 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_overflow_1_safe.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint256[] x; + constructor() public { x.push(42); } + function f() public { + x.push(23); + assert(x[0] == 42 || x[0] == 23); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_overflow_1_safe_no_overflow_assumption.sol b/test/libsolidity/smtCheckerTests/array_members/push_overflow_1_safe_no_overflow_assumption.sol new file mode 100644 index 000000000..7ed99a69e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_overflow_1_safe_no_overflow_assumption.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint256[] x; + constructor() public { x.push(42); } + function f() public { + x.push(23); + assert(x[0] == 42); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_overflow_2_safe.sol b/test/libsolidity/smtCheckerTests/array_members/push_overflow_2_safe.sol new file mode 100644 index 000000000..340f670d0 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_overflow_2_safe.sol @@ -0,0 +1,12 @@ +contract C { + uint256[] x; + function f(uint256 l) public { + require(x.length == 0); + x.push(42); + x.push(84); + for(uint256 i = 0; i < l; ++i) + x.push(23); + assert(x[0] == 42 || x[0] == 23); + } +} + diff --git a/test/libsolidity/smtCheckerTests/array_members/push_overflow_2_safe_no_overflow_assumption.sol b/test/libsolidity/smtCheckerTests/array_members/push_overflow_2_safe_no_overflow_assumption.sol new file mode 100644 index 000000000..1f468f592 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_overflow_2_safe_no_overflow_assumption.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C { + uint256[] x; + function f(uint256 l) public { + require(x.length == 0); + x.push(42); + x.push(84); + for(uint256 i = 0; i < l; ++i) + x.push(23); + assert(x[0] == 42); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_storage_ref_safe_aliasing.sol b/test/libsolidity/smtCheckerTests/array_members/push_storage_ref_safe_aliasing.sol new file mode 100644 index 000000000..8ad5cdcad --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_storage_ref_safe_aliasing.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f() public { + a.push(); + uint[] storage b = a[0]; + b.push(8); + assert(b[b.length - 1] == 8); + // Safe but fails due to aliasing. + assert(a[0][a[0].length - 1] == 8); + } +} +// ---- +// Warning: (217-232): Underflow (resulting value less than 0) happens here +// Warning: (205-239): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/push_storage_ref_unsafe_aliasing.sol b/test/libsolidity/smtCheckerTests/array_members/push_storage_ref_unsafe_aliasing.sol new file mode 100644 index 000000000..f54fc8d83 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_storage_ref_unsafe_aliasing.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f() public { + a.push(); + a[0].push(); + a[0][0] = 16; + uint[] storage b = a[0]; + b[0] = 32; + assert(a[0][0] == 16); + } +} +// ---- +// Warning: (167-188): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/push_zero_2d_safe.sol b/test/libsolidity/smtCheckerTests/array_members/push_zero_2d_safe.sol new file mode 100644 index 000000000..45a56e677 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_zero_2d_safe.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f() public { + a.push(); + a[0].push(); + assert(a[a.length - 1][0] == 0); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_zero_2d_unsafe.sol b/test/libsolidity/smtCheckerTests/array_members/push_zero_2d_unsafe.sol new file mode 100644 index 000000000..9b9516ccd --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_zero_2d_unsafe.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C { + uint[][] a; + function f() public { + a.push(); + a[0].push(); + assert(a[a.length - 1][0] == 100); + } +} +// ---- +// Warning: (111-144): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/push_zero_safe.sol b/test/libsolidity/smtCheckerTests/array_members/push_zero_safe.sol new file mode 100644 index 000000000..e8054e361 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_zero_safe.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + function f() public { + a.push(); + assert(a[a.length - 1] == 0); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_zero_unsafe.sol b/test/libsolidity/smtCheckerTests/array_members/push_zero_unsafe.sol new file mode 100644 index 000000000..966b4e1bd --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_zero_unsafe.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C { + uint[] a; + function f() public { + a.push(); + assert(a[a.length - 1] == 100); + } +} +// ---- +// Warning: (94-124): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol b/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol index 955d5fd1e..aa69285ba 100644 --- a/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol +++ b/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol @@ -34,9 +34,7 @@ library MerkleProof { } // ---- -// Warning: (755-767): Assertion checker does not yet support this expression. // Warning: (988-991): Assertion checker does not yet implement type abi // Warning: (988-1032): Assertion checker does not yet implement this type of function call. // Warning: (1175-1178): Assertion checker does not yet implement type abi // Warning: (1175-1219): Assertion checker does not yet implement this type of function call. -// Warning: (755-767): Assertion checker does not yet support this expression. diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol index a90053024..5c664f214 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol @@ -19,5 +19,7 @@ contract LoopFor2 { } } // ---- +// Warning: (317-337): Error trying to invoke SMT solver. +// Warning: (317-337): Assertion violation happens here // Warning: (341-360): Assertion violation happens here // Warning: (364-383): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol index a47763c03..1892441ca 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol @@ -4,6 +4,7 @@ contract C { uint[][] a; function f(bool b) public { + a[1][1] = 512; a[2][3] = 4; if (b) delete a; @@ -16,3 +17,4 @@ contract C // ==== // SMTSolvers: z3 // ---- +// Warning: (191-211): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/special/msg_data.sol b/test/libsolidity/smtCheckerTests/special/msg_data.sol index dff997395..9db24dbcb 100644 --- a/test/libsolidity/smtCheckerTests/special/msg_data.sol +++ b/test/libsolidity/smtCheckerTests/special/msg_data.sol @@ -7,5 +7,4 @@ contract C } } // ---- -// Warning: (86-101): Assertion checker does not yet support this expression. // Warning: (79-106): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/string_1.sol b/test/libsolidity/smtCheckerTests/types/string_1.sol index 7f03c29da..2f2f64488 100644 --- a/test/libsolidity/smtCheckerTests/types/string_1.sol +++ b/test/libsolidity/smtCheckerTests/types/string_1.sol @@ -7,6 +7,4 @@ contract C } } // ---- -// Warning: (117-133): Assertion checker does not yet support this expression. -// Warning: (137-153): Assertion checker does not yet support this expression. // Warning: (110-154): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTestsJSON/multi.json b/test/libsolidity/smtCheckerTestsJSON/multi.json index b1be75073..986429dac 100644 --- a/test/libsolidity/smtCheckerTestsJSON/multi.json +++ b/test/libsolidity/smtCheckerTestsJSON/multi.json @@ -3,8 +3,8 @@ { "smtlib2responses": { - "0x9c50514d749eabf3c13d97ad7d787e682dd99a114bad652b10a01b8c6ad6c1fb": "sat\n((|EVALEXPR_0| 1))\n", - "0xb524e7c577188e2e36f0e67fead51269fa0f8b8fb41bff2d973dcf584d38cd1e": "sat\n((|EVALEXPR_0| 0))\n" + "0x45598870c7c0bc4c4f61acad7e0dd9399fb28aa3df198379dd36a95d70814ef8": "sat\n((|EVALEXPR_0| 1))\n", + "0xee335f8104fdb81b6e5fb418725923b81f7d78ffbd6bf95fb82e5593a1ac366a": "sat\n((|EVALEXPR_0| 0))\n" } } } diff --git a/test/libsolidity/smtCheckerTestsJSON/simple.json b/test/libsolidity/smtCheckerTestsJSON/simple.json index 89eac1b80..0b70ea24b 100644 --- a/test/libsolidity/smtCheckerTestsJSON/simple.json +++ b/test/libsolidity/smtCheckerTestsJSON/simple.json @@ -3,7 +3,7 @@ { "smtlib2responses": { - "0x45c37a9829e623d7838d82b547d297cd446d6b5faff36c53a56862fcee50fb41": "sat\n((|EVALEXPR_0| 0))\n" + "0x535c76d6b87d14bd4b4bf1014d14b8e91b648f073a68f9f267c4fe1df570bc14": "sat\n((|EVALEXPR_0| 0))\n" } } } diff --git a/test/libsolidity/syntaxTests/types/mapping/assignment_local_err.sol b/test/libsolidity/syntaxTests/types/mapping/assignment_local_err.sol new file mode 100644 index 000000000..1d8f8c301 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/mapping/assignment_local_err.sol @@ -0,0 +1,10 @@ +contract D { + mapping (uint => uint) a; + mapping (uint => uint) b; + function foo() public view { + mapping (uint => uint) storage c = b; + b = c; + } +} +// ---- +// TypeError: (160-161): Mappings cannot be assigned to. diff --git a/test/libsolidity/syntaxTests/types/mapping/assignment_map.sol b/test/libsolidity/syntaxTests/types/mapping/assignment_map.sol new file mode 100644 index 000000000..1ec86d4cf --- /dev/null +++ b/test/libsolidity/syntaxTests/types/mapping/assignment_map.sol @@ -0,0 +1,27 @@ +contract C { + mapping (uint => address payable [ ]) public a = a ; +} + +contract D { + mapping (uint => uint) a; + mapping (uint => uint) b = a; +} + +contract F { + mapping (uint => uint) a; + mapping (uint => uint) b; + + function foo() public { + a = b; + } +} + +contract G { + uint x = 1; + mapping (uint => uint) b = x; +} +// ---- +// TypeError: (17-67): Mappings cannot be assigned to. +// TypeError: (120-148): Mappings cannot be assigned to. +// TypeError: (263-264): Mappings cannot be assigned to. +// TypeError: (312-340): Mappings cannot be assigned to. diff --git a/test/libsolidity/syntaxTests/types/mapping/assignment_type_mismatch.sol b/test/libsolidity/syntaxTests/types/mapping/assignment_type_mismatch.sol new file mode 100644 index 000000000..aa85a8858 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/mapping/assignment_type_mismatch.sol @@ -0,0 +1,8 @@ +contract D { + mapping (uint => uint) a; + function foo() public view { + mapping (uint => int) storage c = a; + } +} +// ---- +// TypeError: (84-119): Type mapping(uint256 => uint256) is not implicitly convertible to expected type mapping(uint256 => int256). diff --git a/test/libsolutil/LazyInit.cpp b/test/libsolutil/LazyInit.cpp new file mode 100644 index 000000000..346805c82 --- /dev/null +++ b/test/libsolutil/LazyInit.cpp @@ -0,0 +1,118 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include + +#include + +namespace solidity::util::test +{ + +namespace +{ + +template +void assertInitCalled(LazyInit lazyInit, bool target) +{ + bool initCalled = false; + + lazyInit.init([&]{ + initCalled = true; + return T(); + }); + + BOOST_REQUIRE_EQUAL(initCalled, target); +} + +// Take ownership to ensure that it doesn't "mutate" +template +void assertNotEmpty(LazyInit _lazyInit) { assertInitCalled(std::move(_lazyInit), false); } + +// Take ownership to ensure that it doesn't "mutate" +template +void assertEmpty(LazyInit _lazyInit) { assertInitCalled(std::move(_lazyInit), true); } + +template +T valueOf(LazyInit _lazyInit) +{ + return _lazyInit.init([&]{ + BOOST_REQUIRE(false); // this should never be called + return T(); + }); +} + +} + +BOOST_AUTO_TEST_SUITE(LazyInitTests) + +BOOST_AUTO_TEST_CASE(default_constructed_is_empty) +{ + assertEmpty(LazyInit()); + assertEmpty(LazyInit()); +} + +BOOST_AUTO_TEST_CASE(initialized_is_not_empty) +{ + LazyInit lazyInit; + lazyInit.init([]{ return 12; }); + + assertNotEmpty(std::move(lazyInit)); +} + +BOOST_AUTO_TEST_CASE(init_returns_init_value) +{ + LazyInit lazyInit; + + BOOST_CHECK_EQUAL(lazyInit.init([]{ return 12; }), 12); + + // A second call to init should not change the value + BOOST_CHECK_EQUAL(lazyInit.init([]{ return 42; }), 12); +} + +BOOST_AUTO_TEST_CASE(moved_from_is_empty) +{ + { + LazyInit lazyInit; + { [[maybe_unused]] auto pilfered = std::move(lazyInit); } + + assertEmpty(std::move(lazyInit)); + } + + { + LazyInit lazyInit; + lazyInit.init([]{ return 12; }); + + { [[maybe_unused]] auto pilfered = std::move(lazyInit); } + + assertEmpty(std::move(lazyInit)); + } +} + +BOOST_AUTO_TEST_CASE(move_constructed_has_same_value_as_original) +{ + LazyInit original; + original.init([]{ return 12; }); + + LazyInit moveConstructed = std::move(original); + BOOST_CHECK_EQUAL(valueOf(std::move(moveConstructed)), 12); +} + +BOOST_AUTO_TEST_SUITE_END() + +}