From 6b77a20134010f7e07fb475af8b021ae3b791696 Mon Sep 17 00:00:00 2001 From: a3d4 Date: Wed, 16 Sep 2020 01:06:54 +0200 Subject: [PATCH] Introduce CompositeType --- libsolidity/analysis/ContractLevelChecker.cpp | 3 +- libsolidity/analysis/StaticAnalyzer.cpp | 21 +++-- libsolidity/ast/Types.cpp | 84 +++++-------------- libsolidity/ast/Types.h | 53 ++++++++++-- .../iceRegressionTests/oversized_var.sol | 2 + .../largeTypes/large_storage_structs.sol | 10 ++- .../largeTypes/oversized_array.sol | 2 +- .../largeTypes/oversized_contract.sol | 2 +- .../oversized_contract_inheritance.sol | 2 +- .../largeTypes/oversized_struct.sol | 2 +- .../largeTypes/storage_parameter.sol | 1 + 11 files changed, 97 insertions(+), 85 deletions(-) diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index ff6b30e79ede..e8804d359b57 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -465,7 +465,6 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract) { bigint size = 0; - vector variables; for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (VariableDeclaration const* variable: contract->stateVariables()) if (!(variable->isConstant() || variable->immutable())) @@ -473,7 +472,7 @@ void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract) size += variable->annotation().type->storageSizeUpperBound(); if (size >= bigint(1) << 256) { - m_errorReporter.typeError(7676_error, _contract.location(), "Contract too large for storage."); + m_errorReporter.typeError(7676_error, _contract.location(), "Contract requires too much storage."); break; } } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index fefea474aa2e..c9be0a3d3f10 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -158,17 +158,16 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable) } if (_variable.isStateVariable() || _variable.referenceLocation() == VariableDeclaration::Location::Storage) - { - TypePointer varType = _variable.annotation().type; - for (Type const* subtype: frontend::oversizedSubtypes(*varType)) - { - string message = "Type " + subtype->toString(true) + - " covers a large part of storage and thus makes collisions likely." - " Either use mappings or dynamic arrays and allow their size to be increased only" - " in small quantities per transaction."; - m_errorReporter.warning(7325_error, _variable.typeName().location(), message); - } - } + if (auto varType = dynamic_cast(_variable.annotation().type)) + for (Type const* type: varType->fullDecomposition()) + if (type->storageSizeUpperBound() >= (bigint(1) << 64)) + { + string message = "Type " + type->toString(true) + + " covers a large part of storage and thus makes collisions likely." + " Either use mappings or dynamic arrays and allow their size to be increased only" + " in small quantities per transaction."; + m_errorReporter.warning(7325_error, _variable.typeName().location(), message); + } return true; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index b0418dee5840..af52f63d9d06 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -44,6 +44,7 @@ #include #include +#include #include using namespace std; @@ -54,57 +55,6 @@ using namespace solidity::frontend; namespace { -struct TypeComp -{ - bool operator()(Type const* lhs, Type const* rhs) const - { - solAssert(lhs && rhs, ""); - return lhs->richIdentifier() < rhs->richIdentifier(); - } -}; -using TypeSet = std::set; - -void oversizedSubtypesInner( - Type const& _type, - bool _includeType, - set& _structsSeen, - TypeSet& _oversizedSubtypes -) -{ - switch (_type.category()) - { - case Type::Category::Array: - { - auto const& t = dynamic_cast(_type); - if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64) - _oversizedSubtypes.insert(&t); - oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes); - break; - } - case Type::Category::Struct: - { - auto const& t = dynamic_cast(_type); - if (_structsSeen.count(&t.structDefinition())) - return; - if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64) - _oversizedSubtypes.insert(&t); - _structsSeen.insert(&t.structDefinition()); - for (auto const& m: t.members(nullptr)) - oversizedSubtypesInner(*m.type, false, _structsSeen, _oversizedSubtypes); - _structsSeen.erase(&t.structDefinition()); - break; - } - case Type::Category::Mapping: - { - auto const* valueType = dynamic_cast(_type).valueType(); - oversizedSubtypesInner(*valueType, true, _structsSeen, _oversizedSubtypes); - break; - } - default: - break; - } -} - /// Check whether (_base ** _exp) fits into 4096 bits. bool fitsPrecisionExp(bigint const& _base, bigint const& _exp) { @@ -201,16 +151,6 @@ util::Result transformParametersToExternal(TypePointers const& _pa } -vector solidity::frontend::oversizedSubtypes(frontend::Type const& _type) -{ - set structsSeen; - TypeSet oversized; - oversizedSubtypesInner(_type, true, structsSeen, oversized); - vector res; - copy(oversized.cbegin(), oversized.cend(), back_inserter(res)); - return res; -} - void Type::clearCache() const { m_members.clear(); @@ -1612,6 +1552,21 @@ TypeResult ContractType::unaryOperatorResult(Token _operator) const return nullptr; } +vector CompositeType::fullDecomposition() const +{ + vector res = {this}; + unordered_set seen = {richIdentifier()}; + for (size_t k = 0; k < res.size(); ++k) + if (auto composite = dynamic_cast(res[k])) + for (Type const* next: composite->decomposition()) + if (seen.count(next->richIdentifier()) == 0) + { + seen.insert(next->richIdentifier()); + res.push_back(next); + } + return res; +} + Type const* ReferenceType::withLocation(DataLocation _location, bool _isPointer) const { return TypeProvider::withLocation(this, _location, _isPointer); @@ -2649,6 +2604,13 @@ vector> StructType::makeStackItems() const solAssert(false, ""); } +vector StructType::decomposition() const +{ + vector res; + for (MemberList::Member const& member: members(nullptr)) + res.push_back(member.type); + return res; +} TypePointer EnumType::encodingType() const { diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 9b32475cab46..f70398a9f8c2 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -60,8 +60,6 @@ using BoolResult = util::Result; namespace solidity::frontend { -std::vector oversizedSubtypes(frontend::Type const& _type); - inline rational makeRational(bigint const& _numerator, bigint const& _denominator) { solAssert(_denominator != 0, "division by zero"); @@ -694,11 +692,37 @@ class BoolType: public Type TypeResult interfaceType(bool) const override { return this; } }; +/** + * Base class for types which can be thought of as several elements of other types put together. + * For example a struct is composed of its members, an array is composed of multiple copies of its + * base element and a mapping is composed of its value type elements (note that keys are not + * stored anywhere). + */ +class CompositeType: public Type +{ +protected: + CompositeType() = default; + +public: + /// @returns a list containing the type itself, elements of its decomposition, + /// elements of decomposition of these elements and so on, up to non-composite types. + /// Each type is included only once. + std::vector fullDecomposition() const; + +protected: + /// @returns a list of types that together make up the data part of this type. + /// Contains all types that will have to be implicitly stored, whenever an object of this type is stored. + /// In particular, it returns the base type for arrays and array slices, the member types for structs, + /// the component types for tuples and the value type for mappings + /// (note that the key type of a mapping is *not* part of the list). + virtual std::vector decomposition() const = 0; +}; + /** * Base class used by types which are not value types and can be stored either in storage, memory * or calldata. This is currently used by arrays and structs. */ -class ReferenceType: public Type +class ReferenceType: public CompositeType { protected: explicit ReferenceType(DataLocation _location): m_location(_location) {} @@ -829,6 +853,8 @@ class ArrayType: public ReferenceType protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override { return {m_baseType}; } + private: /// String is interpreted as a subtype of Bytes. enum class ArrayKind { Ordinary, Bytes, String }; @@ -869,6 +895,8 @@ class ArraySliceType: public ReferenceType protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override { return {m_arrayType.baseType()}; } + private: ArrayType const& m_arrayType; }; @@ -994,6 +1022,8 @@ class StructType: public ReferenceType protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override; + private: StructDefinition const& m_struct; // Caches for interfaceType(bool) @@ -1044,7 +1074,7 @@ class EnumType: public Type * Type that can hold a finite sequence of values of different types. * In some cases, the components are empty pointers (when used as placeholders). */ -class TupleType: public Type +class TupleType: public CompositeType { public: explicit TupleType(std::vector _types = {}): m_components(std::move(_types)) {} @@ -1067,6 +1097,16 @@ class TupleType: public Type protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override + { + // Currently calling TupleType::decomposition() is not expected, because we cannot declare a variable of a tuple type. + // If that changes, before removing the solAssert, make sure the function does the right thing and is used properly. + // Note that different tuple members can have different data locations, so using decomposition() to check + // the tuple validity for a data location might require special care. + solUnimplemented("Tuple decomposition is not expected."); + return m_components; + } + private: std::vector const m_components; }; @@ -1349,7 +1389,7 @@ class FunctionType: public Type * The type of a mapping, there is one distinct type per key/value type pair. * Mappings always occupy their own storage slot, but do not actually use it. */ -class MappingType: public Type +class MappingType: public CompositeType { public: MappingType(Type const* _keyType, Type const* _valueType): @@ -1373,6 +1413,9 @@ class MappingType: public Type Type const* keyType() const { return m_keyType; } Type const* valueType() const { return m_valueType; } +protected: + std::vector decomposition() const override { return {m_valueType}; } + private: TypePointer m_keyType; TypePointer m_valueType; diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol b/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol index a6894c9a3473..761e60a1a873 100644 --- a/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol +++ b/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol @@ -13,6 +13,8 @@ contract b { } // ---- // Warning 7325: (66-67): Type struct b.c covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (66-67): Type uint256[14474011154664524427946373126085988481658748083205070504932198000989141204992] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (111-112): Type struct b.c covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (111-112): Type uint256[14474011154664524427946373126085988481658748083205070504932198000989141204992] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (152-169): Type function ()[984770902183611232881] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 2072: (152-180): Unused local variable. diff --git a/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol b/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol index 9b14dea3daf7..cbc912a35008 100644 --- a/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol +++ b/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol @@ -56,13 +56,19 @@ contract C { } // ---- // Warning 7325: (106-108): Type struct C.S0 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (106-108): Type struct C.P[101] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (171-173): Type struct C.S1 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (171-173): Type struct C.P[102] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (341-343): Type struct C.P[103] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (341-343): Type struct C.P[104] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. -// Warning 7325: (505-507): Type uint256[100000000000000000002] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. -// Warning 7325: (505-507): Type uint256[100000000000000000004] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (505-507): Type struct C.Q0 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[1][][100000000000000000001] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[][100000000000000000003] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[100000000000000000004] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[100000000000000000002] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (576-578): Type struct C.Q1 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (576-578): Type uint256[1][][100000000000000000005] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (647-649): Type uint256[100000000000000000006] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (715-717): Type struct C.Q3 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (715-717): Type uint256[][100000000000000000007] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (783-785): Type uint256[100000000000000000008] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol index 123773403a2c..4bf6c86be0eb 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol @@ -4,5 +4,5 @@ contract C { uint[2**255][2] a; } // ---- -// TypeError 7676: (60-97): Contract too large for storage. +// TypeError 7676: (60-97): Contract requires too much storage. // TypeError 1534: (77-94): Type too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol index 83eacfed0762..f6c82a8235fc 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol @@ -5,4 +5,4 @@ contract C { uint[2**255] b; } // ---- -// TypeError 7676: (60-114): Contract too large for storage. +// TypeError 7676: (60-114): Contract requires too much storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol index 72f3035fe1bf..a181e3a58134 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol @@ -7,4 +7,4 @@ contract D is C { uint[2**255] b; } // ---- -// TypeError 7676: (95-134): Contract too large for storage. +// TypeError 7676: (95-134): Contract requires too much storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol index 757ec9e4afc8..d1c8187568d6 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol @@ -8,5 +8,5 @@ contract C { S s; } // ---- -// TypeError 7676: (60-152): Contract too large for storage. +// TypeError 7676: (60-152): Contract requires too much storage. // TypeError 1534: (146-149): Type too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol b/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol index ea1dd97b8b01..836a49b034e7 100644 --- a/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol +++ b/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol @@ -4,3 +4,4 @@ contract C { } // ---- // Warning 7325: (64-65): Type struct C.S covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (64-65): Type uint256[57896044618658097711785492504343953926634992332820282019728792003956564819968] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.