Merge pull request #8983 from ethereum/develop

Merge develop into breaking.
This commit is contained in:
chriseth 2020-05-19 18:05:28 +02:00 committed by GitHub
commit d422a406ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
130 changed files with 2140 additions and 491 deletions

View File

@ -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

View 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

View File

@ -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 ;

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -455,7 +455,15 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (contractType->contractDefinition().isLibrary())
m_errorReporter.typeError(1273_error, _variable.location(), "The type of a variable cannot be a library.");
if (_variable.value())
expectType(*_variable.value(), *varType);
{
if (_variable.isStateVariable() && dynamic_cast<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())

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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);
w("byteArray", _type.isByteArray());
if (_type.location() == DataLocation::Storage)
{
w("byteArray", _type.isByteArray());
if (_type.isByteArray())
w("extractByteArrayLength", extractByteArrayLengthFunction());
}
if (_type.isDynamicallySized())
solAssert(
_type.location() != DataLocation::CallData,
@ -689,8 +708,9 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
if (_type.isByteArray())
return storageByteArrayPopFunction(_type);
string functionName = "array_pop_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
@ -699,10 +719,8 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
let oldLen := <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)
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
sstore(array, add(oldLen, 1))
<?isByteArray>
let data := sload(array)
let oldLen := <extractByteArrayLength>(data)
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, value)
switch gt(oldLen, 31)
case 0 {
value := byte(0, value)
switch oldLen
case 31 {
// Here we have special case when array switches from short array to long array
// We need to copy data
let dataArea := <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();
});
}

View File

@ -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;

View 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;
}

View 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);
};
}

View File

@ -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);

View File

@ -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:

View File

@ -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(

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -98,17 +98,45 @@ void CHC::analyze(SourceUnit const& _source)
for (auto const& [scope, target]: m_verificationTargets)
{
auto assertions = transactionAssertions(scope);
for (auto const* assertion: assertions)
if (target.type == VerificationTarget::Type::Assert)
{
auto assertions = transactionAssertions(scope);
for (auto const* assertion: assertions)
{
createErrorBlock();
connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id()));
auto [result, model] = query(error(), assertion->location());
// This should be fine but it's a bug in the old compiler
(void)model;
if (result == smt::CheckResult::UNSATISFIABLE)
m_safeAssertions.insert(assertion);
}
}
else if (target.type == VerificationTarget::Type::PopEmptyArray)
{
solAssert(dynamic_cast<FunctionCall const*>(scope), "");
createErrorBlock();
connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id()));
auto [result, model] = query(error(), assertion->location());
connectBlocks(target.value, error(), target.constraints && (target.errorId == scope->id()));
auto [result, model] = query(error(), scope->location());
// This should be fine but it's a bug in the old compiler
(void)model;
if (result == smt::CheckResult::UNSATISFIABLE)
m_safeAssertions.insert(assertion);
if (result != smt::CheckResult::UNSATISFIABLE)
{
string msg = "Empty array \"pop\" ";
if (result == smt::CheckResult::SATISFIABLE)
msg += "detected here.";
else
msg += "might happen here.";
m_unsafeTargets.insert(scope);
m_outerErrorReporter.warning(
2529_error,
scope->location(),
msg
);
}
}
else
solAssert(false, "");
}
}
@ -161,7 +189,7 @@ void CHC::endVisit(ContractDefinition const& _contract)
auto stateExprs = vector<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()

View File

@ -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.

View File

@ -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, "");
}

View File

@ -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)
{
assignment(_variable, expr(_value, _variable.type()));
// In general, at this point, the SMT sorts of _variable and _value are the same,
// even if there is implicit conversion.
// This is a special case where the SMT sorts are different.
// For now we are unaware of other cases where this happens, but if they do appear
// we should extract this into an `implicitConversion` function.
if (_variable.type()->category() != Type::Category::Array || _value.annotation().type->category() != Type::Category::StringLiteral)
assignment(_variable, expr(_value, _variable.type()));
// TODO else { store each string literal byte into the array }
}
void SMTEncoder::assignment(VariableDeclaration const& _variable, smt::Expression const& _value)
@ -1616,7 +1726,7 @@ SMTEncoder::VariableIndices SMTEncoder::copyVariableIndices()
void SMTEncoder::resetVariableIndices(VariableIndices const& _indices)
{
for (auto const& var: _indices)
m_context.variable(*var.first)->index() = var.second;
m_context.variable(*var.first)->setIndex(var.second);
}
void SMTEncoder::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function)

