diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 0cca63a80..3a83058c1 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -830,11 +830,14 @@ void FunctionCall::checkTypeRequirements(TypePointers const*) return; } + /// For error message: Struct members that were removed during conversion to memory. + set membersRemovedForStructConstructor; if (isStructConstructorCall()) { TypeType const& type = dynamic_cast(*expressionType); auto const& structType = dynamic_cast(*type.getActualType()); functionType = structType.constructorType(); + membersRemovedForStructConstructor = structType.membersMissingInMemory(); } else functionType = dynamic_pointer_cast(expressionType); @@ -847,13 +850,22 @@ void FunctionCall::checkTypeRequirements(TypePointers const*) // function parameters TypePointers const& parameterTypes = functionType->getParameterTypes(); if (!functionType->takesArbitraryParameters() && parameterTypes.size() != m_arguments.size()) - BOOST_THROW_EXCEPTION(createTypeError( + { + string msg = "Wrong argument count for function call: " + toString(m_arguments.size()) + " arguments given but expected " + toString(parameterTypes.size()) + - "." - )); + "."; + // Extend error message in case we try to construct a struct with mapping member. + if (isStructConstructorCall() && !membersRemovedForStructConstructor.empty()) + { + msg += " Members that have to be skipped in memory:"; + for (auto const& member: membersRemovedForStructConstructor) + msg += " " + member; + } + BOOST_THROW_EXCEPTION(createTypeError(msg)); + } if (isPositionalCall) { @@ -972,10 +984,22 @@ void MemberAccess::checkTypeRequirements(TypePointers const* _argumentTypes) ++it; } if (possibleMembers.size() == 0) + { + auto storageType = ReferenceType::copyForLocationIfReference( + DataLocation::Storage, + m_expression->getType() + ); + if (!storageType->getMembers().membersByName(*m_memberName).empty()) + BOOST_THROW_EXCEPTION(createTypeError( + "Member \"" + *m_memberName + "\" is not available in " + + type.toString() + + " outside of storage." + )); BOOST_THROW_EXCEPTION(createTypeError( "Member \"" + *m_memberName + "\" not found or not visible " "after argument-dependent lookup in " + type.toString() )); + } else if (possibleMembers.size() > 1) BOOST_THROW_EXCEPTION(createTypeError( "Member \"" + *m_memberName + "\" not unique " diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 91ef16b50..d85c0511f 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -1040,14 +1040,6 @@ u256 StructType::getStorageSize() const return max(1, getMembers().getStorageSize()); } -bool StructType::canLiveOutsideStorage() const -{ - for (auto const& member: getMembers()) - if (!member.type->canLiveOutsideStorage()) - return false; - return true; -} - string StructType::toString(bool _short) const { string ret = "struct " + m_struct.getName(); @@ -1064,9 +1056,13 @@ MemberList const& StructType::getMembers() const MemberList::MemberMap members; for (ASTPointer const& variable: m_struct.getMembers()) { + TypePointer type = variable->getType(); + // Skip all mapping members if we are not in storage. + if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) + continue; members.push_back(MemberList::Member( variable->getName(), - copyForLocationIfReference(variable->getType()), + copyForLocationIfReference(type), variable.get()) ); } @@ -1077,8 +1073,7 @@ MemberList const& StructType::getMembers() const TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const { - auto copy = make_shared(m_struct); - copy->m_location = _location; + auto copy = make_shared(m_struct, _location); copy->m_isPointer = _isPointer; return copy; } @@ -1122,6 +1117,15 @@ u256 StructType::memoryOffsetOfMember(string const& _name) const return 0; } +set StructType::membersMissingInMemory() const +{ + set missing; + for (ASTPointer const& variable: m_struct.getMembers()) + if (!variable->getType()->canLiveOutsideStorage()) + missing.insert(variable->getName()); + return missing; +} + TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const { return _operator == Token::Delete ? make_shared() : TypePointer(); diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 8bdfc5e69..f18855b26 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -574,14 +574,14 @@ class StructType: public ReferenceType { public: virtual Category getCategory() const override { return Category::Struct; } - explicit StructType(StructDefinition const& _struct): - ReferenceType(DataLocation::Storage), m_struct(_struct) {} + explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage): + ReferenceType(_location), m_struct(_struct) {} virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; virtual bool operator==(Type const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded) const override; u256 memorySize() const; virtual u256 getStorageSize() const override; - virtual bool canLiveOutsideStorage() const override; + virtual bool canLiveOutsideStorage() const override { return true; } virtual std::string toString(bool _short) const override; virtual MemberList const& getMembers() const override; @@ -597,6 +597,9 @@ public: StructDefinition const& structDefinition() const { return m_struct; } + /// @returns the set of all members that are removed in the memory version (typically mappings). + std::set membersMissingInMemory() const; + private: StructDefinition const& m_struct; /// List of member types, will be lazy-initialized because of recursive references. diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 9f806347e..b5c786564 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -5034,6 +5034,29 @@ BOOST_AUTO_TEST_CASE(literal_strings) } +BOOST_AUTO_TEST_CASE(memory_structs_with_mappings) +{ + char const* sourceCode = R"( + contract Test { + struct S { uint8 a; mapping(uint => uint) b; uint8 c; } + S s; + function f() returns (uint) { + S memory x; + if (x.a != 0 || x.c != 0) return 1; + x.a = 4; x.c = 5; + s = x; + if (s.a != 4 || s.c != 5) return 2; + x = S(2, 3); + if (x.a != 2 || x.c != 3) return 3; + x = s; + if (s.a != 4 || s.c != 5) return 4; + } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 0f5e4800f..cfc43df91 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2134,6 +2134,21 @@ BOOST_AUTO_TEST_CASE(invalid_integer_literal_exp) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } +BOOST_AUTO_TEST_CASE(memory_structs_with_mappings) +{ + char const* text = R"( + contract Test { + struct S { uint8 a; mapping(uint => uint) b; uint8 c; } + S s; + function f() { + S memory x; + x.b[1]; + } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() }