mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8983 from ethereum/develop
Merge develop into breaking.
This commit is contained in:
commit
d422a406ba
@ -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
|
||||
|
66
.circleci/docker/Dockerfile.emscripten
Normal file
66
.circleci/docker/Dockerfile.emscripten
Normal file
@ -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 <http://www.gnu.org/licenses/>
|
||||
#
|
||||
# (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
|
@ -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 <toolset>emscripten:<testing.launcher>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 : <toolset>emscripten : js ;
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
{
|
||||
if (_variable.isStateVariable() && dynamic_cast<MappingType const*>(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())
|
||||
|
@ -153,10 +153,10 @@ FunctionDefinition const* ContractDefinition::receiveFunction() const
|
||||
|
||||
vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() const
|
||||
{
|
||||
if (!m_interfaceEvents)
|
||||
{
|
||||
return m_interfaceEvents.init([&]{
|
||||
set<string> eventsSeen;
|
||||
m_interfaceEvents = make_unique<vector<EventDefinition const*>>();
|
||||
vector<EventDefinition const*> interfaceEvents;
|
||||
|
||||
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
||||
for (EventDefinition const* e: contract->events())
|
||||
{
|
||||
@ -169,19 +169,20 @@ vector<EventDefinition const*> 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<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
|
||||
{
|
||||
if (!m_interfaceFunctionList[_includeInheritedFunctions])
|
||||
{
|
||||
return m_interfaceFunctionList[_includeInheritedFunctions].init([&]{
|
||||
set<string> signaturesSeen;
|
||||
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
|
||||
vector<pair<util::FixedHash<4>, FunctionTypePointer>> interfaceFunctionList;
|
||||
|
||||
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
||||
{
|
||||
if (_includeInheritedFunctions == false && contract != this)
|
||||
@ -203,12 +204,13 @@ vector<pair<util::FixedHash<4>, 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
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libsolutil/FixedHash.h>
|
||||
#include <libsolutil/LazyInit.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <json/json.h>
|
||||
@ -536,8 +537,8 @@ private:
|
||||
ContractKind m_contractKind;
|
||||
bool m_abstract{false};
|
||||
|
||||
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
|
||||
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
|
||||
util::LazyInit<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
|
||||
util::LazyInit<std::vector<EventDefinition const*>> m_interfaceEvents;
|
||||
};
|
||||
|
||||
class InheritanceSpecifier: public ASTNode
|
||||
|
@ -207,26 +207,31 @@ void MemberList::combine(MemberList const & _other)
|
||||
|
||||
pair<u256, unsigned> 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<StorageOffsets>();
|
||||
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
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <libsolutil/LazyInit.h>
|
||||
#include <libsolutil/Result.h>
|
||||
|
||||
#include <boost/rational.hpp>
|
||||
@ -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<StorageOffsets> m_storageOffsets;
|
||||
util::LazyInit<StorageOffsets> m_storageOffsets;
|
||||
};
|
||||
|
||||
static_assert(std::is_nothrow_move_constructible<MemberList>::value, "MemberList should be noexcept move constructible");
|
||||
|
@ -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 <functionName>(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)
|
||||
<?storage>
|
||||
length := sload(value)
|
||||
<?byteArray>
|
||||
// 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 := <extractByteArrayLength>(length)
|
||||
</byteArray>
|
||||
</storage>
|
||||
<!dynamic>
|
||||
@ -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);
|
||||
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 := <fetchLength>(array)
|
||||
if iszero(oldLen) { invalid() }
|
||||
let newLen := sub(oldLen, 1)
|
||||
|
||||
let slot, offset := <indexAccess>(array, newLen)
|
||||
<setToZero>(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 <functionName>(array) {
|
||||
let data := sload(array)
|
||||
let oldLen := <extractByteArrayLength>(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 := <dataAreaFunction>(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(<shl>(mul(8, sub(31, newLen)), 0xff))
|
||||
data := and(data, mask)
|
||||
}
|
||||
default {
|
||||
let slot, offset := <indexAccess>(array, newLen)
|
||||
<setToZero>(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 <functionName>(array, value) {
|
||||
let oldLen := <fetchLength>(array)
|
||||
<?isByteArray>
|
||||
let data := sload(array)
|
||||
let oldLen := <extractByteArrayLength>(data)
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
|
||||
sstore(array, add(oldLen, 1))
|
||||
|
||||
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 := <dataAreaFunction>(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 := <shl>(shiftBits, and(0xff, value))
|
||||
let mask := <shl>(shiftBits, 0xff)
|
||||
data := or(and(data, not(mask)), valueShifted)
|
||||
sstore(array, data)
|
||||
}
|
||||
}
|
||||
default {
|
||||
sstore(array, add(data, 2))
|
||||
let slot, offset := <indexAccess>(array, oldLen)
|
||||
<storeValue>(slot, offset, value)
|
||||
}
|
||||
<!isByteArray>
|
||||
let oldLen := sload(array)
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
|
||||
sstore(array, add(oldLen, 1))
|
||||
let slot, offset := <indexAccess>(array, oldLen)
|
||||
<storeValue>(slot, offset, value)
|
||||
</isByteArray>
|
||||
})")
|
||||
("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 <functionName>(array, index) -> slot, offset {
|
||||
if iszero(lt(index, <arrayLen>(array))) {
|
||||
invalid()
|
||||
}
|
||||
let arrayLength := <arrayLen>(array)
|
||||
if iszero(lt(index, arrayLength)) { invalid() }
|
||||
|
||||
let data := <dataAreaFunc>(array)
|
||||
<?multipleItemsPerSlot>
|
||||
|
||||
<?isBytesArray>
|
||||
offset := sub(31, mod(index, 0x20))
|
||||
switch lt(arrayLength, 0x20)
|
||||
case 0 {
|
||||
let dataArea := <dataAreaFunc>(array)
|
||||
slot := add(dataArea, div(index, 0x20))
|
||||
}
|
||||
default {
|
||||
slot := array
|
||||
}
|
||||
<!isBytesArray>
|
||||
let itemsPerSlot := div(0x20, <storageBytes>)
|
||||
let dataArea := <dataAreaFunc>(array)
|
||||
slot := add(dataArea, div(index, itemsPerSlot))
|
||||
offset := mod(index, itemsPerSlot)
|
||||
</isBytesArray>
|
||||
<!multipleItemsPerSlot>
|
||||
slot := add(data, mul(index, <storageSize>))
|
||||
let dataArea := <dataAreaFunc>(array)
|
||||
slot := add(dataArea, mul(index, <storageSize>))
|
||||
offset := 0
|
||||
</multipleItemsPerSlot>
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
86
libsolidity/codegen/ir/Common.cpp
Normal file
86
libsolidity/codegen/ir/Common.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/ir/Common.h>
|
||||
|
||||
#include <libsolutil/CommonIO.h>
|
||||
|
||||
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<FunctionCallAnnotation const*>(&_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;
|
||||
}
|
47
libsolidity/codegen/ir/Common.h
Normal file
47
libsolidity/codegen/ir/Common.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Miscellaneous utilities for use in IR generator.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
@ -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<FunctionCallAnnotation const&>(_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<string, string> {
|
||||
{ "funID", to_string(function->id()) },
|
||||
{ "name", functionName(*function)}
|
||||
{ "name", IRNames::function(*function)}
|
||||
});
|
||||
|
||||
enqueueFunctionForCodeGeneration(*function);
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
#include <libsolidity/codegen/ir/Common.h>
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
@ -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<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
|
||||
|
||||
private:
|
||||
|
@ -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 <functionName>(<params>)<?+retParams> -> <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 <functionName>(<params><comma><baseParams>) {
|
||||
<evalBaseArguments>
|
||||
@ -395,7 +395,7 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra
|
||||
vector<string> baseParams = listAllParams(baseConstructorParams);
|
||||
t("baseParams", joinHumanReadable(baseParams));
|
||||
t("comma", !params.empty() && !baseParams.empty() ? ", " : "");
|
||||
t("functionName", implicitConstructorName(*contract));
|
||||
t("functionName", IRNames::implicitConstructor(*contract));
|
||||
pair<string, map<ContractDefinition const*, vector<string>>> 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("<object>"))
|
||||
)X");
|
||||
t("object", m_context.runtimeObjectName(_contract));
|
||||
t("object", IRNames::runtimeObject(_contract));
|
||||
|
||||
vector<map<string, string>> loadImmutables;
|
||||
vector<map<string, string>> 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(
|
||||
|
@ -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();
|
||||
|
@ -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 <functionName>() -> <ret> {
|
||||
@ -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<MemberAccess const&>(_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<MemberAccess const&>(_functionCall.expression()).expression();
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*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<FunctionType::Kind, std::tuple<u160, size_t>> 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<string> argumentStrings;
|
||||
for (auto const& arg: arguments)
|
||||
{
|
||||
argumentTypes.emplace_back(&type(*arg));
|
||||
argumentStrings += IRVariable(*arg).stackSlots();
|
||||
}
|
||||
Whiskers templ(R"(
|
||||
let <pos> := <allocateTemporary>()
|
||||
let <end> := <encodeArgs>(<pos> <argumentString>)
|
||||
<?isECRecover>
|
||||
mstore(0, 0)
|
||||
</isECRecover>
|
||||
let <success> := <call>(<gas>, <address> <?isCall>, 0</isCall>, <pos>, sub(<end>, <pos>), 0, 32)
|
||||
if iszero(<success>) { <forwardingRevert>() }
|
||||
let <retVars> := <shl>(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<int>(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();
|
||||
|
@ -14,6 +14,7 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <libsolidity/codegen/ir/Common.h>
|
||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
@ -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
|
||||
|
@ -97,6 +97,8 @@ void CHC::analyze(SourceUnit const& _source)
|
||||
source->accept(*this);
|
||||
|
||||
for (auto const& [scope, target]: m_verificationTargets)
|
||||
{
|
||||
if (target.type == VerificationTarget::Type::Assert)
|
||||
{
|
||||
auto assertions = transactionAssertions(scope);
|
||||
for (auto const* assertion: assertions)
|
||||
@ -110,6 +112,32 @@ void CHC::analyze(SourceUnit const& _source)
|
||||
m_safeAssertions.insert(assertion);
|
||||
}
|
||||
}
|
||||
else if (target.type == VerificationTarget::Type::PopEmptyArray)
|
||||
{
|
||||
solAssert(dynamic_cast<FunctionCall const*>(scope), "");
|
||||
createErrorBlock();
|
||||
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)
|
||||
{
|
||||
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, "");
|
||||
}
|
||||
}
|
||||
|
||||
vector<string> CHC::unhandledQueries() const
|
||||
@ -161,7 +189,7 @@ void CHC::endVisit(ContractDefinition const& _contract)
|
||||
auto stateExprs = vector<smt::Expression>{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<FunctionType const&>(*_arrayPop.expression().annotation().type);
|
||||
solAssert(funType.kind() == FunctionType::Kind::ArrayPop, "");
|
||||
|
||||
auto memberAccess = dynamic_cast<MemberAccess const*>(&_arrayPop.expression());
|
||||
solAssert(memberAccess, "");
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(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<smt::CheckResult, vector<string>> 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()
|
||||
|
@ -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 <false, model> otherwise.
|
||||
std::pair<smt::CheckResult, std::vector<std::string>> 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<Expression const*> m_safeAssertions;
|
||||
/// Targets proven unsafe.
|
||||
std::set<ASTNode const*> m_unsafeTargets;
|
||||
//@}
|
||||
|
||||
/// Control-flow.
|
||||
|
@ -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> tupleSort = std::dynamic_pointer_cast<TupleSort>(_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, "");
|
||||
}
|
||||
|
@ -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<smt::SymbolicArrayVariable>(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<smt::SymbolicArrayVariable>(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<smt::SymbolicArrayVariable>(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 const*>(&indexAccess->baseExpression()))
|
||||
{
|
||||
toStore = smt::Expression::store(expr(*base), expr(*indexAccess->indexExpression()), toStore);
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(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<MemberAccess const*>(&_funCall.expression());
|
||||
solAssert(memberAccess, "");
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(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<MemberAccess const*>(&_funCall.expression());
|
||||
solAssert(memberAccess, "");
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(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<Identifier const*>(&_expr))
|
||||
{
|
||||
auto varDecl = identifierToVariable(*id);
|
||||
solAssert(varDecl, "");
|
||||
m_context.addAssertion(m_context.newValue(*varDecl) == _array);
|
||||
}
|
||||
else if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(&_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)
|
||||
{
|
||||
// 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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -153,7 +153,15 @@ string SMTLib2Interface::toSExpr(smt::Expression const& _expr)
|
||||
auto tupleSort = dynamic_pointer_cast<TupleSort>(_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<TupleSort>(_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<TupleSort const&>(_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");
|
||||
|
@ -29,5 +29,12 @@ SSAVariable::SSAVariable()
|
||||
void SSAVariable::resetIndex()
|
||||
{
|
||||
m_currentIndex = 0;
|
||||
m_nextFreeIndex = make_unique<unsigned>(1);
|
||||
m_nextFreeIndex = 1;
|
||||
}
|
||||
|
||||
void SSAVariable::setIndex(unsigned _index)
|
||||
{
|
||||
m_currentIndex = _index;
|
||||
if (m_nextFreeIndex <= _index)
|
||||
m_nextFreeIndex = _index + 1;
|
||||
}
|
||||
|
@ -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<unsigned> m_nextFreeIndex;
|
||||
unsigned m_nextFreeIndex;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -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<SortSort>(smtSort(*_type))) {}
|
||||
explicit Expression(frontend::TypePointer _type): Expression(_type->toString(true), {}, std::make_shared<SortSort>(smtSort(*_type))) {}
|
||||
explicit Expression(std::shared_ptr<SortSort> _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<TupleSort>(sort);
|
||||
solAssert(tupleSort, "");
|
||||
return arguments.size() == tupleSort->components.size();
|
||||
}
|
||||
|
||||
static std::map<std::string, unsigned> 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> arraySort = std::dynamic_pointer_cast<ArraySort>(_array.sort);
|
||||
auto arraySort = std::dynamic_pointer_cast<ArraySort>(_array.sort);
|
||||
solAssert(arraySort, "");
|
||||
solAssert(_index.sort, "");
|
||||
solAssert(_element.sort, "");
|
||||
@ -180,6 +188,20 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
static Expression tuple_constructor(Expression _tuple, std::vector<Expression> _arguments)
|
||||
{
|
||||
solAssert(_tuple.sort->kind == Kind::Sort, "");
|
||||
auto sortSort = std::dynamic_pointer_cast<SortSort>(_tuple.sort);
|
||||
auto tupleSort = std::dynamic_pointer_cast<TupleSort>(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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -55,17 +56,18 @@ SortPointer smtSort(frontend::Type const& _type)
|
||||
}
|
||||
case Kind::Array:
|
||||
{
|
||||
shared_ptr<ArraySort> array;
|
||||
if (isMapping(_type.category()))
|
||||
{
|
||||
auto mapType = dynamic_cast<frontend::MappingType const*>(&_type);
|
||||
solAssert(mapType, "");
|
||||
return make_shared<ArraySort>(smtSortAbstractFunction(*mapType->keyType()), smtSortAbstractFunction(*mapType->valueType()));
|
||||
array = make_shared<ArraySort>(smtSortAbstractFunction(*mapType->keyType()), smtSortAbstractFunction(*mapType->valueType()));
|
||||
}
|
||||
else if (isStringLiteral(_type.category()))
|
||||
{
|
||||
auto stringLitType = dynamic_cast<frontend::StringLiteralType const*>(&_type);
|
||||
solAssert(stringLitType, "");
|
||||
return make_shared<ArraySort>(SortProvider::intSort, SortProvider::intSort);
|
||||
array = make_shared<ArraySort>(SortProvider::intSort, SortProvider::intSort);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -78,8 +80,25 @@ SortPointer smtSort(frontend::Type const& _type)
|
||||
solAssert(false, "");
|
||||
|
||||
solAssert(arrayType, "");
|
||||
return make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
|
||||
array = make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
|
||||
}
|
||||
|
||||
string tupleName;
|
||||
if (
|
||||
auto arrayType = dynamic_cast<ArrayType const*>(&_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<TupleSort>(
|
||||
tupleName,
|
||||
vector<string>{tupleName + "_accessor_array", tupleName + "_accessor_length"},
|
||||
vector<SortPointer>{array, SortProvider::intSort}
|
||||
);
|
||||
}
|
||||
case Kind::Tuple:
|
||||
{
|
||||
@ -219,9 +238,7 @@ pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
else
|
||||
var = make_shared<SymbolicIntVariable>(type, type, _uniqueName, _context);
|
||||
}
|
||||
else if (isMapping(_type.category()))
|
||||
var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _context);
|
||||
else if (isArray(_type.category()))
|
||||
else if (isMapping(_type.category()) || isArray(_type.category()))
|
||||
var = make_shared<SymbolicArrayVariable>(type, type, _uniqueName, _context);
|
||||
else if (isTuple(_type.category()))
|
||||
var = make_shared<SymbolicTupleVariable>(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<TupleSort>(smtSort(*_type));
|
||||
solAssert(tupleSort, "");
|
||||
auto sortSort = make_shared<SortSort>(tupleSort->components.front());
|
||||
|
||||
std::optional<Expression> zeroArray;
|
||||
auto length = bigint(0);
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(_type))
|
||||
return Expression::const_array(Expression(arrayType), zeroValue(arrayType->baseType()));
|
||||
auto mappingType = dynamic_cast<MappingType const*>(_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<MappingType const*>(_type))
|
||||
zeroArray = Expression::const_array(Expression(sortSort), zeroValue(mappingType->valueType()));
|
||||
else
|
||||
solAssert(false, "");
|
||||
|
||||
solAssert(zeroArray, "");
|
||||
return Expression::tuple_constructor(Expression(_type), vector<Expression>{*zeroArray, length});
|
||||
|
||||
}
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
@ -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<smt::Expression> 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<TupleSort>(
|
||||
"array_length_pair",
|
||||
std::vector<std::string>{"array", "length"},
|
||||
std::vector<SortPointer>{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<smt::Expression> 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);
|
||||
}
|
||||
|
@ -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<Expression> /*_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<Expression> _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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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, "");
|
||||
}
|
||||
|
@ -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<Json::Value>(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<Json::Value>(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<Json::Value>(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<Json::Value>(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<string>(createMetadata(_contract));
|
||||
|
||||
return *_contract.metadata;
|
||||
return _contract.metadata.init([&]{ return createMetadata(_contract); });
|
||||
}
|
||||
|
||||
Scanner const& CompilerStack::scanner(string const& _sourceName) const
|
||||
|
@ -37,6 +37,7 @@
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/FixedHash.h>
|
||||
#include <libsolutil/LazyInit.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <json/json.h>
|
||||
@ -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<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
|
||||
mutable std::unique_ptr<Json::Value const> abi;
|
||||
mutable std::unique_ptr<Json::Value const> storageLayout;
|
||||
mutable std::unique_ptr<Json::Value const> userDocumentation;
|
||||
mutable std::unique_ptr<Json::Value const> devDocumentation;
|
||||
util::LazyInit<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
|
||||
util::LazyInit<Json::Value const> abi;
|
||||
util::LazyInit<Json::Value const> storageLayout;
|
||||
util::LazyInit<Json::Value const> userDocumentation;
|
||||
util::LazyInit<Json::Value const> devDocumentation;
|
||||
mutable std::unique_ptr<std::string const> sourceMapping;
|
||||
mutable std::unique_ptr<std::string const> runtimeSourceMapping;
|
||||
};
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libsolutil/JSON.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
@ -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();
|
||||
|
||||
for (string const& objectKind: vector<string>{"bytecode", "deployedBytecode"})
|
||||
{
|
||||
auto artifacts = util::applyMap(
|
||||
vector<string>{"", ".object", ".opcodes", ".sourceMap", ".linkReferences"},
|
||||
[&](auto const& _s) { return "evm." + objectKind + _s; }
|
||||
);
|
||||
if (isArtifactRequested(
|
||||
_inputsAndSettings.outputSelection,
|
||||
sourceName,
|
||||
contractName,
|
||||
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
|
||||
artifacts,
|
||||
wildcardMatchesExperimental
|
||||
))
|
||||
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get(), false);
|
||||
{
|
||||
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();
|
||||
|
@ -19,6 +19,7 @@ set(sources
|
||||
JSON.h
|
||||
Keccak256.cpp
|
||||
Keccak256.h
|
||||
LazyInit.h
|
||||
picosha2.h
|
||||
Result.h
|
||||
StringUtils.cpp
|
||||
|
94
libsolutil/LazyInit.h
Normal file
94
libsolutil/LazyInit.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <libsolutil/Exceptions.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
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<typename T>
|
||||
class LazyInit
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
static_assert(std::is_object_v<value_type>, "Function, reference, and void types are not supported");
|
||||
static_assert(!std::is_array_v<value_type>, "Array types are not supported.");
|
||||
static_assert(!std::is_volatile_v<value_type>, "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<typename F>
|
||||
value_type& init(F&& _fun)
|
||||
{
|
||||
doInit(std::forward<F>(_fun));
|
||||
return m_value.value();
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
value_type const& init(F&& _fun) const
|
||||
{
|
||||
doInit(std::forward<F>(_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<typename F>
|
||||
void doInit(F&& _fun) const
|
||||
{
|
||||
if (!m_value.has_value())
|
||||
m_value.emplace(std::forward<F>(_fun)());
|
||||
}
|
||||
|
||||
mutable std::optional<value_type> m_value;
|
||||
};
|
||||
|
||||
}
|
@ -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<YulString> AsmAnalyzer::operator()(Literal const& _literal)
|
||||
vector<YulString> 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<YulString> 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;
|
||||
}
|
||||
}
|
||||
|
||||
return {type};
|
||||
@ -155,8 +149,9 @@ vector<YulString> AsmAnalyzer::operator()(Identifier const& _identifier)
|
||||
|
||||
void AsmAnalyzer::operator()(ExpressionStatement const& _statement)
|
||||
{
|
||||
auto watcher = m_errorReporter.errorWatcher();
|
||||
vector<YulString> 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<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
{
|
||||
yulAssert(!_funCall.functionName.name.empty(), "");
|
||||
auto watcher = m_errorReporter.errorWatcher();
|
||||
vector<YulString> const* parameterTypes = nullptr;
|
||||
vector<YulString> const* returnTypes = nullptr;
|
||||
vector<bool> const* needsLiteralArguments = nullptr;
|
||||
@ -281,7 +277,7 @@ vector<YulString> 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<YulString> 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.");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<evmasm::LinkerObject>(assembly.assemble());
|
||||
yulAssert(object.bytecode->immutableReferences.empty(), "Leftover immutables.");
|
||||
object.assembly = assembly.assemblyString();
|
||||
object.sourceMappings = make_unique<string>(
|
||||
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<MachineAssemblyObject, MachineAssemblyObject> 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<evmasm::LinkerObject>(assembly.assemble());
|
||||
yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
|
||||
creationObject.assembly = assembly.assemblyString();
|
||||
creationObject.sourceMappings = make_unique<string>(
|
||||
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<evmasm::LinkerObject>(runtimeAssembly.assemble());
|
||||
runtimeObject.assembly = runtimeAssembly.assemblyString();
|
||||
runtimeObject.sourceMappings = make_unique<string>(
|
||||
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, "");
|
||||
|
@ -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<MachineAssemblyObject, MachineAssemblyObject> assembleAndGuessRuntime() const;
|
||||
|
||||
/// @returns the errors generated during parsing, analysis (and potentially assembly).
|
||||
langutil::ErrorList const& errors() const { return m_errors; }
|
||||
|
||||
|
@ -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
|
||||
|
@ -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__":
|
||||
|
@ -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
|
||||
|
@ -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 <http://www.gnu.org/licenses/>
|
||||
#
|
||||
# (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'
|
@ -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
|
||||
|
1
test/cmdlineTests/optimizer_BlockDeDuplicator/args
Normal file
1
test/cmdlineTests/optimizer_BlockDeDuplicator/args
Normal file
@ -0,0 +1 @@
|
||||
--optimize --asm --metadata-hash none
|
11
test/cmdlineTests/optimizer_BlockDeDuplicator/err
Normal file
11
test/cmdlineTests/optimizer_BlockDeDuplicator/err
Normal file
@ -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;}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
9
test/cmdlineTests/optimizer_BlockDeDuplicator/input.sol
Normal file
9
test/cmdlineTests/optimizer_BlockDeDuplicator/input.sol
Normal file
@ -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;
|
||||
}
|
107
test/cmdlineTests/optimizer_BlockDeDuplicator/output
Normal file
107
test/cmdlineTests/optimizer_BlockDeDuplicator/output
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
|
@ -24,9 +24,12 @@
|
||||
#include <test/contracts/ContractInterface.h>
|
||||
#include <test/EVMHost.h>
|
||||
|
||||
#include <libsolutil/LazyInit.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -211,17 +214,18 @@ contract GlobalRegistrar is Registrar, AuctionSystem {
|
||||
}
|
||||
)DELIMITER";
|
||||
|
||||
static unique_ptr<bytes> s_compiledRegistrar;
|
||||
static LazyInit<bytes> s_compiledRegistrar;
|
||||
|
||||
class AuctionRegistrarTestFramework: public SolidityExecutionFramework
|
||||
{
|
||||
protected:
|
||||
void deployRegistrar()
|
||||
{
|
||||
if (!s_compiledRegistrar)
|
||||
s_compiledRegistrar = make_unique<bytes>(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());
|
||||
}
|
||||
|
@ -20,8 +20,11 @@
|
||||
* Tests for a fixed fee registrar contract.
|
||||
*/
|
||||
|
||||
#include <libsolutil/LazyInit.h>
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <optional>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
@ -122,17 +125,18 @@ contract FixedFeeRegistrar is Registrar {
|
||||
}
|
||||
)DELIMITER";
|
||||
|
||||
static unique_ptr<bytes> s_compiledRegistrar;
|
||||
static LazyInit<bytes> s_compiledRegistrar;
|
||||
|
||||
class RegistrarTestFramework: public SolidityExecutionFramework
|
||||
{
|
||||
protected:
|
||||
void deployRegistrar()
|
||||
{
|
||||
if (!s_compiledRegistrar)
|
||||
s_compiledRegistrar = make_unique<bytes>(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());
|
||||
}
|
||||
|
@ -20,8 +20,11 @@
|
||||
* Tests for a (comparatively) complex multisig wallet contract.
|
||||
*/
|
||||
|
||||
#include <libsolutil/LazyInit.h>
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <optional>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
@ -434,7 +437,7 @@ contract Wallet is multisig, multiowned, daylimit {
|
||||
}
|
||||
)DELIMITER";
|
||||
|
||||
static unique_ptr<bytes> s_compiledWallet;
|
||||
static LazyInit<bytes> s_compiledWallet;
|
||||
|
||||
class WalletTestFramework: public SolidityExecutionFramework
|
||||
{
|
||||
@ -446,11 +449,12 @@ protected:
|
||||
u256 _dailyLimit = 0
|
||||
)
|
||||
{
|
||||
if (!s_compiledWallet)
|
||||
s_compiledWallet = make_unique<bytes>(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());
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
[
|
||||
{
|
||||
"absolutePath": "a",
|
||||
"exportedSymbols":
|
||||
@ -218,3 +219,4 @@
|
||||
],
|
||||
"src": "0:160:3"
|
||||
}
|
||||
]
|
||||
|
@ -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);
|
||||
|
@ -12,6 +12,7 @@ contract c {
|
||||
l = data.length;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> 2, 1, 1
|
||||
|
@ -9,5 +9,7 @@ contract c {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> FAILURE
|
||||
|
@ -13,6 +13,7 @@ contract c {
|
||||
if (l != 0x03) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> false
|
||||
|
@ -13,6 +13,7 @@ contract c {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> 0
|
||||
|
@ -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
|
@ -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
|
@ -4,5 +4,7 @@ contract C {
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0x9c1185a5c5e9fc54612808977ee8f548b2258d31000000000000000000000000
|
||||
|
@ -3,5 +3,7 @@ contract C {
|
||||
return sha256("");
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
|
@ -3,6 +3,8 @@ contract test {
|
||||
return ecrecover(h, v, r, s);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// a(bytes32,uint8,bytes32,bytes32):
|
||||
// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c,
|
||||
|
@ -4,6 +4,8 @@ contract test {
|
||||
return ecrecover(h, v, r, s);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// a(bytes32,uint8,bytes32,bytes32):
|
||||
// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c,
|
||||
|
@ -6,5 +6,7 @@ contract C {
|
||||
return ecrecover(bytes32(uint(-1)), 1, bytes32(uint(2)), bytes32(uint(3)));
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0
|
||||
|
@ -11,5 +11,7 @@ contract C {
|
||||
);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0
|
||||
|
@ -16,5 +16,7 @@ contract C {
|
||||
return ecrecover(hash, v, r, s);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
mapping (uint => uint[]) map;
|
||||
function f() public view {
|
||||
assert(map[0].length == map[1].length);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
uint[] arr;
|
||||
uint[] arr2;
|
||||
function f() public {
|
||||
arr2 = arr;
|
||||
assert(arr2.length == arr.length);
|
||||
}
|
||||
}
|
@ -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
|
@ -0,0 +1,9 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
uint[] arr;
|
||||
function f(uint[] memory marr) public {
|
||||
arr = marr;
|
||||
assert(marr.length == arr.length);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
uint[] arr;
|
||||
function f() public view {
|
||||
uint[] memory marr = arr;
|
||||
assert(marr.length == arr.length);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -0,0 +1,9 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
uint[] a;
|
||||
function f() public {
|
||||
a.push();
|
||||
a.pop();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
uint[] a;
|
||||
function f() public {
|
||||
a.pop();
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning: (82-89): Empty array "pop" detected here.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user