diff --git a/Changelog.md b/Changelog.md index 7b0a87a9a..1c8ffe171 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Compiler Features: Bugfixes: * Type Checker: Fix overload resolution in combination with ``{value: ...}``. + * Type Checker: Fix internal compiler error related to oversized types. Compiler Features: * Optimizer: Add rule to remove shifts inside the byte opcode. diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index b650f1fc1..62a1861d1 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -155,29 +155,6 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable) // This is not a no-op, the entry might pre-exist. m_localVarUseCount[make_pair(_variable.id(), &_variable)] += 0; } - else if (_variable.isStateVariable()) - { - set structsSeen; - TypeSet oversizedSubTypes; - if (structureSizeEstimate(*_variable.type(), structsSeen, oversizedSubTypes) >= bigint(1) << 64) - m_errorReporter.warning( - 3408_error, - _variable.location(), - "Variable " + util::escapeAndQuoteString(_variable.name()) + - " 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." - ); - for (Type const* type: oversizedSubTypes) - m_errorReporter.warning( - 7325_error, - _variable.location(), - "Type " + util::escapeAndQuoteString(type->canonicalName()) + - " has large size and thus makes collisions likely. " - "Either use mappings or dynamic arrays and allow their size to be increased only " - "in small quantities per transaction." - ); - } return true; } @@ -349,47 +326,3 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall) } return true; } - -bigint StaticAnalyzer::structureSizeEstimate( - Type const& _type, - set& _structsSeen, - TypeSet& _oversizedSubTypes -) -{ - switch (_type.category()) - { - case Type::Category::Array: - { - auto const& t = dynamic_cast(_type); - bigint baseTypeSize = structureSizeEstimate(*t.baseType(), _structsSeen, _oversizedSubTypes); - if (baseTypeSize >= bigint(1) << 64) - _oversizedSubTypes.insert(t.baseType()); - if (!t.isDynamicallySized()) - return structureSizeEstimate(*t.baseType(), _structsSeen, _oversizedSubTypes) * t.length(); - break; - } - case Type::Category::Struct: - { - auto const& t = dynamic_cast(_type); - bigint size = 1; - if (_structsSeen.count(&t.structDefinition())) - return size; - _structsSeen.insert(&t.structDefinition()); - for (auto const& m: t.members(nullptr)) - size += structureSizeEstimate(*m.type, _structsSeen, _oversizedSubTypes); - _structsSeen.erase(&t.structDefinition()); - return size; - } - case Type::Category::Mapping: - { - auto const* valueType = dynamic_cast(_type).valueType(); - bigint valueTypeSize = structureSizeEstimate(*valueType, _structsSeen, _oversizedSubTypes); - if (valueTypeSize >= bigint(1) << 64) - _oversizedSubTypes.insert(valueType); - break; - } - default: - break; - } - return bigint(1); -} diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index 8f7fbc597..7886b4288 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -73,23 +73,6 @@ private: bool visit(BinaryOperation const& _operation) override; bool visit(FunctionCall const& _functionCall) override; - struct TypeComp - { - bool operator()(Type const* lhs, Type const* rhs) const - { - solAssert(lhs && rhs, ""); - return lhs->richIdentifier() < rhs->richIdentifier(); - } - }; - using TypeSet = std::set; - - /// @returns the size of this type in storage, including all sub-types. - static bigint structureSizeEstimate( - Type const& _type, - std::set& _structsSeen, - TypeSet& _oversizedSubTypes - ); - langutil::ErrorReporter& m_errorReporter; /// Flag that indicates whether the current contract definition is a library. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 6bb9dd39f..aa316cb48 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -91,6 +92,20 @@ bool TypeChecker::visit(ContractDefinition const& _contract) for (auto const& n: _contract.subNodes()) n->accept(*this); + bigint size = 0; + vector variables; + for (ContractDefinition const* contract: boost::adaptors::reverse(m_currentContract->annotation().linearizedBaseContracts)) + for (VariableDeclaration const* variable: contract->stateVariables()) + if (!(variable->isConstant() || variable->immutable())) + { + size += storageSizeUpperBound(*(variable->annotation().type)); + if (size >= bigint(1) << 256) + { + m_errorReporter.typeError(7676_error, m_currentContract->location(), "Contract too large for storage."); + break; + } + } + return false; } @@ -523,6 +538,10 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) m_errorReporter.typeError(6744_error, _variable.location(), "Internal or recursive type is not allowed for public state variables."); } + bool isStructMemberDeclaration = dynamic_cast(_variable.scope()) != nullptr; + if (isStructMemberDeclaration) + return false; + if (auto referenceType = dynamic_cast(varType)) { auto result = referenceType->validForLocation(referenceType->location()); @@ -532,9 +551,33 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) { solAssert(!result.message().empty(), "Expected detailed error message"); m_errorReporter.typeError(1534_error, _variable.location(), result.message()); + return false; } } + if (varType->dataStoredIn(DataLocation::Storage)) + { + auto collisionMessage = [&](string const& variableOrType, bool isVariable) -> string { + return + (isVariable ? "Variable " : "Type ") + + util::escapeAndQuoteString(variableOrType) + + " 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."; + }; + + if (storageSizeUpperBound(*varType) >= bigint(1) << 64) + { + if (_variable.isStateVariable()) + m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true)); + else + m_errorReporter.warning(2332_error, _variable.typeName()->location(), collisionMessage(varType->canonicalName(), false)); + } + vector oversizedSubtypes = frontend::oversizedSubtypes(*varType); + for (Type const* subtype: oversizedSubtypes) + m_errorReporter.warning(7325_error, _variable.typeName()->location(), collisionMessage(subtype->canonicalName(), false)); + } + return false; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 992f0343d..546e6ee63 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -53,6 +53,88 @@ 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; + +bigint storageSizeUpperBoundInner( + Type const& _type, + set& _structsSeen +) +{ + switch (_type.category()) + { + case Type::Category::Array: + { + auto const& t = dynamic_cast(_type); + if (!t.isDynamicallySized()) + return storageSizeUpperBoundInner(*t.baseType(), _structsSeen) * t.length(); + break; + } + case Type::Category::Struct: + { + auto const& t = dynamic_cast(_type); + solAssert(!_structsSeen.count(&t.structDefinition()), "Recursive struct."); + bigint size = 1; + _structsSeen.insert(&t.structDefinition()); + for (auto const& m: t.members(nullptr)) + size += storageSizeUpperBoundInner(*m.type, _structsSeen); + _structsSeen.erase(&t.structDefinition()); + return size; + } + default: + break; + } + return bigint(1); +} + +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 && storageSizeUpperBound(t) >= 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 && storageSizeUpperBound(t) >= 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) { @@ -149,6 +231,22 @@ util::Result transformParametersToExternal(TypePointers const& _pa } +bigint solidity::frontend::storageSizeUpperBound(frontend::Type const& _type) +{ + set structsSeen; + return storageSizeUpperBoundInner(_type, structsSeen); +} + +vector solidity::frontend::oversizedSubtypes(frontend::Type const& _type) +{ + set structsSeen; + TypeSet oversized; + oversizedSubtypesInner(_type, false, structsSeen, oversized); + vector res; + copy(oversized.cbegin(), oversized.cend(), back_inserter(res)); + return res; +} + void Type::clearCache() const { m_members.clear(); @@ -1749,6 +1847,8 @@ BoolResult ArrayType::validForLocation(DataLocation _loc) const break; } case DataLocation::Storage: + if (storageSizeUpperBound(*this) >= bigint(1) << 256) + return BoolResult::err("Type too large for storage."); break; } return true; @@ -2388,6 +2488,13 @@ BoolResult StructType::validForLocation(DataLocation _loc) const if (!result) return result; } + + if ( + _loc == DataLocation::Storage && + storageSizeUpperBound(*this) >= bigint(1) << 256 + ) + return BoolResult::err("Type too large for storage."); + return true; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index b902a1c77..b05021630 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -54,6 +54,14 @@ using rational = boost::rational; using TypeResult = util::Result; using BoolResult = util::Result; +} + +namespace solidity::frontend +{ + +bigint storageSizeUpperBound(frontend::Type const& _type); +std::vector oversizedSubtypes(frontend::Type const& _type); + inline rational makeRational(bigint const& _numerator, bigint const& _denominator) { solAssert(_denominator != 0, "division by zero"); diff --git a/test/libsolidity/syntaxTests/largeTypes/large_storage_array_mapping.sol b/test/libsolidity/syntaxTests/largeTypes/large_storage_array_mapping.sol index 01a68bcda..2dfc50a41 100644 --- a/test/libsolidity/syntaxTests/largeTypes/large_storage_array_mapping.sol +++ b/test/libsolidity/syntaxTests/largeTypes/large_storage_array_mapping.sol @@ -2,4 +2,4 @@ contract C { mapping(uint => uint[2**100]) x; } // ---- -// Warning 7325: (17-48): Type "uint256[1267650600228229401496703205376]" has large size 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: (17-46): Type "uint256[1267650600228229401496703205376]" 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/large_storage_structs.sol b/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol index 2956a6549..a29401d94 100644 --- a/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol +++ b/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol @@ -23,9 +23,9 @@ contract C { struct Q0 { - uint[1][][10**20 + 4] x; - uint[10**20 + 4][][1] y; - uint[][10**20 + 4] z; + uint[1][][10**20 + 1] x; + uint[10**20 + 2][][1] y; + uint[][10**20 + 3] z; uint[10**20 + 4][] t; } Q0 q0; @@ -57,11 +57,12 @@ contract C { // ---- // Warning 3408: (106-111): Variable "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 3408: (171-176): Variable "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: (341-346): Type "C.P[103]" has large size 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-346): Type "C.P[104]" has large size 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 "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 "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 3408: (505-510): Variable "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-510): Type "uint256[100000000000000000004]" has large size 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 3408: (576-581): Variable "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: (647-652): Type "uint256[100000000000000000006]" has large size 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 3408: (715-720): Variable "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: (783-788): Type "uint256[100000000000000000008]" has large size 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 new file mode 100644 index 000000000..951840fd4 --- /dev/null +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >= 0.0; +contract C { + uint[2**255][2] a; +} +// ---- +// TypeError 1534: (77-94): Type too large for storage. +// TypeError 7676: (60-97): Contract too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol new file mode 100644 index 000000000..69f8b1024 --- /dev/null +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >= 0.0; +contract C { + uint[2**255] a; + uint[2**255] b; +} +// ---- +// Warning 3408: (77-91): Variable "a" 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 3408: (97-111): Variable "b" 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. +// TypeError 7676: (60-114): Contract too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol new file mode 100644 index 000000000..a3f3a4160 --- /dev/null +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >= 0.0; +contract C { + struct S { + uint[2**255] a; + uint[2**255] b; + } + S s; +} +// ---- +// TypeError 1534: (146-149): Type too large for storage. +// TypeError 7676: (60-152): Contract too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol b/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol new file mode 100644 index 000000000..b32815028 --- /dev/null +++ b/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol @@ -0,0 +1,6 @@ +contract C { + struct S { uint256[2**255] x; } + function f(S storage) internal {} +} +// ---- +// Warning 2332: (64-65): Type "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.