Added containsNestedMapping()

This commit is contained in:
Harikrishnan Mulackal 2020-06-03 16:08:35 +05:30
parent af0cd4ab98
commit d41eaeba56
8 changed files with 136 additions and 105 deletions

View File

@ -112,18 +112,16 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
{
Type const* memberType = member->annotation().type;
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
{
if (arrayType->isDynamicallySized())
break;
memberType = arrayType->baseType();
}
if (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
memberType = arrayType->finalBaseType(true);
if (auto structType = dynamic_cast<StructType const*>(memberType))
if (_cycleDetector.run(structType->structDefinition()))
return;
}
};
if (util::CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
if (util::CycleDetector<StructDefinition>(visitor).run(_struct))
m_errorReporter.fatalTypeError(2046_error, _struct.location(), "Recursive struct definition.");
return false;

View File

@ -339,31 +339,21 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
m_errorReporter.typeError(5587_error, _function.location(), "Internal functions cannot be payable.");
}
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) {
if (type(var)->category() == Type::Category::Mapping)
if (type(var)->containsNestedMapping())
if (var.referenceLocation() == VariableDeclaration::Location::Storage)
solAssert(
_function.libraryFunction() || !_function.isPublic(),
"Mapping types for parameters or return variables "
"can only be used in internal or library functions."
);
if (_function.isPublic())
{
if (var.referenceLocation() != VariableDeclaration::Location::Storage)
{
if (!_function.libraryFunction() && _function.isPublic())
m_errorReporter.typeError(3442_error, var.location(), "Mapping types can only have a data location of \"storage\" and thus only be parameters or return variables for internal or library functions.");
else
m_errorReporter.typeError(5380_error, var.location(), "Mapping types can only have a data location of \"storage\"." );
}
else
solAssert(_function.libraryFunction() || !_function.isPublic(), "Mapping types for parameters or return variables can only be used in internal or library functions.");
}
else
{
if (!type(var)->canLiveOutsideStorage() && _function.isPublic())
m_errorReporter.typeError(3312_error, var.location(), "Type is required to live outside storage.");
if (_function.isPublic())
{
auto iType = type(var)->interfaceType(_function.libraryFunction());
auto iType = type(var)->interfaceType(_function.libraryFunction());
if (!iType)
{
solAssert(!iType.message().empty(), "Expected detailed error message!");
m_errorReporter.fatalTypeError(4103_error, var.location(), iType.message());
}
if (!iType)
{
solAssert(!iType.message().empty(), "Expected detailed error message!");
m_errorReporter.typeError(4103_error, var.location(), iType.message());
}
}
if (
@ -457,9 +447,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
m_errorReporter.typeError(1273_error, _variable.location(), "The type of a variable cannot be a library.");
if (_variable.value())
{
if (_variable.isStateVariable() && dynamic_cast<MappingType const*>(varType))
if (_variable.isStateVariable() && varType->containsNestedMapping())
{
m_errorReporter.typeError(6280_error, _variable.location(), "Mappings cannot be assigned to.");
m_errorReporter.typeError(
6280_error,
_variable.location(),
"Types in storage containing (nested) mappings cannot be assigned to."
);
_variable.value()->accept(*this);
}
else
@ -499,9 +493,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (!_variable.isStateVariable())
{
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
if (!varType->canLiveOutsideStorage())
m_errorReporter.typeError(4061_error, _variable.location(), "Type " + varType->toString() + " is only valid in storage.");
if (
_variable.referenceLocation() == VariableDeclaration::Location::CallData ||
_variable.referenceLocation() == VariableDeclaration::Location::Memory
)
if (varType->containsNestedMapping())
m_errorReporter.fatalTypeError(
4061_error,
_variable.location(),
"Type " + varType->toString(true) + " is only valid in storage because it contains a (nested) mapping."
);
}
else if (_variable.visibility() >= Visibility::Public)
{
@ -612,8 +613,12 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
{
if (var->isIndexed())
numIndexed++;
if (!type(*var)->canLiveOutsideStorage())
m_errorReporter.typeError(3448_error, var->location(), "Type is required to live outside storage.");
if (type(*var)->containsNestedMapping())
m_errorReporter.typeError(
3448_error,
var->location(),
"Type containing a (nested) mapping is not allowed as event parameter type."
);
if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
if (
@ -1386,7 +1391,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
checkExpressionAssignment(*types[i], *tupleExpression->components()[i]);
}
}
else if (_type.category() == Type::Category::Mapping)
else if (_type.nameable() && _type.containsNestedMapping())
{
bool isLocalOrReturn = false;
if (auto const* identifier = dynamic_cast<Identifier const*>(&_expression))
@ -1394,7 +1399,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
if (variableDeclaration->isLocalOrReturn())
isLocalOrReturn = true;
if (!isLocalOrReturn)
m_errorReporter.typeError(9214_error, _expression.location(), "Mappings cannot be assigned to.");
m_errorReporter.typeError(9214_error, _expression.location(), "Types in storage containing (nested) mappings cannot be assigned to.");
}
}
@ -1530,8 +1535,12 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.location(),
"Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element."
);
else if (!inlineArrayType->canLiveOutsideStorage())
m_errorReporter.fatalTypeError(1545_error, _tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage.");
else if (inlineArrayType->containsNestedMapping())
m_errorReporter.fatalTypeError(
1545_error,
_tuple.location(),
"Type " + inlineArrayType->toString(true) + " is only valid in storage."
);
_tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size());
}
@ -2032,24 +2041,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
toString(parameterTypes.size()) +
".";
// Extend error message in case we try to construct a struct with mapping member.
if (isStructConstructorCall)
{
/// For error message: Struct members that were removed during conversion to memory.
TypePointer const expressionType = type(_functionCall.expression());
auto const& t = dynamic_cast<TypeType const&>(*expressionType);
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
set<string> membersRemovedForStructConstructor = structType.membersMissingInMemory();
if (!membersRemovedForStructConstructor.empty())
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
}
return { isVariadic ? 1123_error : 9755_error, msg };
}
else if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
@ -2269,6 +2262,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (actualType->category() == Type::Category::Struct)
{
if (actualType->containsNestedMapping())
m_errorReporter.fatalTypeError(
9515_error,
_functionCall.location(),
"Struct containing a (nested) mapping cannot be constructed."
);
functionType = dynamic_cast<StructType const&>(*actualType).constructorType();
funcCallAnno.kind = FunctionCallKind::StructConstructorCall;
funcCallAnno.isPure = argumentsArePure;
@ -2518,11 +2517,11 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
}
else if (type->category() == Type::Category::Array)
{
if (!type->canLiveOutsideStorage())
if (type->containsNestedMapping())
m_errorReporter.fatalTypeError(
1164_error,
_newExpression.typeName().location(),
"Type cannot live outside storage."
"Array containing a (nested) mapping cannot be constructed in memory."
);
if (!type->isDynamicallySized())
m_errorReporter.typeError(

View File

@ -136,6 +136,9 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
/// recursion immediately raises an error.
/// Will be filled in by the DeclarationTypeChecker.
std::optional<bool> recursive;
/// Whether the struct contains a mapping type, either directly or, indirectly inside another
/// struct or an array.
std::optional<bool> containsNestedMapping;
};
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation

View File

@ -1985,6 +1985,20 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const
return result;
}
Type const* ArrayType::finalBaseType(bool _breakIfDynamicArrayType) const
{
Type const* finalBaseType = this;
while (auto arrayType = dynamic_cast<ArrayType const*>(finalBaseType))
{
if (_breakIfDynamicArrayType && arrayType->isDynamicallySized())
break;
finalBaseType = arrayType->baseType();
}
return finalBaseType;
}
u256 ArrayType::memoryDataSize() const
{
solAssert(!isDynamicallySized(), "");
@ -2213,7 +2227,7 @@ unsigned StructType::calldataEncodedSize(bool) const
unsigned size = 0;
for (auto const& member: members(nullptr))
{
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
// Struct members are always padded.
size += member.type->calldataEncodedSize();
}
@ -2228,7 +2242,7 @@ unsigned StructType::calldataEncodedTailSize() const
unsigned size = 0;
for (auto const& member: members(nullptr))
{
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
// Struct members are always padded.
size += member.type->calldataHeadSize();
}
@ -2240,7 +2254,7 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const
unsigned offset = 0;
for (auto const& member: members(nullptr))
{
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
if (member.name == _member)
return offset;
// Struct members are always padded.
@ -2277,6 +2291,42 @@ u256 StructType::storageSize() const
return max<u256>(1, members(nullptr).storageSize());
}
bool StructType::containsNestedMapping() const
{
if (!m_struct.annotation().containsNestedMapping.has_value())
{
bool hasNestedMapping = false;
util::BreadthFirstSearch<StructDefinition const*> breadthFirstSearch{{&m_struct}};
breadthFirstSearch.run(
[&](StructDefinition const* _struct, auto&& _addChild)
{
for (auto const& member: _struct->members())
{
TypePointer memberType = member->annotation().type;
solAssert(memberType, "");
if (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
memberType = arrayType->finalBaseType(false);
if (dynamic_cast<MappingType const*>(memberType))
{
hasNestedMapping = true;
breadthFirstSearch.abort();
}
else if (auto structType = dynamic_cast<StructType const*>(memberType))
_addChild(&structType->structDefinition());
}
});
m_struct.annotation().containsNestedMapping = hasNestedMapping;
}
return m_struct.annotation().containsNestedMapping.value();
}
string StructType::toString(bool _short) const
{
string ret = "struct " + m_struct.annotation().canonicalName;
@ -2292,10 +2342,7 @@ MemberList::MemberMap StructType::nativeMembers(ASTNode const*) const
{
TypePointer type = variable->annotation().type;
solAssert(type, "");
// If we are not in storage, skip all members that cannot live outside of storage,
// ex. mappings and array of mappings
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
continue;
solAssert(!(location() != DataLocation::Storage && type->containsNestedMapping()), "");
members.emplace_back(
variable->name(),
copyForLocationIfReference(type),
@ -2457,10 +2504,9 @@ FunctionTypePointer StructType::constructorType() const
{
TypePointers paramTypes;
strings paramNames;
solAssert(!containsNestedMapping(), "");
for (auto const& member: members(nullptr))
{
if (!member.type->canLiveOutsideStorage())
continue;
paramNames.push_back(member.name);
paramTypes.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, member.type));
}
@ -2494,20 +2540,12 @@ u256 StructType::memoryOffsetOfMember(string const& _name) const
TypePointers StructType::memoryMemberTypes() const
{
solAssert(!containsNestedMapping(), "");
TypePointers types;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (variable->annotation().type->canLiveOutsideStorage())
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
return types;
}
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
set<string> StructType::membersMissingInMemory() const
{
set<string> missing;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (!variable->annotation().type->canLiveOutsideStorage())
missing.insert(variable->name());
return missing;
return types;
}
vector<tuple<string, TypePointer>> StructType::makeStackItems() const
@ -3654,7 +3692,10 @@ TypeResult MappingType::interfaceType(bool _inLibrary) const
}
}
else
return TypeResult::err("Only libraries are allowed to use the mapping type in public or external functions.");
return TypeResult::err(
"Types containing (nested) mappings can only be parameters or "
"return variables of internal or library functions."
);
return this;
}

View File

@ -259,7 +259,11 @@ public:
/// Returns true if the type can be stored in storage.
virtual bool canBeStored() const { return true; }
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
virtual bool canLiveOutsideStorage() const { return true; }
virtual bool containsNestedMapping() const
{
solAssert(nameable(), "Called for a non nameable type.");
return false;
}
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
@ -549,7 +553,6 @@ public:
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; }
std::string toString(bool _short) const override;
u256 literalValue(Literal const* _literal) const override;
@ -610,7 +613,6 @@ public:
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; }
std::string toString(bool) const override;
TypePointer mobileType() const override;
@ -781,8 +783,9 @@ public:
bool isDynamicallySized() const override { return m_hasDynamicLength; }
bool isDynamicallyEncoded() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
bool containsNestedMapping() const override { return m_baseType->containsNestedMapping(); }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
std::string signatureInExternalFunction(bool _structsByName) const override;
@ -798,6 +801,7 @@ public:
/// @returns true if this is a string
bool isString() const { return m_arrayKind == ArrayKind::String; }
Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; }
Type const* finalBaseType(bool breakIfDynamicArrayType) const;
u256 const& length() const { return m_length; }
u256 memoryDataSize() const override;
@ -842,7 +846,6 @@ public:
unsigned calldataEncodedTailSize() const override { return 32; }
bool isDynamicallySized() const override { return true; }
bool isDynamicallyEncoded() const override { return true; }
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
std::string toString(bool _short) const override;
TypePointer mobileType() const override;
@ -883,7 +886,6 @@ public:
}
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
bool canLiveOutsideStorage() const override { return !isSuper(); }
bool isValueType() const override { return !isSuper(); }
bool nameable() const override { return !isSuper(); }
std::string toString(bool _short) const override;
@ -945,7 +947,7 @@ public:
bool isDynamicallyEncoded() const override;
u256 memoryDataSize() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return true; }
bool containsNestedMapping() const override;
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
@ -975,8 +977,6 @@ public:
/// @returns the vector of types of members available in memory.
TypePointers memoryMemberTypes() const;
/// @returns the set of all members that are removed in the memory version (typically mappings).
std::set<std::string> membersMissingInMemory() const;
void clearCache() const override;
@ -1007,7 +1007,6 @@ public:
}
unsigned storageBytes() const override;
bool leftAligned() const override { return false; }
bool canLiveOutsideStorage() const override { return true; }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
bool isValueType() const override { return true; }
@ -1047,7 +1046,6 @@ public:
std::string toString(bool) const override;
bool canBeStored() const override { return false; }
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; }
bool hasSimpleZeroValueInMemory() const override { return false; }
TypePointer mobileType() const override;
/// Converts components to their temporary types and performs some wildcard matching.
@ -1218,7 +1216,6 @@ public:
unsigned storageBytes() const override;
bool isValueType() const override { return true; }
bool nameable() const override;
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
bool hasSimpleZeroValueInMemory() const override { return false; }
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
TypePointer encodingType() const override;
@ -1351,7 +1348,7 @@ public:
bool operator==(Type const& _other) const override;
std::string toString(bool _short) const override;
std::string canonicalName() const override;
bool canLiveOutsideStorage() const override { return false; }
bool containsNestedMapping() const override { return true; }
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
Type const* encodingType() const override;
TypeResult interfaceType(bool _inLibrary) const override;
@ -1386,7 +1383,6 @@ public:
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
@ -1412,7 +1408,6 @@ public:
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
bool canBeStored() const override { return false; }
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
@ -1439,7 +1434,6 @@ public:
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
@ -1479,7 +1473,6 @@ public:
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
@ -1512,7 +1505,6 @@ public:
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
unsigned calldataEncodedSize(bool) const override { return 32; }
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; }
bool isValueType() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string toString(bool) const override { return "inaccessible dynamic type"; }

