Merge pull request #8931 from random-internet-cat/lazy-init

Add LazyInit
This commit is contained in:
chriseth 2020-05-14 18:54:19 +02:00 committed by GitHub
commit 2d1e7d9504
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 288 additions and 70 deletions

View File

@ -153,10 +153,10 @@ FunctionDefinition const* ContractDefinition::receiveFunction() const
vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() const
{
if (!m_interfaceEvents)
{
return m_interfaceEvents.init([&]{
set<string> eventsSeen;
m_interfaceEvents = make_unique<vector<EventDefinition const*>>();
vector<EventDefinition const*> interfaceEvents;
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
for (EventDefinition const* e: contract->events())
{
@ -169,19 +169,20 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
if (eventsSeen.count(eventSignature) == 0)
{
eventsSeen.insert(eventSignature);
m_interfaceEvents->push_back(e);
interfaceEvents.push_back(e);
}
}
}
return *m_interfaceEvents;
return interfaceEvents;
});
}
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
{
if (!m_interfaceFunctionList[_includeInheritedFunctions])
{
return m_interfaceFunctionList[_includeInheritedFunctions].init([&]{
set<string> signaturesSeen;
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
vector<pair<util::FixedHash<4>, FunctionTypePointer>> interfaceFunctionList;
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
{
if (_includeInheritedFunctions == false && contract != this)
@ -203,12 +204,13 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
{
signaturesSeen.insert(functionSignature);
util::FixedHash<4> hash(util::keccak256(functionSignature));
m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun);
interfaceFunctionList.emplace_back(hash, fun);
}
}
}
}
return *m_interfaceFunctionList[_includeInheritedFunctions];
return interfaceFunctionList;
});
}
TypePointer ContractDefinition::type() const

View File

@ -31,6 +31,7 @@
#include <liblangutil/SourceLocation.h>
#include <libevmasm/Instruction.h>
#include <libsolutil/FixedHash.h>
#include <libsolutil/LazyInit.h>
#include <boost/noncopyable.hpp>
#include <json/json.h>
@ -536,8 +537,8 @@ private:
ContractKind m_contractKind;
bool m_abstract{false};
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
util::LazyInit<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
util::LazyInit<std::vector<EventDefinition const*>> m_interfaceEvents;
};
class InheritanceSpecifier: public ASTNode

View File

@ -207,26 +207,31 @@ void MemberList::combine(MemberList const & _other)
pair<u256, unsigned> const* MemberList::memberStorageOffset(string const& _name) const
{
if (!m_storageOffsets)
{
TypePointers memberTypes;
memberTypes.reserve(m_memberTypes.size());
for (auto const& member: m_memberTypes)
memberTypes.push_back(member.type);
m_storageOffsets = std::make_unique<StorageOffsets>();
m_storageOffsets->computeOffsets(memberTypes);
}
StorageOffsets const& offsets = storageOffsets();
for (size_t index = 0; index < m_memberTypes.size(); ++index)
if (m_memberTypes[index].name == _name)
return m_storageOffsets->offset(index);
return offsets.offset(index);
return nullptr;
}
u256 const& MemberList::storageSize() const
{
// trigger lazy computation
memberStorageOffset("");
return m_storageOffsets->storageSize();
return storageOffsets().storageSize();
}
StorageOffsets const& MemberList::storageOffsets() const {
return m_storageOffsets.init([&]{
TypePointers memberTypes;
memberTypes.reserve(m_memberTypes.size());
for (auto const& member: m_memberTypes)
memberTypes.push_back(member.type);
StorageOffsets storageOffsets;
storageOffsets.computeOffsets(memberTypes);
return storageOffsets;
});
}
/// Helper functions for type identifier

View File

