Browse Source

Struct types.

cl-refactor
Christian 10 years ago
parent
commit
bbc3a1b37e
  1. 13
      libsolidity/AST.cpp
  2. 5
      libsolidity/AST.h
  3. 17
      libsolidity/ExpressionCompiler.cpp
  4. 29
      libsolidity/NameAndTypeResolver.cpp
  5. 11
      libsolidity/NameAndTypeResolver.h
  6. 55
      libsolidity/Types.cpp
  7. 16
      libsolidity/Types.h
  8. 37
      test/solidityEndToEndTest.cpp
  9. 38
      test/solidityNameAndTypeResolution.cpp

13
libsolidity/AST.cpp

@ -460,8 +460,17 @@ bool FunctionCall::isTypeConversion() const
void MemberAccess::checkTypeRequirements() void MemberAccess::checkTypeRequirements()
{ {
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access not yet implemented.")); m_expression->checkTypeRequirements();
// m_type = ; m_expression->requireLValue();
if (m_expression->getType()->getCategory() != Type::Category::STRUCT)
BOOST_THROW_EXCEPTION(createTypeError("Member access to a non-struct (is " +
m_expression->getType()->toString() + ")"));
StructType const& type = dynamic_cast<StructType const&>(*m_expression->getType());
unsigned memberIndex = type.memberNameToIndex(*m_memberName);
if (memberIndex >= type.getMemberCount())
BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found in " + type.toString()));
m_type = type.getMemberByIndex(memberIndex).getType();
m_isLvalue = true;
} }
void IndexAccess::checkTypeRequirements() void IndexAccess::checkTypeRequirements()

5
libsolidity/AST.h

@ -146,7 +146,7 @@ private:
/** /**
* Parameter list, used as function parameter list and return list. * Parameter list, used as function parameter list and return list.
* None of the parameters is allowed to contain mappings (not even recursively * None of the parameters is allowed to contain mappings (not even recursively
* inside structs), but (@todo) this is not yet enforced. * inside structs).
*/ */
class ParameterList: public ASTNode class ParameterList: public ASTNode
{ {
@ -368,7 +368,6 @@ private:
/** /**
* Statement in which a break statement is legal. * Statement in which a break statement is legal.
* @todo actually check this requirement.
*/ */
class BreakableStatement: public Statement class BreakableStatement: public Statement
{ {
@ -629,6 +628,7 @@ public:
ASTPointer<ASTString> const& _memberName): ASTPointer<ASTString> const& _memberName):
Expression(_location), m_expression(_expression), m_memberName(_memberName) {} Expression(_location), m_expression(_expression), m_memberName(_memberName) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
Expression& getExpression() const { return *m_expression; }
ASTString const& getMemberName() const { return *m_memberName; } ASTString const& getMemberName() const { return *m_memberName; }
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements() override;
@ -651,6 +651,7 @@ public:
Expression& getBaseExpression() const { return *m_base; } Expression& getBaseExpression() const { return *m_base; }
Expression& getIndexExpression() const { return *m_index; } Expression& getIndexExpression() const { return *m_index; }
private: private:
ASTPointer<Expression> m_base; ASTPointer<Expression> m_base;
ASTPointer<Expression> m_index; ASTPointer<Expression> m_index;

17
libsolidity/ExpressionCompiler.cpp

@ -49,7 +49,6 @@ bool ExpressionCompiler::visit(Assignment& _assignment)
{ {
_assignment.getRightHandSide().accept(*this); _assignment.getRightHandSide().accept(*this);
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType());
m_currentLValue.reset();
_assignment.getLeftHandSide().accept(*this); _assignment.getLeftHandSide().accept(*this);
if (asserts(m_currentLValue.isValid())) if (asserts(m_currentLValue.isValid()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved."));
@ -63,6 +62,7 @@ bool ExpressionCompiler::visit(Assignment& _assignment)
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType());
} }
m_currentLValue.storeValue(_assignment); m_currentLValue.storeValue(_assignment);
m_currentLValue.reset();
return false; return false;
} }
@ -90,6 +90,7 @@ void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation)
if (m_currentLValue.storesReferenceOnStack()) if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
m_currentLValue.storeValue(_unaryOperation); m_currentLValue.storeValue(_unaryOperation);
m_currentLValue.reset();
break; break;
case Token::INC: // ++ (pre- or postfix) case Token::INC: // ++ (pre- or postfix)
case Token::DEC: // -- (pre- or postfix) case Token::DEC: // -- (pre- or postfix)
@ -113,6 +114,7 @@ void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation)
if (m_currentLValue.storesReferenceOnStack()) if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
m_currentLValue.storeValue(_unaryOperation, !_unaryOperation.isPrefixOperation()); m_currentLValue.storeValue(_unaryOperation, !_unaryOperation.isPrefixOperation());
m_currentLValue.reset();
break; break;
case Token::ADD: // + case Token::ADD: // +
// unary add, so basically no-op // unary add, so basically no-op
@ -182,10 +184,10 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall)
arguments[i]->accept(*this); arguments[i]->accept(*this);
appendTypeConversion(*arguments[i]->getType(), *function.getParameters()[i]->getType()); appendTypeConversion(*arguments[i]->getType(), *function.getParameters()[i]->getType());
} }
m_currentLValue.reset();
_functionCall.getExpression().accept(*this); _functionCall.getExpression().accept(*this);
if (asserts(m_currentLValue.isInCode())) if (asserts(m_currentLValue.isInCode()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Code reference expected.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Code reference expected."));
m_currentLValue.reset();
m_context.appendJump(); m_context.appendJump();
m_context << returnLabel; m_context << returnLabel;
@ -201,9 +203,16 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall)
return false; return false;
} }
void ExpressionCompiler::endVisit(MemberAccess&) void ExpressionCompiler::endVisit(MemberAccess& _memberAccess)
{ {
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access not yet implemented.")); if (asserts(m_currentLValue.isInStorage()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to a non-storage value."));
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType());
unsigned memberIndex = type.memberNameToIndex(_memberAccess.getMemberName());
if (asserts(memberIndex <= type.getMemberCount()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member not found in struct during compilation."));
m_context << type.getStorageOffsetOfMember(memberIndex) << eth::Instruction::ADD;
m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess);
} }
bool ExpressionCompiler::visit(IndexAccess& _indexAccess) bool ExpressionCompiler::visit(IndexAccess& _indexAccess)

