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 ()+retParams> -> +retParams> {
@@ -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("