@ -29,6 +29,7 @@
#include <libsolutil/Common.h>
#include <libsolutil/CommonIO.h>
#include <libsolutil/LazyInit.h>
#include <libsolutil/Result.h>
#include <boost/rational.hpp>
@ -139,8 +140,10 @@ public:
MemberMap::const_iterator end() const { return m_memberTypes.end(); }
private:
StorageOffsets const& storageOffsets() const;
MemberMap m_memberTypes;
mutable std::unique_ptr<StorageOffsets> m_storageOffsets;
util::LazyInit<StorageOffsets> m_storageOffsets;
};
static_assert(std::is_nothrow_move_constructible<MemberList>::value, "MemberList should be noexcept move constructible");

View File

@ -733,11 +733,7 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
solAssert(_contract.contract, "");
// caches the result
if (!_contract.abi)
_contract.abi = make_unique<Json::Value>(ABI::generate(*_contract.contract));
return *_contract.abi;
return _contract.abi.init([&]{ return ABI::generate(*_contract.contract); });
}
Json::Value const& CompilerStack::storageLayout(string const& _contractName) const
@ -755,11 +751,7 @@ Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const
solAssert(_contract.contract, "");
// caches the result
if (!_contract.storageLayout)
_contract.storageLayout = make_unique<Json::Value>(StorageLayout().generate(*_contract.contract));
return *_contract.storageLayout;
return _contract.storageLayout.init([&]{ return StorageLayout().generate(*_contract.contract); });
}
Json::Value const& CompilerStack::natspecUser(string const& _contractName) const
@ -777,11 +769,7 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const
solAssert(_contract.contract, "");
// caches the result
if (!_contract.userDocumentation)
_contract.userDocumentation = make_unique<Json::Value>(Natspec::userDocumentation(*_contract.contract));
return *_contract.userDocumentation;
return _contract.userDocumentation.init([&]{ return Natspec::userDocumentation(*_contract.contract); });
}
Json::Value const& CompilerStack::natspecDev(string const& _contractName) const
@ -799,11 +787,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
solAssert(_contract.contract, "");
// caches the result
if (!_contract.devDocumentation)
_contract.devDocumentation = make_unique<Json::Value>(Natspec::devDocumentation(*_contract.contract));
return *_contract.devDocumentation;
return _contract.devDocumentation.init([&]{ return Natspec::devDocumentation(*_contract.contract); });
}
Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
@ -832,11 +816,7 @@ string const& CompilerStack::metadata(Contract const& _contract) const
solAssert(_contract.contract, "");
// cache the result
if (!_contract.metadata)
_contract.metadata = make_unique<string>(createMetadata(_contract));
return *_contract.metadata;
return _contract.metadata.init([&]{ return createMetadata(_contract); });
}
Scanner const& CompilerStack::scanner(string const& _sourceName) const

View File

@ -37,6 +37,7 @@
#include <libsolutil/Common.h>
#include <libsolutil/FixedHash.h>
#include <libsolutil/LazyInit.h>
#include <boost/noncopyable.hpp>
#include <json/json.h>
@ -342,11 +343,11 @@ private:
std::string yulIROptimized; ///< Optimized experimental Yul IR code.
std::string ewasm; ///< Experimental Ewasm text representation
evmasm::LinkerObject ewasmObject; ///< Experimental Ewasm code
mutable std::unique_ptr<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
mutable std::unique_ptr<Json::Value const> abi;
mutable std::unique_ptr<Json::Value const> storageLayout;
mutable std::unique_ptr<Json::Value const> userDocumentation;
mutable std::unique_ptr<Json::Value const> devDocumentation;
util::LazyInit<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
util::LazyInit<Json::Value const> abi;
util::LazyInit<Json::Value const> storageLayout;
util::LazyInit<Json::Value const> userDocumentation;
util::LazyInit<Json::Value const> devDocumentation;
mutable std::unique_ptr<std::string const> sourceMapping;
mutable std::unique_ptr<std::string const> runtimeSourceMapping;
};

View File