29
libsolidity/NameAndTypeResolver.cpp

@ -37,7 +37,10 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
reset(); reset();
DeclarationRegistrationHelper registrar(m_scopes, _contract); DeclarationRegistrationHelper registrar(m_scopes, _contract);
m_currentScope = &m_scopes[&_contract]; m_currentScope = &m_scopes[&_contract];
//@todo structs for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs())
ReferencesResolver resolver(*structDef, *this, nullptr);
for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs())
checkForRecursion(*structDef);
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables())
ReferencesResolver resolver(*variable, *this, nullptr); ReferencesResolver resolver(*variable, *this, nullptr);
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
@ -70,6 +73,24 @@ Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name
return m_currentScope->resolveName(_name, _recursive); return m_currentScope->resolveName(_name, _recursive);
} }
void NameAndTypeResolver::checkForRecursion(StructDefinition const& _struct)
{
set<StructDefinition const*> definitionsSeen;
vector<StructDefinition const*> queue = {&_struct};
while (!queue.empty())
{
StructDefinition const* def = queue.back();
queue.pop_back();
if (definitionsSeen.count(def))
BOOST_THROW_EXCEPTION(ParserError() << errinfo_sourceLocation(def->getLocation())
<< errinfo_comment("Recursive struct definition."));
definitionsSeen.insert(def);
for (ASTPointer<VariableDeclaration> const& member: def->getMembers())
if (member->getType()->getCategory() == Type::Category::STRUCT)
queue.push_back(dynamic_cast<UserDefinedTypeName&>(*member->getTypeName()).getReferencedStruct());
}
}
void NameAndTypeResolver::reset() void NameAndTypeResolver::reset()
{ {
m_scopes.clear(); m_scopes.clear();
@ -163,8 +184,8 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
} }
ReferencesResolver::ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, ReferencesResolver::ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver,
ParameterList* _returnParameters): ParameterList* _returnParameters, bool _allowLazyTypes):
m_resolver(_resolver), m_returnParameters(_returnParameters) m_resolver(_resolver), m_returnParameters(_returnParameters), m_allowLazyTypes(_allowLazyTypes)
{ {
_root.accept(*this); _root.accept(*this);
} }
@ -175,6 +196,8 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable)
// or mapping // or mapping
if (_variable.getTypeName()) if (_variable.getTypeName())
_variable.setType(_variable.getTypeName()->toType()); _variable.setType(_variable.getTypeName()->toType());
else if (!m_allowLazyTypes)
BOOST_THROW_EXCEPTION(_variable.createTypeError("Explicit type needed."));
// otherwise we have a "var"-declaration whose type is resolved by the first assignment // otherwise we have a "var"-declaration whose type is resolved by the first assignment
} }

11
libsolidity/NameAndTypeResolver.h