View File

@ -860,8 +860,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
for (auto const& member: _to.members(nullptr))
{
solAssert(member.type, "");
if (!member.type->canLiveOutsideStorage())
continue;
solAssert(!member.type->containsNestedMapping(), "");
TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false);
solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented.");
auto memberTypeFrom = _from.memberType(member.name);
@ -1340,7 +1339,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
for (auto const& member: _type.members(nullptr))
{
solAssert(member.type, "");
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
auto decodingType = member.type->decodingType();
solAssert(decodingType, "");
bool dynamic = decodingType->isDynamicallyEncoded();

View File

@ -1069,8 +1069,7 @@ void CompilerUtils::convertType(
// stack: <memory ptr> <source ref> <memory ptr>
for (auto const& member: typeOnStack->members(nullptr))
{
if (!member.type->canLiveOutsideStorage())
continue;
solAssert(!member.type->containsNestedMapping(), "");
pair<u256, unsigned> const& offsets = typeOnStack->storageOffsetsOfMember(member.name);
_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
_context << u256(offsets.second);

View File

@ -355,13 +355,13 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
structType.structDefinition() == sourceType.structDefinition(),
"Struct assignment with conversion."
);
solAssert(!structType.containsNestedMapping(), "");
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
for (auto const& member: structType.members(nullptr))
{
// assign each member that can live outside of storage
TypePointer const& memberType = member.type;
if (!memberType->canLiveOutsideStorage())
continue;
solAssert(memberType->nameable(), "");
TypePointer sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage)
{