@ -19,6 +19,7 @@ set(sources
JSON.h
Keccak256.cpp
Keccak256.h
LazyInit.h
picosha2.h
Result.h
StringUtils.cpp

94
libsolutil/LazyInit.h Normal file
View File

@ -0,0 +1,94 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolutil/Assertions.h>
#include <libsolutil/Exceptions.h>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
namespace solidity::util
{
DEV_SIMPLE_EXCEPTION(BadLazyInitAccess);
/**
* A value that is initialized at some point after construction of the LazyInit. The stored value can only be accessed
* while calling "init", which initializes the stored value (if it has not already been initialized).
*
* @tparam T the type of the stored value; may not be a function, reference, array, or void type; may be const-qualified.
*/
template<typename T>
class LazyInit
{
public:
using value_type = T;
static_assert(std::is_object_v<value_type>, "Function, reference, and void types are not supported");
static_assert(!std::is_array_v<value_type>, "Array types are not supported.");
static_assert(!std::is_volatile_v<value_type>, "Volatile-qualified types are not supported.");
LazyInit() = default;
LazyInit(LazyInit const&) = delete;
LazyInit& operator=(LazyInit const&) = delete;
// Move constructor must be overridden to ensure that moved-from object is left empty.
constexpr LazyInit(LazyInit&& _other) noexcept:
m_value(std::move(_other.m_value))
{
_other.m_value.reset();
}
LazyInit& operator=(LazyInit&& _other) noexcept
{
this->m_value.swap(_other.m_value);
_other.m_value.reset();
}
template<typename F>
value_type& init(F&& _fun)
{
doInit(std::forward<F>(_fun));
return m_value.value();
}
template<typename F>
value_type const& init(F&& _fun) const
{
doInit(std::forward<F>(_fun));
return m_value.value();
}
private:
/// Although not quite logically const, this is marked const for pragmatic reasons. It doesn't change the platonic
/// value of the object (which is something that is initialized to some computed value on first use).
template<typename F>
void doInit(F&& _fun) const
{
if (!m_value.has_value())
m_value.emplace(std::forward<F>(_fun)());
}
mutable std::optional<value_type> m_value;
};
}

View File

@ -34,6 +34,7 @@ set(libsolutil_sources
libsolutil/IterateReplacing.cpp
libsolutil/JSON.cpp
libsolutil/Keccak256.cpp
libsolutil/LazyInit.cpp
libsolutil/StringUtils.cpp
libsolutil/SwarmHash.cpp
libsolutil/UTF8.cpp

View File

@ -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;
@ -212,17 +215,18 @@ contract GlobalRegistrar is Registrar, AuctionSystem {
}
)DELIMITER";
static unique_ptr<bytes> s_compiledRegistrar;
static LazyInit<bytes> s_compiledRegistrar;
class AuctionRegistrarTestFramework: public SolidityExecutionFramework
{
protected:
void deployRegistrar()
{
if (!s_compiledRegistrar)
s_compiledRegistrar = make_unique<bytes>(compileContract(registrarCode, "GlobalRegistrar"));
bytes const& compiled = s_compiledRegistrar.init([&]{
return compileContract(registrarCode, "GlobalRegistrar");
});
sendMessage(*s_compiledRegistrar, true);
sendMessage(compiled, true);
BOOST_REQUIRE(m_transactionSuccessful);
BOOST_REQUIRE(!m_output.empty());
}

View File

@ -20,8 +20,11 @@
* Tests for a fixed fee registrar contract.
*/
#include <libsolutil/LazyInit.h>
#include <string>
#include <tuple>
#include <optional>
#if defined(_MSC_VER)
#pragma warning(push)
@ -122,17 +125,18 @@ contract FixedFeeRegistrar is Registrar {
}
)DELIMITER";
static unique_ptr<bytes> s_compiledRegistrar;
static LazyInit<bytes> s_compiledRegistrar;
class RegistrarTestFramework: public SolidityExecutionFramework
{
protected:
void deployRegistrar()
{
if (!s_compiledRegistrar)
s_compiledRegistrar = make_unique<bytes>(compileContract(registrarCode, "FixedFeeRegistrar"));
bytes const& compiled = s_compiledRegistrar.init([&]{
return compileContract(registrarCode, "FixedFeeRegistrar");
});
sendMessage(*s_compiledRegistrar, true);
sendMessage(compiled, true);
BOOST_REQUIRE(m_transactionSuccessful);
BOOST_REQUIRE(!m_output.empty());
}

View File

@ -20,8 +20,11 @@
* Tests for a (comparatively) complex multisig wallet contract.
*/
#include <libsolutil/LazyInit.h>
#include <string>
#include <tuple>
#include <optional>
#if defined(_MSC_VER)
#pragma warning(push)
@ -435,7 +438,7 @@ contract Wallet is multisig, multiowned, daylimit {
}
)DELIMITER";
static unique_ptr<bytes> s_compiledWallet;
static LazyInit<bytes> s_compiledWallet;
class WalletTestFramework: public SolidityExecutionFramework
{
@ -447,11 +450,12 @@ protected:
u256 _dailyLimit = 0
)
{
if (!s_compiledWallet)
s_compiledWallet = make_unique<bytes>(compileContract(walletCode, "Wallet"));
bytes const& compiled = s_compiledWallet.init([&]{
return compileContract(walletCode, "Wallet");
});
bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners);
sendMessage(*s_compiledWallet + args, true, _value);
sendMessage(compiled + args, true, _value);
BOOST_REQUIRE(m_transactionSuccessful);
BOOST_REQUIRE(!m_output.empty());
}