@ -55,10 +55,13 @@ public:
Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true); Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true);
private: private:
/// Throws if @a _struct contains a recursive loop. Note that recursion via mappings is fine.
void checkForRecursion(StructDefinition const& _struct);
void reset(); void reset();
/// Maps nodes declaring a scope to scopes, i.e. ContractDefinition, FunctionDeclaration and /// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration,
/// StructDefinition (@todo not yet implemented), where nullptr denotes the global scope. /// where nullptr denotes the global scope. Note that structs are not scope since they do
/// not contain code.
std::map<ASTNode const*, Scope> m_scopes; std::map<ASTNode const*, Scope> m_scopes;
Scope* m_currentScope; Scope* m_currentScope;
@ -99,7 +102,8 @@ private:
class ReferencesResolver: private ASTVisitor class ReferencesResolver: private ASTVisitor
{ {
public: public:
ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, ParameterList* _returnParameters); ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver,
ParameterList* _returnParameters, bool _allowLazyTypes = true);
private: private:
virtual void endVisit(VariableDeclaration& _variable) override; virtual void endVisit(VariableDeclaration& _variable) override;
@ -110,6 +114,7 @@ private:
NameAndTypeResolver& m_resolver; NameAndTypeResolver& m_resolver;
ParameterList* m_returnParameters; ParameterList* m_returnParameters;
bool m_allowLazyTypes;
}; };
} }

55
libsolidity/Types.cpp

@ -80,7 +80,7 @@ shared_ptr<Type> Type::forLiteral(Literal const& _literal)
case Token::NUMBER: case Token::NUMBER:
return IntegerType::smallestTypeForLiteral(_literal.getValue()); return IntegerType::smallestTypeForLiteral(_literal.getValue());
case Token::STRING_LITERAL: case Token::STRING_LITERAL:
return shared_ptr<Type>(); // @todo return shared_ptr<Type>(); // @todo add string literals
default: default:
return shared_ptr<Type>(); return shared_ptr<Type>();
} }
@ -231,6 +231,48 @@ u256 StructType::getStorageSize() const
return max<u256>(1, size); return max<u256>(1, size);
} }
bool StructType::canLiveOutsideStorage() const
{
for (unsigned i = 0; i < getMemberCount(); ++i)
if (!getMemberByIndex(i).getType()->canLiveOutsideStorage())
return false;
return true;
}
string StructType::toString() const
{
return string("struct ") + m_struct.getName();
}
unsigned StructType::getMemberCount() const
{
return m_struct.getMembers().size();
}
unsigned StructType::memberNameToIndex(string const& _name) const
{
vector<ASTPointer<VariableDeclaration>> const& members = m_struct.getMembers();
for (unsigned index = 0; index < members.size(); ++index)
if (members[index]->getName() == _name)
return index;
return unsigned(-1);
}
VariableDeclaration const& StructType::getMemberByIndex(unsigned _index) const
{
return *m_struct.getMembers()[_index];
}
u256 StructType::getStorageOffsetOfMember(unsigned _index) const
{
//@todo cache member offset?
u256 offset;
vector<ASTPointer<VariableDeclaration>> const& members = m_struct.getMembers();
for (unsigned index = 0; index < _index; ++index)
offset += getMemberByIndex(index).getType()->getStorageSize();
return offset;
}
bool FunctionType::operator==(Type const& _other) const bool FunctionType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
@ -239,6 +281,12 @@ bool FunctionType::operator==(Type const& _other) const
return other.m_function == m_function; return other.m_function == m_function;
} }
string FunctionType::toString() const
{
//@todo nice string for function types
return "function(...)returns(...)";
}
bool MappingType::operator==(Type const& _other) const bool MappingType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
@ -247,6 +295,11 @@ bool MappingType::operator==(Type const& _other) const
return *other.m_keyType == *m_keyType && *other.m_valueType == *m_valueType; return *other.m_keyType == *m_keyType && *other.m_valueType == *m_valueType;
} }
string MappingType::toString() const
{
return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")";
}
bool TypeType::operator==(Type const& _other) const bool TypeType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())

16
libsolidity/Types.h

