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
130 changed files with 2140 additions and 491 deletions
+42 -13
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
+66
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
@@ -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 ;
+1 -1
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
+5 -1
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)
+2 -1
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)
+11
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.
+1
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);
+4
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;
+22
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,
+2
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
+9 -1
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())
+14 -12
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
+3 -2
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
+18 -13
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
+4 -1
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");
+143 -25
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();
});
}
+8
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;
+86
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;
}
+47
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);
};
}
+2 -34
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);
+1 -10
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:
+10 -15
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(
-2
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();
@@ -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();
+4 -9
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
+88 -10
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()
+6 -1
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.
+9
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, "");
}
+118 -8
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)
+8 -1
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;
};
+15 -6
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");
+8 -1
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;
}
+5 -2
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;
};
}
+25 -3
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);
+5 -3
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);
}
+42 -10
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, "");
}
+71 -40
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);
}
+36 -36
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;
};
}
+9
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, "");
}
+5 -25
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
+6 -5
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;
};
+23 -9
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();
+1
View File
@@ -19,6 +19,7 @@ set(sources
JSON.h
Keccak256.cpp
Keccak256.h
LazyInit.h
picosha2.h
Result.h
StringUtils.cpp
+94
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;
};
}
+18 -45
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;
}
-2
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
+41 -16
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, "");
+6
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; }
+1 -3
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
+1 -1
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__":
+1 -30
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
-49
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'
+1
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
@@ -0,0 +1 @@
--optimize --asm --metadata-hash none
@@ -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;}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -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;
}
@@ -0,0 +1,107 @@
======= optimizer_BlockDeDuplicator/input.sol:C =======
EVM assembly:
/* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */
mstore(0x40, 0x80)
/* "optimizer_BlockDeDuplicator/input.sol":179:210 function() r = true ? fun_x : f */
0x00
dup1
sload
not(sub(shl(0x40, 0x01), 0x01))
and
/* "optimizer_BlockDeDuplicator/input.sol":201:206 fun_x */
or(tag_0_7, shl(0x20, tag_4))
sub(shl(0x40, 0x01), 0x01)
/* "optimizer_BlockDeDuplicator/input.sol":179:210 function() r = true ? fun_x : f */
and
or
swap1
sstore
/* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */
callvalue
/* "--CODEGEN--":2:4 */
dup1
iszero
tag_5
jumpi
/* "--CODEGEN--":27:28 */
0x00
/* "--CODEGEN--":24:25 */
dup1
/* "--CODEGEN--":17:29 */
revert
/* "--CODEGEN--":2:4 */
tag_5:
/* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */
pop
jump(tag_6)
/* "optimizer_BlockDeDuplicator/input.sol":77:103 function fun_x() public {} */
tag_4:
jump // out
/* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */
tag_6:
dataSize(sub_0)
dup1
dataOffset(sub_0)
0x00
codecopy
0x00
return
stop
sub_0: assembly {
/* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */
mstore(0x40, 0x80)
callvalue
/* "--CODEGEN--":5:14 */
dup1
/* "--CODEGEN--":2:4 */
iszero
tag_1
jumpi
/* "--CODEGEN--":27:28 */
0x00
/* "--CODEGEN--":24:25 */
dup1
/* "--CODEGEN--":17:29 */
revert
/* "--CODEGEN--":2:4 */
tag_1:
/* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */
pop
jumpi(tag_2, lt(calldatasize, 0x04))
shr(0xe0, calldataload(0x00))
dup1
0x26121ff0
eq
tag_3
jumpi
dup1
0x2e1fb2bc
eq
tag_3
jumpi
dup1
0x4753a67d
eq
tag_3
jumpi
tag_2:
/* "--CODEGEN--":12:13 */
0x00
/* "--CODEGEN--":9:10 */
dup1
/* "--CODEGEN--":2:14 */
revert
/* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */
tag_3:
tag_6
tag_7
jump // in
tag_6:
stop
tag_7:
jump // out
auxdata: AUXDATA REMOVED
}
@@ -22,7 +22,7 @@ sub_0: assembly {
/* \"A\":137:149 */
revert
}
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" {
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"},"deployedBytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" {
code {
let x := dataoffset(\"DataName\")
sstore(add(x, 0), 0)
+8 -4
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());
}
+8 -4
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());
}
+8 -4
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());
}
@@ -1,3 +1,4 @@
[
{
"absolutePath": "a",
"exportedSymbols":
@@ -218,3 +219,4 @@
],
"src": "0:160:3"
}
]
+6
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);
@@ -12,6 +12,7 @@ contract c {
l = data.length;
}
}
// ====
// compileViaYul: also
// ----
// test() -> 2, 1, 1
@@ -9,5 +9,7 @@ contract c {
return true;
}
}
// ====
// compileViaYul: also
// ----
// test() -> FAILURE
@@ -13,6 +13,7 @@ contract c {
if (l != 0x03) return true;
}
}
// ====
// compileViaYul: also
// ----
// test() -> false
@@ -13,6 +13,7 @@ contract c {
return 0;
}
}
// ====
// compileViaYul: also
// ----
// test() -> 0
@@ -0,0 +1,45 @@
contract c {
bytes data;
function test_short() public returns (uint256 r) {
assembly {
sstore(data_slot, 0)
}
for (uint8 i = 0; i < 15; i++) {
data.push(bytes1(i));
}
assembly {
r := sload(data_slot)
}
}
function test_long() public returns (uint256 r) {
assembly {
sstore(data_slot, 0)
}
for (uint8 i = 0; i < 33; i++) {
data.push(bytes1(i));
}
assembly {
r := sload(data_slot)
}
}
function test_pop() public returns (uint256 r) {
assembly {
sstore(data_slot, 0)
}
for (uint8 i = 0; i < 32; i++) {
data.push(bytes1(i));
}
data.pop();
assembly {
r := sload(data_slot)
}
}
}
// ====
// compileViaYul: also
// ----
// test_short() -> 1780731860627700044960722568376587075150542249149356309979516913770823710
// test_long() -> 67
// test_pop() -> 1780731860627700044960722568376592200742329637303199754547598369979440702
@@ -0,0 +1,21 @@
// Tests transition between short and long encoding both ways
contract c {
bytes data;
function test() public returns (uint256) {
for (uint8 i = 0; i < 33; i++) {
data.push(bytes1(i));
}
for (uint8 i = 0; i < data.length; i++)
if (data[i] != bytes1(i)) return i;
data.pop();
data.pop();
for (uint8 i = 0; i < data.length; i++)
if (data[i] != bytes1(i)) return i;
return 0;
}
}
// ====
// compileViaYul: also
// ----
// test() -> 0
@@ -4,5 +4,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x9c1185a5c5e9fc54612808977ee8f548b2258d31000000000000000000000000
@@ -3,5 +3,7 @@ contract C {
return sha256("");
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
@@ -3,6 +3,8 @@ contract test {
return ecrecover(h, v, r, s);
}
}
// ====
// compileViaYul: also
// ----
// a(bytes32,uint8,bytes32,bytes32):
// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c,
@@ -4,6 +4,8 @@ contract test {
return ecrecover(h, v, r, s);
}
}
// ====
// compileViaYul: also
// ----
// a(bytes32,uint8,bytes32,bytes32):
// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c,
@@ -6,5 +6,7 @@ contract C {
return ecrecover(bytes32(uint(-1)), 1, bytes32(uint(2)), bytes32(uint(3)));
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0
@@ -11,5 +11,7 @@ contract C {
);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0
@@ -16,5 +16,7 @@ contract C {
return ecrecover(hash, v, r, s);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0
@@ -0,0 +1,10 @@
pragma experimental SMTChecker;
pragma experimental ABIEncoderV2;
contract C {
function f(uint[][] memory arr) public pure {
uint[][] memory arr2 = arr;
assert(arr2[0].length == arr[0].length);
assert(arr.length == arr2.length);
}
}
@@ -0,0 +1,11 @@
pragma experimental SMTChecker;
pragma experimental ABIEncoderV2;
contract C {
uint[][] arr;
uint[][] arr2;
function f() public view {
assert(arr2[0].length == arr[0].length);
assert(arr2.length == arr.length);
}
}
@@ -0,0 +1,11 @@
pragma experimental SMTChecker;
pragma experimental ABIEncoderV2;
contract C {
uint[][] arr;
function f(uint[][] memory arr2) public {
arr = arr2;
assert(arr2[0].length == arr[0].length);
assert(arr2.length == arr.length);
}
}
@@ -0,0 +1,11 @@
pragma experimental SMTChecker;
pragma experimental ABIEncoderV2;
contract C {
uint[][] arr;
function f() public view {
uint[][] memory arr2 = arr;
assert(arr2[0].length == arr[0].length);
assert(arr2.length == arr.length);
}
}
@@ -0,0 +1,8 @@
pragma experimental SMTChecker;
contract C {
mapping (uint => uint[]) map;
function f() public view {
assert(map[0].length == map[1].length);
}
}
@@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
mapping (uint => uint[]) map;
function f(uint x, uint y) public view {
require(x == y);
assert(map[x].length == map[y].length);
}
}
@@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
mapping (uint => uint[][]) map;
function f(uint x, uint y) public view {
require(x == y);
assert(map[x][0].length == map[y][0].length);
}
}
@@ -0,0 +1,20 @@
pragma experimental SMTChecker;
contract C {
struct S {
uint[] arr;
}
S s1;
S s2;
function f() public view {
assert(s1.arr.length == s2.arr.length);
}
}
// ----
// Warning: (76-80): Assertion checker does not yet support the type of this variable.
// Warning: (83-87): Assertion checker does not yet support the type of this variable.
// Warning: (126-132): Assertion checker does not yet support this expression.
// Warning: (126-128): Assertion checker does not yet implement type struct C.S storage ref
// Warning: (143-149): Assertion checker does not yet support this expression.
// Warning: (143-145): Assertion checker does not yet implement type struct C.S storage ref
// Warning: (119-157): Assertion violation happens here
@@ -0,0 +1,22 @@
pragma experimental SMTChecker;
contract C {
struct S {
uint[][] arr;
}
S s1;
S s2;
function f() public view {
assert(s1.arr[0].length == s2.arr[0].length);
}
}
// ----
// Warning: (78-82): Assertion checker does not yet support the type of this variable.
// Warning: (85-89): Assertion checker does not yet support the type of this variable.
// Warning: (128-134): Assertion checker does not yet support this expression.
// Warning: (128-130): Assertion checker does not yet implement type struct C.S storage ref
// Warning: (128-137): Assertion checker does not yet implement this expression.
// Warning: (148-154): Assertion checker does not yet support this expression.
// Warning: (148-150): Assertion checker does not yet implement type struct C.S storage ref
// Warning: (148-157): Assertion checker does not yet implement this expression.
// Warning: (121-165): Assertion violation happens here
@@ -0,0 +1,9 @@
pragma experimental SMTChecker;
pragma experimental ABIEncoderV2;
contract C {
function f(uint[][] memory arr) public pure {
uint[][] memory arr2 = arr;
assert(arr2.length == arr.length);
}
}
@@ -0,0 +1,8 @@
pragma experimental SMTChecker;
contract C {
function f(uint[] memory arr) public pure {
uint[] memory arr2 = arr;
assert(arr2.length == arr.length);
}
}
@@ -0,0 +1,10 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
uint[] arr2;
function f() public {
arr2 = arr;
assert(arr2.length == arr.length);
}
}
@@ -0,0 +1,13 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
function f() public view {
uint x = arr.length;
uint y = x;
assert(arr.length == y);
assert(arr.length != y);
}
}
// ----
// Warning: (153-176): Assertion violation happens here
@@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
function f(uint[] memory marr) public {
arr = marr;
assert(marr.length == arr.length);
}
}
@@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
function f() public view {
uint[] memory marr = arr;
assert(marr.length == arr.length);
}
}
@@ -0,0 +1,10 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
function f() public view {
assert(arr.length == g().length);
}
function g() internal pure returns (uint[] memory) {
}
}
@@ -0,0 +1,10 @@
pragma experimental SMTChecker;
contract C {
uint[] arr;
function f() public view {
uint[] memory arr2 = arr;
arr2[2] = 3;
assert(arr.length == arr2.length);
}
}
@@ -0,0 +1,15 @@
pragma experimental SMTChecker;
contract C {
uint[][] arr;
uint[][] arr2;
function f() public {
uint x = arr[2].length;
uint y = arr[3].length;
uint z = arr.length;
arr[2][333] = 444;
assert(arr[2].length == x);
assert(arr[3].length == y);
assert(arr.length == z);
}
}
@@ -0,0 +1,19 @@
pragma experimental SMTChecker;
contract C {
uint[][] arr;
uint[][] arr2;
function f() public {
uint x = arr[2].length;
uint y = arr[3].length;
uint z = arr.length;
arr[2][333] = 444;
assert(arr[2].length != x);
assert(arr[3].length != y);
assert(arr.length != z);
}
}
// ----
// Warning: (198-224): Assertion violation happens here
// Warning: (228-254): Assertion violation happens here
// Warning: (258-281): Assertion violation happens here
@@ -0,0 +1,17 @@
pragma experimental SMTChecker;
contract C {
uint[][] arr;
uint[][] arr2;
function f() public {
uint x = arr[2].length;
uint y = arr[3].length;
uint z = arr.length;
uint t = arr[5].length;
arr[5] = arr[8];
assert(arr[2].length == x);
assert(arr[3].length == y);
assert(arr.length == z);
assert(arr[5].length == t);
}
}
@@ -0,0 +1,22 @@
pragma experimental SMTChecker;
contract C {
uint[][] arr;
uint[][] arr2;
function f() public {
uint x = arr[2].length;
uint y = arr[3].length;
uint z = arr.length;
uint t = arr[5].length;
arr[5] = arr[8];
assert(arr[2].length != x);
assert(arr[3].length != y);
assert(arr.length != z);
assert(arr[5].length != t);
}
}
// ----
// Warning: (222-248): Assertion violation happens here
// Warning: (252-278): Assertion violation happens here
// Warning: (282-305): Assertion violation happens here
// Warning: (309-335): Assertion violation happens here
@@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
uint[] a;
function f() public {
a.push();
a.pop();
}
}
@@ -0,0 +1,10 @@
pragma experimental SMTChecker;
contract C {
uint[] a;
function f() public {
a.pop();
}
}
// ----
// Warning: (82-89): Empty array "pop" detected here.

Some files were not shown because too many files have changed in this diff Show More