View File

@ -0,0 +1,118 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolutil/LazyInit.h>
#include <boost/test/unit_test.hpp>
#include <utility>
namespace solidity::util::test
{
namespace
{
template<typename T>
void assertInitCalled(LazyInit<T> lazyInit, bool target)
{
bool initCalled = false;
lazyInit.init([&]{
initCalled = true;
return T();
});
BOOST_REQUIRE_EQUAL(initCalled, target);
}
// Take ownership to ensure that it doesn't "mutate"
template<typename T>
void assertNotEmpty(LazyInit<T> _lazyInit) { assertInitCalled(std::move(_lazyInit), false); }
// Take ownership to ensure that it doesn't "mutate"
template<typename T>
void assertEmpty(LazyInit<T> _lazyInit) { assertInitCalled(std::move(_lazyInit), true); }
template<typename T>
T valueOf(LazyInit<T> _lazyInit)
{
return _lazyInit.init([&]{
BOOST_REQUIRE(false); // this should never be called
return T();
});
}
}
BOOST_AUTO_TEST_SUITE(LazyInitTests)
BOOST_AUTO_TEST_CASE(default_constructed_is_empty)
{
assertEmpty(LazyInit<int>());
assertEmpty(LazyInit<int const>());
}
BOOST_AUTO_TEST_CASE(initialized_is_not_empty)
{
LazyInit<int> lazyInit;
lazyInit.init([]{ return 12; });
assertNotEmpty(std::move(lazyInit));
}
BOOST_AUTO_TEST_CASE(init_returns_init_value)
{
LazyInit<int> lazyInit;
BOOST_CHECK_EQUAL(lazyInit.init([]{ return 12; }), 12);
// A second call to init should not change the value
BOOST_CHECK_EQUAL(lazyInit.init([]{ return 42; }), 12);
}
BOOST_AUTO_TEST_CASE(moved_from_is_empty)
{
{
LazyInit<int> lazyInit;
{ [[maybe_unused]] auto pilfered = std::move(lazyInit); }
assertEmpty(std::move(lazyInit));
}
{
LazyInit<int> lazyInit;
lazyInit.init([]{ return 12; });
{ [[maybe_unused]] auto pilfered = std::move(lazyInit); }
assertEmpty(std::move(lazyInit));
}
}
BOOST_AUTO_TEST_CASE(move_constructed_has_same_value_as_original)
{
LazyInit<int> original;
original.init([]{ return 12; });
LazyInit<int> moveConstructed = std::move(original);
BOOST_CHECK_EQUAL(valueOf(std::move(moveConstructed)), 12);
}
BOOST_AUTO_TEST_SUITE_END()
}