@ -184,9 +184,14 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual u256 getStorageSize() const; virtual u256 getStorageSize() const;
//@todo it can, if its members can virtual bool canLiveOutsideStorage() const;
virtual bool canLiveOutsideStorage() const { return false; } virtual std::string toString() const override;
virtual std::string toString() const override { return "struct{...}"; }
unsigned getMemberCount() const;
/// Returns the index of the member with name @a _name or unsigned(-1) if it does not exist.
unsigned memberNameToIndex(std::string const& _name) const;
VariableDeclaration const& getMemberByIndex(unsigned _index) const;
u256 getStorageOffsetOfMember(unsigned _index) const;
private: private:
StructDefinition const& m_struct; StructDefinition const& m_struct;
@ -204,7 +209,7 @@ public:
FunctionDefinition const& getFunction() const { return m_function; } FunctionDefinition const& getFunction() const { return m_function; }
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override { return "function(...)returns(...)"; } virtual std::string toString() const override;
virtual u256 getStorageSize() const { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } virtual u256 getStorageSize() const { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); }
virtual bool canLiveOutsideStorage() const { return false; } virtual bool canLiveOutsideStorage() const { return false; }
@ -223,11 +228,12 @@ public:
m_keyType(_keyType), m_valueType(_valueType) {} m_keyType(_keyType), m_valueType(_valueType) {}
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override { return "mapping(...=>...)"; } virtual std::string toString() const override;
virtual bool canLiveOutsideStorage() const { return false; } virtual bool canLiveOutsideStorage() const { return false; }
std::shared_ptr<Type const> getKeyType() const { return m_keyType; } std::shared_ptr<Type const> getKeyType() const { return m_keyType; }
std::shared_ptr<Type const> getValueType() const { return m_valueType; } std::shared_ptr<Type const> getValueType() const { return m_valueType; }
private: private:
std::shared_ptr<Type const> m_keyType; std::shared_ptr<Type const> m_keyType;
std::shared_ptr<Type const> m_valueType; std::shared_ptr<Type const> m_valueType;

37
test/solidityEndToEndTest.cpp

@ -663,6 +663,43 @@ BOOST_AUTO_TEST_CASE(multi_level_mapping)
testSolidityAgainstCpp(0, f, u256(5), u256(4), u256(0)); testSolidityAgainstCpp(0, f, u256(5), u256(4), u256(0));
} }
BOOST_AUTO_TEST_CASE(structs)
{
char const* sourceCode = "contract test {\n"
" struct s1 {\n"
" uint8 x;\n"
" bool y;\n"
" }\n"
" struct s2 {\n"
" uint32 z;\n"
" s1 s1data;\n"
" mapping(uint8 => s2) recursive;\n"
" }\n"
" s2 data;\n"
" function check() returns (bool ok) {\n"
" return data.z == 1 && data.s1data.x == 2 && \n"
" data.s1data.y == true && \n"
" data.recursive[3].recursive[4].z == 5 && \n"
" data.recursive[4].recursive[3].z == 6 && \n"
" data.recursive[0].s1data.y == false && \n"
" data.recursive[4].z == 9;\n"
" }\n"
" function set() {\n"
" data.z = 1;\n"
" data.s1data.x = 2;\n"
" data.s1data.y = true;\n"
" data.recursive[3].recursive[4].z = 5;\n"
" data.recursive[4].recursive[3].z = 6;\n"
" data.recursive[0].s1data.y = false;\n"
" data.recursive[4].z = 9;\n"
" }\n"
"}\n";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction(0) == bytes({0x00}));
BOOST_CHECK(callContractFunction(1) == bytes());
BOOST_CHECK(callContractFunction(0) == bytes({0x01}));
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
} }

38
test/solidityNameAndTypeResolution.cpp

@ -121,6 +121,44 @@ BOOST_AUTO_TEST_CASE(reference_to_later_declaration)
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
} }
BOOST_AUTO_TEST_CASE(struct_definition_directly_recursive)
{
char const* text = "contract test {\n"
" struct MyStructName {\n"
" address addr;\n"
" MyStructName x;\n"
" }\n"
"}\n";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), ParserError);
}
BOOST_AUTO_TEST_CASE(struct_definition_indirectly_recursive)
{
char const* text = "contract test {\n"
" struct MyStructName1 {\n"
" address addr;\n"
" uint256 count;\n"
" MyStructName2 x;\n"
" }\n"
" struct MyStructName2 {\n"
" MyStructName1 x;\n"
" }\n"
"}\n";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), ParserError);
}
BOOST_AUTO_TEST_CASE(struct_definition_recursion_via_mapping)
{
char const* text = "contract test {\n"
" struct MyStructName1 {\n"
" address addr;\n"
" uint256 count;\n"
" mapping(uint => MyStructName1) x;\n"
" }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
}
BOOST_AUTO_TEST_CASE(type_inference_smoke_test) BOOST_AUTO_TEST_CASE(type_inference_smoke_test)
{ {
char const* text = "contract test {\n" char const* text = "contract test {\n"

Loading…
Cancel
Save