View File

@ -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;
};

View File

@ -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");

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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, "");
}

View File

@ -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);
}

View File

@ -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;
};
}

View File

@ -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, "");
}

View File

@ -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

View File

@ -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;
};

View File

@ -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();
if (isArtifactRequested(
_inputsAndSettings.outputSelection,
sourceName,
contractName,
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
wildcardMatchesExperimental
))
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get(), false);
for (string const& objectKind: vector<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,
artifacts,
wildcardMatchesExperimental
))
{
MachineAssemblyObject const& o = objectKind == "bytecode" ? object : runtimeObject;
if (o.bytecode)
output["contracts"][sourceName][contractName]["evm"][objectKind] = collectEVMObject(*o.bytecode, o.sourceMappings.get(), false);
}
}
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();

View File

@ -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
View 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;
};
}

View File

@ -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;
}
declarationError(_identifier.location, "Identifier not found.");
}
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.");
}
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;
}

View File

@ -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

View File

@ -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, "");

View File

@ -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; }

View File

@ -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

View File

@ -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__":

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -0,0 +1 @@
--optimize --asm --metadata-hash none

View 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;}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View 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;
}

View 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
}

View File

@ -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)

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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());
}

View File

@ -1,3 +1,4 @@
[
{
"absolutePath": "a",
"exportedSymbols":
@ -218,3 +219,4 @@
],
"src": "0:160:3"
}
]

View File

@ -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);

View File

@ -12,6 +12,7 @@ contract c {
l = data.length;
}
}
// ====
// compileViaYul: also
// ----
// test() -> 2, 1, 1

View File

@ -9,5 +9,7 @@ contract c {
return true;
}
}
// ====
// compileViaYul: also
// ----
// test() -> FAILURE

View File

@ -13,6 +13,7 @@ contract c {
if (l != 0x03) return true;
}
}
// ====
// compileViaYul: also
// ----
// test() -> false

View File

@ -13,6 +13,7 @@ contract c {
return 0;
}
}
// ====
// compileViaYul: also
// ----
// test() -> 0

View File

@ -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

View File

@ -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

View File

@ -4,5 +4,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x9c1185a5c5e9fc54612808977ee8f548b2258d31000000000000000000000000

View File

@ -3,5 +3,7 @@ contract C {
return sha256("");
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

View File

@ -3,6 +3,8 @@ contract test {
return ecrecover(h, v, r, s);
}
}
// ====
// compileViaYul: also
// ----
// a(bytes32,uint8,bytes32,bytes32):
// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c,

View File

@ -4,6 +4,8 @@ contract test {
return ecrecover(h, v, r, s);
}
}
// ====
// compileViaYul: also
// ----
// a(bytes32,uint8,bytes32,bytes32):
// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c,

View File

@ -6,5 +6,7 @@ contract C {
return ecrecover(bytes32(uint(-1)), 1, bytes32(uint(2)), bytes32(uint(3)));
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0

View File

@ -11,5 +11,7 @@ contract C {
);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0

View File

@ -16,5 +16,7 @@ contract C {
return ecrecover(hash, v, r, s);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,8 @@
pragma experimental SMTChecker;
contract C {
mapping (uint => uint[]) map;
function f() public view {
assert(map[0].length == map[1].length);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,10 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
uint[] arr2;
function f() public {
arr2 = arr;
assert(arr2.length == arr.length);
}
}

View File

@ -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

View File

@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
function f(uint[] memory marr) public {
arr = marr;
assert(marr.length == arr.length);
}
}

View File

@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
function f() public view {
uint[] memory marr = arr;
assert(marr.length == arr.length);
}
}

View File

@ -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) {
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
uint[] a;
function f() public {
a.push();
a.pop();
}
}

View File

@ -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