diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index e8bdecf31..168a095c8 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -51,6 +51,40 @@ void StructDefinition::accept(ASTVisitor& _visitor) _visitor.endVisit(*this); } +void StructDefinition::checkValidityOfMembers() +{ + checkMemberTypes(); + checkRecursion(); +} + +void StructDefinition::checkMemberTypes() +{ + for (ASTPointer const& member: getMembers()) + if (!member->getType()->canBeStored()) + BOOST_THROW_EXCEPTION(member->createTypeError("Type cannot be used in struct.")); +} + +void StructDefinition::checkRecursion() +{ + set definitionsSeen; + vector queue = {this}; + 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 const& member: def->getMembers()) + if (member->getType()->getCategory() == Type::Category::STRUCT) + { + UserDefinedTypeName const& typeName = dynamic_cast(*member->getTypeName()); + queue.push_back(&dynamic_cast(*typeName.getReferencedDeclaration())); + } + } +} + void ParameterList::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) @@ -258,7 +292,7 @@ void Literal::accept(ASTVisitor& _visitor) _visitor.endVisit(*this); } -TypeError ASTNode::createTypeError(string const& _description) +TypeError ASTNode::createTypeError(string const& _description) const { return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description); } @@ -338,8 +372,6 @@ void VariableDefinition::checkTypeRequirements() m_variable->setType(m_value->getType()); } } - if (m_variable->getType() && !m_variable->getType()->canLiveOutsideStorage()) - BOOST_THROW_EXCEPTION(m_variable->createTypeError("Type is required to live outside storage.")); } void Assignment::checkTypeRequirements() @@ -432,8 +464,6 @@ void FunctionCall::checkTypeRequirements() } else { - m_expression->requireLValue(); - //@todo would be nice to create a struct type from the arguments // and then ask if that is implicitly convertible to the struct represented by the // function parameters @@ -461,29 +491,23 @@ bool FunctionCall::isTypeConversion() const void MemberAccess::checkTypeRequirements() { m_expression->checkTypeRequirements(); - 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(*m_expression->getType()); - unsigned memberIndex = type.memberNameToIndex(*m_memberName); - if (memberIndex >= type.getMemberCount()) + Type const& type = *m_expression->getType(); + m_type = type.getMemberType(*m_memberName); + if (!m_type) BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found in " + type.toString())); - m_type = type.getMemberByIndex(memberIndex).getType(); - m_isLvalue = true; + m_isLvalue = (type.getCategory() == Type::Category::STRUCT && m_type->getCategory() != Type::Category::MAPPING); } void IndexAccess::checkTypeRequirements() { m_base->checkTypeRequirements(); - m_base->requireLValue(); if (m_base->getType()->getCategory() != Type::Category::MAPPING) BOOST_THROW_EXCEPTION(m_base->createTypeError("Indexed expression has to be a mapping (is " + m_base->getType()->toString() + ")")); MappingType const& type = dynamic_cast(*m_base->getType()); m_index->expectType(*type.getKeyType()); m_type = type.getValueType(); - m_isLvalue = true; + m_isLvalue = m_type->getCategory() != Type::Category::MAPPING; } void Identifier::checkTypeRequirements() @@ -515,7 +539,6 @@ void Identifier::checkTypeRequirements() // Calling a function (e.g. function(12), otherContract.function(34)) does not do a type // conversion. m_type = make_shared(*functionDef); - m_isLvalue = true; return; } ContractDefinition* contractDef = dynamic_cast(m_referencedDeclaration); @@ -524,6 +547,12 @@ void Identifier::checkTypeRequirements() m_type = make_shared(make_shared(*contractDef)); return; } + MagicVariableDeclaration* magicVariable = dynamic_cast(m_referencedDeclaration); + if (magicVariable) + { + m_type = magicVariable->getType(); + return; + } BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration reference of unknown/forbidden type.")); } diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 80c7dd198..89f268b33 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -66,7 +66,7 @@ public: /// Creates a @ref TypeError exception and decorates it with the location of the node and /// the given description - TypeError createTypeError(std::string const& _description); + TypeError createTypeError(std::string const& _description) const; ///@{ ///@name equality operators @@ -122,6 +122,7 @@ public: /// Returns the functions that make up the calling interface in the intended order. std::vector getInterfaceFunctions() const; + private: std::vector> m_definedStructs; std::vector> m_stateVariables; @@ -139,7 +140,14 @@ public: std::vector> const& getMembers() const { return m_members; } + /// Checks that the members do not include any recursive structs and have valid types + /// (e.g. no functions). + void checkValidityOfMembers(); + private: + void checkMemberTypes(); + void checkRecursion(); + std::vector> m_members; }; @@ -188,6 +196,7 @@ public: /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. void checkTypeRequirements(); + private: bool m_isPublic; ASTPointer m_parameters; @@ -210,7 +219,6 @@ public: Declaration(_location, _name), m_typeName(_type) {} virtual void accept(ASTVisitor& _visitor) override; - bool isTypeGivenExplicitly() const { return bool(m_typeName); } TypeName* getTypeName() const { return m_typeName.get(); } /// Returns the declared or inferred type. Can be an empty pointer if no type was explicitly @@ -224,6 +232,27 @@ private: std::shared_ptr m_type; ///< derived type, initially empty }; +/** + * Pseudo AST node that is used as declaration for "this", "msg", "tx" and "block" when the + * identifier is encountered. Will never have a valid location in the source code. + */ +class MagicVariableDeclaration: public Declaration +{ +public: + enum class VariableKind { THIS, MSG, TX, BLOCK }; + MagicVariableDeclaration(VariableKind _kind, ASTString const& _name, std::shared_ptr const& _type): + Declaration(Location(), std::make_shared(_name)), m_kind(_kind), m_type(_type) {} + virtual void accept(ASTVisitor&) override { BOOST_THROW_EXCEPTION(InternalCompilerError() + << errinfo_comment("MagicVariableDeclaration used inside real AST.")); } + + std::shared_ptr const& getType() const { return m_type; } + VariableKind getKind() const { return m_kind; } + +private: + VariableKind m_kind; + std::shared_ptr m_type; +}; + /// Types /// @{ @@ -238,6 +267,7 @@ public: /// Retrieve the element of the type hierarchy this node refers to. Can return an empty shared /// pointer until the types have been resolved using the @ref NameAndTypeResolver. + /// If it returns an empty shared pointer after that, this indicates that the type was not found. virtual std::shared_ptr toType() const = 0; }; @@ -263,8 +293,7 @@ private: }; /** - * Name referring to a user-defined type (i.e. a struct). - * @todo some changes are necessary if this is also used to refer to contract types later + * Name referring to a user-defined type (i.e. a struct, contract, etc.). */ class UserDefinedTypeName: public TypeName { @@ -275,13 +304,13 @@ public: virtual std::shared_ptr toType() const override { return Type::fromUserDefinedTypeName(*this); } ASTString const& getName() const { return *m_name; } - void setReferencedStruct(StructDefinition& _referencedStruct) { m_referencedStruct = &_referencedStruct; } - StructDefinition const* getReferencedStruct() const { return m_referencedStruct; } + void setReferencedDeclaration(Declaration& _referencedDeclaration) { m_referencedDeclaration = &_referencedDeclaration; } + Declaration const* getReferencedDeclaration() const { return m_referencedDeclaration; } private: ASTPointer m_name; - StructDefinition* m_referencedStruct; + Declaration* m_referencedDeclaration; }; /** diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index 2b0bd8869..a369c8a79 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -40,6 +40,7 @@ class StructDefinition; class ParameterList; class FunctionDefinition; class VariableDeclaration; +class MagicVariableDeclaration; class TypeName; class ElementaryTypeName; class UserDefinedTypeName; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index a3865bc30..a82ecd95a 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -32,17 +32,13 @@ using namespace std; namespace dev { namespace solidity { -bytes Compiler::compile(ContractDefinition& _contract, bool _optimize) -{ - Compiler compiler; - compiler.compileContract(_contract); - return compiler.m_context.getAssembledBytecode(_optimize); -} - -void Compiler::compileContract(ContractDefinition& _contract) +void Compiler::compileContract(ContractDefinition& _contract, vector const& _magicGlobals) { m_context = CompilerContext(); // clear it just in case + for (MagicVariableDeclaration const* variable: _magicGlobals) + m_context.addMagicGlobal(*variable); + for (ASTPointer const& function: _contract.getDefinedFunctions()) if (function->getName() != _contract.getName()) // don't add the constructor here m_context.addFunction(*function); @@ -328,7 +324,8 @@ bool Compiler::visit(ExpressionStatement& _expressionStatement) { Expression& expression = _expressionStatement.getExpression(); ExpressionCompiler::compileExpression(m_context, expression); - if (expression.getType()->getCategory() != Type::Category::VOID) + Type::Category category = expression.getType()->getCategory(); + if (category != Type::Category::VOID && category != Type::Category::MAGIC) m_context << eth::Instruction::POP; return false; } diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 3887d3b5b..70e6c44f2 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -32,13 +32,10 @@ class Compiler: private ASTVisitor public: Compiler(): m_returnTag(m_context.newTag()) {} - void compileContract(ContractDefinition& _contract); + void compileContract(ContractDefinition& _contract, std::vector const& _magicGlobals); bytes getAssembledBytecode(bool _optimize = false) { return m_context.getAssembledBytecode(_optimize); } void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); } - /// Compile the given contract and return the EVM bytecode. - static bytes compile(ContractDefinition& _contract, bool _optimize); - private: /// Creates a new compiler context / assembly, packs the current code into the data part and /// adds the constructor code. diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index 3c1acdfa7..b89a8e5b5 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -30,6 +30,11 @@ using namespace std; namespace dev { namespace solidity { +void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration) +{ + m_magicGlobals.insert(&_declaration); +} + void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) { m_stateVariables[&_declaration] = m_stateVariablesSize; diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index e624222dd..6a48e1485 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -40,6 +40,7 @@ class CompilerContext public: CompilerContext(): m_stateVariablesSize(0) {} + void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration); void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); } void initializeLocalVariables(unsigned _numVariables); @@ -48,6 +49,7 @@ public: void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } + bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration); } bool isFunctionDefinition(Declaration const* _declaration) const { return m_functionEntryLabels.count(_declaration); } bool isLocalVariable(Declaration const* _declaration) const; bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration); } @@ -90,6 +92,8 @@ public: private: eth::Assembly m_asm; + /// Magic global variables like msg, tx or this, distinguished by type. + std::set m_magicGlobals; /// Size of the state variables, offset of next variable to be added. u256 m_stateVariablesSize; /// Storage offsets of state variables diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index d87c27916..6535e00d4 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -45,7 +46,9 @@ void CompilerStack::parse() if (!m_scanner) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Source not available.")); m_contractASTNode = Parser().parse(m_scanner); - NameAndTypeResolver().resolveNamesAndTypes(*m_contractASTNode); + m_globalContext = make_shared(); + m_globalContext->setCurrentContract(*m_contractASTNode); + NameAndTypeResolver(m_globalContext->getDeclarations()).resolveNamesAndTypes(*m_contractASTNode); m_parseSuccessful = true; } @@ -61,7 +64,7 @@ bytes const& CompilerStack::compile(bool _optimize) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); m_bytecode.clear(); m_compiler = make_shared(); - m_compiler->compileContract(*m_contractASTNode); + m_compiler->compileContract(*m_contractASTNode, m_globalContext->getMagicVariables()); return m_bytecode = m_compiler->getAssembledBytecode(_optimize); } diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index 2fb505897..6cae8660f 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -30,9 +30,11 @@ namespace dev { namespace solidity { -class Scanner; // forward -class ContractDefinition; // forward -class Compiler; // forward +// forward declarations +class Scanner; +class ContractDefinition; +class Compiler; +class GlobalContext; /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. @@ -71,6 +73,7 @@ public: private: std::shared_ptr m_scanner; + std::shared_ptr m_globalContext; std::shared_ptr m_contractASTNode; bool m_parseSuccessful; std::string m_interface; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index f37ce39ce..b1a49457c 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -162,15 +162,25 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) { if (_functionCall.isTypeConversion()) { - //@todo we only have integers and bools for now which cannot be explicitly converted + //@todo struct construction if (asserts(_functionCall.getArguments().size() == 1)) BOOST_THROW_EXCEPTION(InternalCompilerError()); Expression& firstArgument = *_functionCall.getArguments().front(); firstArgument.accept(*this); - appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); + if (firstArgument.getType()->getCategory() == Type::Category::CONTRACT && + _functionCall.getType()->getCategory() == Type::Category::INTEGER) + { + // explicit type conversion contract -> address, nothing to do. + } + else + { + appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); + } } else { + //@todo: check for "external call" (to be stored in type) + // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values FunctionDefinition const& function = dynamic_cast(*_functionCall.getExpression().getType()).getFunction(); @@ -185,9 +195,6 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) appendTypeConversion(*arguments[i]->getType(), *function.getParameters()[i]->getType()); } _functionCall.getExpression().accept(*this); - if (asserts(m_currentLValue.isInCode())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Code reference expected.")); - m_currentLValue.reset(); m_context.appendJump(); m_context << returnLabel; @@ -205,22 +212,36 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) void ExpressionCompiler::endVisit(MemberAccess& _memberAccess) { - if (asserts(m_currentLValue.isInStorage())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to a non-storage value.")); - StructType const& type = dynamic_cast(*_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); + switch (_memberAccess.getExpression().getType()->getCategory()) + { + case Type::Category::INTEGER: + if (asserts(_memberAccess.getMemberName() == "balance")) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); + m_context << eth::Instruction::BALANCE; + break; + case Type::Category::CONTRACT: + // call function + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Contract variables not yet implemented.")); + break; + case Type::Category::MAGIC: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Magic variables not yet implemented.")); + break; + case Type::Category::STRUCT: + { + StructType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); + m_context << type.getStorageOffsetOfMember(_memberAccess.getMemberName()) << eth::Instruction::ADD; + m_currentLValue = LValue(m_context, LValue::STORAGE); + m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess); + break; + } + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type.")); + } } bool ExpressionCompiler::visit(IndexAccess& _indexAccess) { - m_currentLValue.reset(); _indexAccess.getBaseExpression().accept(*this); - if (asserts(m_currentLValue.isInStorage())) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Index access to a non-storage value.")); _indexAccess.getIndexExpression().accept(*this); appendTypeConversion(*_indexAccess.getIndexExpression().getType(), *dynamic_cast(*_indexAccess.getBaseExpression().getType()).getKeyType(), @@ -237,8 +258,25 @@ bool ExpressionCompiler::visit(IndexAccess& _indexAccess) void ExpressionCompiler::endVisit(Identifier& _identifier) { - m_currentLValue.fromDeclaration(_identifier, *_identifier.getReferencedDeclaration()); - m_currentLValue.retrieveValueIfLValueNotRequested(_identifier); + Declaration* declaration = _identifier.getReferencedDeclaration(); + if (MagicVariableDeclaration* magicVar = dynamic_cast(declaration)) + { + if (magicVar->getKind() == MagicVariableDeclaration::VariableKind::THIS) + m_context << eth::Instruction::ADDRESS; + return; + } + if (FunctionDefinition* functionDef = dynamic_cast(declaration)) + { + m_context << m_context.getFunctionEntryLabel(*functionDef).pushTag(); + return; + } + if (VariableDeclaration* varDef = dynamic_cast(declaration)) + { + m_currentLValue.fromIdentifier(_identifier, *_identifier.getReferencedDeclaration()); + m_currentLValue.retrieveValueIfLValueNotRequested(_identifier); + return; + } + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); } void ExpressionCompiler::endVisit(Literal& _literal) @@ -405,9 +443,6 @@ void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bo { switch (m_type) { - case CODE: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Tried to retrieve value of a function.")); - break; case STACK: { unsigned stackPos = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); @@ -418,11 +453,15 @@ void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bo break; } case STORAGE: + if (!_expression.getType()->isValueType()) + break; // no distinction between value and reference for non-value types if (!_remove) *m_context << eth::Instruction::DUP1; *m_context << eth::Instruction::SLOAD; break; case MEMORY: + if (!_expression.getType()->isValueType()) + break; // no distinction between value and reference for non-value types BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) << errinfo_comment("Location type not yet implemented.")); break; @@ -450,15 +489,15 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool break; } case LValue::STORAGE: + if (!_expression.getType()->isValueType()) + break; // no distinction between value and reference for non-value types if (!_move) *m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; *m_context << eth::Instruction::SSTORE; break; - case LValue::CODE: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) - << errinfo_comment("Location type does not support assignment.")); - break; case LValue::MEMORY: + if (!_expression.getType()->isValueType()) + break; // no distinction between value and reference for non-value types BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) << errinfo_comment("Location type not yet implemented.")); break; @@ -469,7 +508,7 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool } } -void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(const Expression& _expression) +void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression const& _expression) { if (!_expression.lvalueRequested()) { @@ -478,7 +517,7 @@ void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(const Express } } -void ExpressionCompiler::LValue::fromDeclaration( Expression const& _expression, Declaration const& _declaration) +void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) { if (m_context->isLocalVariable(&_declaration)) { @@ -490,13 +529,8 @@ void ExpressionCompiler::LValue::fromDeclaration( Expression const& _expression, m_type = STORAGE; *m_context << m_context->getStorageLocationOfVariable(_declaration); } - else if (m_context->isFunctionDefinition(&_declaration)) - { - m_type = CODE; - *m_context << m_context->getFunctionEntryLabel(dynamic_cast(_declaration)).pushTag(); - } else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) << errinfo_comment("Identifier type not supported or identifier not found.")); } diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index f52da29ec..3ed7848b5 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -83,31 +83,30 @@ private: /** * Helper class to store and retrieve lvalues to and from various locations. - * All types except STACK store a reference in a slot on the stack, STACK just stores the - * base stack offset of the variable in @a m_baseStackOffset. + * All types except STACK store a reference in a slot on the stack, STACK just + * stores the base stack offset of the variable in @a m_baseStackOffset. */ class LValue { public: - enum LValueType { NONE, CODE, STACK, MEMORY, STORAGE }; + enum LValueType { NONE, STACK, MEMORY, STORAGE }; explicit LValue(CompilerContext& _compilerContext): m_context(&_compilerContext) { reset(); } LValue(CompilerContext& _compilerContext, LValueType _type, unsigned _baseStackOffset = 0): m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset) {} /// Set type according to the declaration and retrieve the reference. - /// @a _expression is the current expression, used for error reporting. - void fromDeclaration(Expression const& _expression, Declaration const& _declaration); + /// @a _expression is the current expression + void fromIdentifier(Identifier const& _identifier, Declaration const& _declaration); void reset() { m_type = NONE; m_baseStackOffset = 0; } bool isValid() const { return m_type != NONE; } - bool isInCode() const { return m_type == CODE; } bool isInOnStack() const { return m_type == STACK; } bool isInMemory() const { return m_type == MEMORY; } bool isInStorage() const { return m_type == STORAGE; } /// @returns true if this lvalue reference type occupies a slot on the stack. - bool storesReferenceOnStack() const { return m_type == STORAGE || m_type == MEMORY || m_type == CODE; } + bool storesReferenceOnStack() const { return m_type == STORAGE || m_type == MEMORY; } /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, /// also removes the reference from the stack (note that is does not reset the type to @a NONE). diff --git a/libsolidity/GlobalContext.cpp b/libsolidity/GlobalContext.cpp new file mode 100644 index 000000000..f2e6d9ce9 --- /dev/null +++ b/libsolidity/GlobalContext.cpp @@ -0,0 +1,82 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Container of the (implicit and explicit) global objects. + */ + +#include +#include +#include +#include + +using namespace std; + +namespace dev +{ +namespace solidity +{ + +GlobalContext::GlobalContext() +{ + // CurrentContract this; // @todo type depends on context -> switch prior to entering contract + // Message msg; + // Transaction tx; + // Block block; + + //@todo type will be a custom complex type, maybe the same type class for msg tx and block. + //addVariable("msg", ); +} + +void GlobalContext::setCurrentContract(ContractDefinition const& _contract) +{ + m_currentContract = &_contract; +} + +vector GlobalContext::getDeclarations() const +{ + vector declarations; + declarations.reserve(m_magicVariables.size() + 1); + for (ASTPointer const& variable: m_magicVariables) + declarations.push_back(variable.get()); + declarations.push_back(getCurrentThis()); + return declarations; +} + +MagicVariableDeclaration*GlobalContext::getCurrentThis() const +{ + if (!m_thisPointer[m_currentContract]) + m_thisPointer[m_currentContract] = make_shared( + MagicVariableDeclaration::VariableKind::THIS, + "this", make_shared(*m_currentContract)); + return m_thisPointer[m_currentContract].get(); + +} + +vector GlobalContext::getMagicVariables() const +{ + vector declarations; + declarations.reserve(m_magicVariables.size() + 1); + for (ASTPointer const& variable: m_magicVariables) + declarations.push_back(variable.get()); + declarations.push_back(getCurrentThis()); + return declarations; +} + +} +} diff --git a/libsolidity/GlobalContext.h b/libsolidity/GlobalContext.h new file mode 100644 index 000000000..0166734ca --- /dev/null +++ b/libsolidity/GlobalContext.h @@ -0,0 +1,62 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Container of the (implicit and explicit) global objects. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class Type; // forward + +/** + * Container for all global objects which look like AST nodes, but are not part of the AST + * that is currently being compiled. + * @note must not be destroyed or moved during compilation as its objects can be referenced from + * other objects. + */ +class GlobalContext: private boost::noncopyable +{ +public: + GlobalContext(); + void setCurrentContract(ContractDefinition const& _contract); + + std::vector getMagicVariables() const; + std::vector getDeclarations() const; + +private: + MagicVariableDeclaration* getCurrentThis() const; + std::vector> m_magicVariables; + ContractDefinition const* m_currentContract; + std::map> mutable m_thisPointer; +}; + +} +} diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index aa7726582..225f2a78a 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -32,15 +32,20 @@ namespace solidity { +NameAndTypeResolver::NameAndTypeResolver(std::vector const& _globals) +{ + for (Declaration* declaration: _globals) + m_scopes[nullptr].registerDeclaration(*declaration); +} + void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) { - reset(); DeclarationRegistrationHelper registrar(m_scopes, _contract); m_currentScope = &m_scopes[&_contract]; for (ASTPointer const& structDef: _contract.getDefinedStructs()) ReferencesResolver resolver(*structDef, *this, nullptr); for (ASTPointer const& structDef: _contract.getDefinedStructs()) - checkForRecursion(*structDef); + structDef->checkValidityOfMembers(); for (ASTPointer const& variable: _contract.getStateVariables()) ReferencesResolver resolver(*variable, *this, nullptr); for (ASTPointer const& function: _contract.getDefinedFunctions()) @@ -73,30 +78,6 @@ Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name return m_currentScope->resolveName(_name, _recursive); } -void NameAndTypeResolver::checkForRecursion(StructDefinition const& _struct) -{ - set definitionsSeen; - vector 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 const& member: def->getMembers()) - if (member->getType()->getCategory() == Type::Category::STRUCT) - queue.push_back(dynamic_cast(*member->getTypeName()).getReferencedStruct()); - } -} - -void NameAndTypeResolver::reset() -{ - m_scopes.clear(); - m_currentScope = nullptr; -} - DeclarationRegistrationHelper::DeclarationRegistrationHelper(map& _scopes, ASTNode& _astRoot): m_scopes(_scopes), m_currentScope(&m_scopes[nullptr]) @@ -195,7 +176,11 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) // endVisit because the internal type needs resolving if it is a user defined type // or mapping if (_variable.getTypeName()) + { _variable.setType(_variable.getTypeName()->toType()); + if (!_variable.getType()) + BOOST_THROW_EXCEPTION(_variable.getTypeName()->createTypeError("Invalid type name")); + } 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 @@ -221,11 +206,7 @@ bool ReferencesResolver::visit(UserDefinedTypeName& _typeName) if (!declaration) BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_typeName.getLocation()) << errinfo_comment("Undeclared identifier.")); - StructDefinition* referencedStruct = dynamic_cast(declaration); - //@todo later, contracts are also valid types - if (!referencedStruct) - BOOST_THROW_EXCEPTION(_typeName.createTypeError("Identifier does not name a type name.")); - _typeName.setReferencedStruct(*referencedStruct); + _typeName.setReferencedDeclaration(*declaration); return false; } diff --git a/libsolidity/NameAndTypeResolver.h b/libsolidity/NameAndTypeResolver.h index d335807e5..64f3c89db 100644 --- a/libsolidity/NameAndTypeResolver.h +++ b/libsolidity/NameAndTypeResolver.h @@ -41,8 +41,7 @@ namespace solidity class NameAndTypeResolver: private boost::noncopyable { public: - NameAndTypeResolver() {} - + explicit NameAndTypeResolver(std::vector const& _globals); void resolveNamesAndTypes(ContractDefinition& _contract); /// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted, diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 895b8ae7a..dd18a320f 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -683,7 +683,6 @@ Token::Value Scanner::scanNumber(char _charSeen) KEYWORD("switch", Token::SWITCH) \ KEYWORD_GROUP('t') \ KEYWORD("text", Token::TEXT) \ - KEYWORD("this", Token::THIS) \ KEYWORD("true", Token::TRUE_LITERAL) \ KEYWORD_GROUP('u') \ KEYWORD("uint", Token::UINT) \ diff --git a/libsolidity/Token.h b/libsolidity/Token.h index 524487521..8974ca1ad 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -160,7 +160,6 @@ namespace solidity K(RETURNS, "returns", 0) \ K(STRUCT, "struct", 0) \ K(SWITCH, "switch", 0) \ - K(THIS, "this", 0) \ K(VAR, "var", 0) \ K(WHILE, "while", 0) \ \ diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index e24435976..8ded428f8 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -60,13 +60,24 @@ shared_ptr Type::fromElementaryTypeName(Token::Value _typeToken) shared_ptr Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName) { - return make_shared(*_typeName.getReferencedStruct()); + Declaration const* declaration = _typeName.getReferencedDeclaration(); + if (StructDefinition const* structDef = dynamic_cast(declaration)) + return make_shared(*structDef); + else if (FunctionDefinition const* function = dynamic_cast(declaration)) + return make_shared(*function); + else if (ContractDefinition const* contract = dynamic_cast(declaration)) + return make_shared(*contract); + return shared_ptr(); } shared_ptr Type::fromMapping(Mapping const& _typeName) { shared_ptr keyType = _typeName.getKeyType().toType(); + if (!keyType) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Error resolving type name.")); shared_ptr valueType = _typeName.getValueType().toType(); + if (!valueType) + BOOST_THROW_EXCEPTION(_typeName.getValueType().createTypeError("Invalid type name")); return make_shared(keyType, valueType); } @@ -86,6 +97,8 @@ shared_ptr Type::forLiteral(Literal const& _literal) } } +const MemberList Type::EmptyMemberList = MemberList(); + shared_ptr IntegerType::smallestTypeForLiteral(string const& _literal) { bigint value(_literal); @@ -176,6 +189,8 @@ u256 IntegerType::literalValue(Literal const& _literal) const return u256(value); } +const MemberList IntegerType::AddressMemberList = MemberList({{"balance", std::make_shared(256)}}); + bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const { // conversion to integer is fine, but not to address @@ -199,6 +214,15 @@ u256 BoolType::literalValue(Literal const& _literal) const BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Bool type constructed from non-boolean literal.")); } +bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (isImplicitlyConvertibleTo(_convertTo)) + return true; + if (_convertTo.getCategory() == Category::INTEGER) + return dynamic_cast(_convertTo).isAddress(); + return false; +} + bool ContractType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -215,6 +239,11 @@ u256 ContractType::getStorageSize() const return max(1, size); } +string ContractType::toString() const +{ + return "contract " + m_contract.getName(); +} + bool StructType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -226,15 +255,15 @@ bool StructType::operator==(Type const& _other) const u256 StructType::getStorageSize() const { u256 size = 0; - for (ASTPointer const& variable: m_struct.getMembers()) - size += variable->getType()->getStorageSize(); + for (pair> const& member: getMembers()) + size += member.second->getStorageSize(); return max(1, size); } bool StructType::canLiveOutsideStorage() const { - for (unsigned i = 0; i < getMemberCount(); ++i) - if (!getMemberByIndex(i).getType()->canLiveOutsideStorage()) + for (pair> const& member: getMembers()) + if (!member.second->canLiveOutsideStorage()) return false; return true; } @@ -244,33 +273,30 @@ string StructType::toString() const return string("struct ") + m_struct.getName(); } -unsigned StructType::getMemberCount() const +MemberList const& StructType::getMembers() const { - return m_struct.getMembers().size(); -} - -unsigned StructType::memberNameToIndex(string const& _name) const -{ - vector> 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]; + // We need to lazy-initialize it because of recursive references. + if (!m_members) + { + map> members; + for (ASTPointer const& variable: m_struct.getMembers()) + members[variable->getName()] = variable->getType(); + m_members.reset(new MemberList(members)); + } + return *m_members; } -u256 StructType::getStorageOffsetOfMember(unsigned _index) const +u256 StructType::getStorageOffsetOfMember(string const& _name) const { //@todo cache member offset? u256 offset; -// vector> const& members = m_struct.getMembers(); - for (unsigned index = 0; index < _index; ++index) - offset += getMemberByIndex(index).getType()->getStorageSize(); - return offset; + for (ASTPointer variable: m_struct.getMembers()) + { + offset += variable->getType()->getStorageSize(); + if (variable->getName() == _name) + return offset; + } + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested.")); } bool FunctionType::operator==(Type const& _other) const @@ -283,8 +309,7 @@ bool FunctionType::operator==(Type const& _other) const string FunctionType::toString() const { - //@todo nice string for function types - return "function(...)returns(...)"; + return "function " + m_function.getName(); } bool MappingType::operator==(Type const& _other) const diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 726470172..a8caf7154 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,33 @@ namespace solidity // @todo realMxN, string +class Type; // forward + +/** + * List of members of a type. + */ +class MemberList +{ +public: + using TypePointer = std::shared_ptr; + using MemberMap = std::map; + + MemberList() {} + explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} + TypePointer getMemberType(std::string const& _name) const + { + auto it = m_memberTypes.find(_name); + return it != m_memberTypes.end() ? it->second : std::shared_ptr(); + } + + MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } + MemberMap::const_iterator end() const { return m_memberTypes.end(); } + +private: + MemberMap m_memberTypes; +}; + + /** * Abstract base class that forms the root of the type hierarchy. */ @@ -45,7 +73,7 @@ class Type: private boost::noncopyable public: enum class Category { - INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE + INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE, MAGIC }; ///@{ @@ -78,8 +106,18 @@ public: /// @returns number of bytes required to hold this value in storage. /// For dynamically "allocated" types, it returns the size of the statically allocated head, virtual u256 getStorageSize() const { return 1; } + /// 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; } + /// 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; } + + /// Returns the list of all members of this type. Default implementation: no members. + virtual MemberList const& getMembers() const { return EmptyMemberList; } + /// Convenience method, returns the type of the given named member or an empty pointer if no such member exists. + std::shared_ptr getMemberType(std::string const& _name) const { return getMembers().getMemberType(_name); } virtual std::string toString() const = 0; virtual u256 literalValue(Literal const&) const @@ -87,6 +125,10 @@ public: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Literal value requested " "for type without literals.")); } + +protected: + /// Convenience object used when returning an empty member list. + static const MemberList EmptyMemberList; }; /** @@ -114,7 +156,10 @@ public: virtual bool operator==(Type const& _other) const override; - virtual unsigned getCalldataEncodedSize() const { return m_bits / 8; } + virtual unsigned getCalldataEncodedSize() const override { return m_bits / 8; } + virtual bool isValueType() const override { return true; } + + virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } virtual std::string toString() const override; virtual u256 literalValue(Literal const& _literal) const override; @@ -127,6 +172,7 @@ public: private: int m_bits; Modifier m_modifier; + static const MemberList AddressMemberList; }; /** @@ -147,6 +193,7 @@ public: } virtual unsigned getCalldataEncodedSize() const { return 1; } + virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bool"; } virtual u256 literalValue(Literal const& _literal) const override; @@ -160,10 +207,11 @@ class ContractType: public Type public: virtual Category getCategory() const override { return Category::CONTRACT; } ContractType(ContractDefinition const& _contract): m_contract(_contract) {} - + /// Contracts can be converted to themselves and to addresses. + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool operator==(Type const& _other) const override; - virtual u256 getStorageSize() const; - virtual std::string toString() const override { return "contract{...}"; } + virtual u256 getStorageSize() const override; + virtual std::string toString() const override; private: ContractDefinition const& m_contract; @@ -183,18 +231,18 @@ public: } virtual bool operator==(Type const& _other) const override; - virtual u256 getStorageSize() const; - virtual bool canLiveOutsideStorage() const; + virtual u256 getStorageSize() const override; + virtual bool canLiveOutsideStorage() const override; virtual std::string toString() const override; - 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; + virtual MemberList const& getMembers() const override; + + u256 getStorageOffsetOfMember(std::string const& _name) const; private: StructDefinition const& m_struct; + /// List of member types, will be lazy-initialized because of recursive references. + mutable std::unique_ptr m_members; }; /** @@ -210,8 +258,9 @@ public: virtual bool operator==(Type const& _other) const override; 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 bool canLiveOutsideStorage() const { return false; } + virtual bool canBeStored() const override { return false; } + virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } + virtual bool canLiveOutsideStorage() const override { return false; } private: FunctionDefinition const& m_function; @@ -229,7 +278,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; - virtual bool canLiveOutsideStorage() const { return false; } + virtual bool canLiveOutsideStorage() const override { return false; } std::shared_ptr getKeyType() const { return m_keyType; } std::shared_ptr getValueType() const { return m_valueType; } @@ -250,8 +299,9 @@ public: VoidType() {} virtual std::string toString() const override { return "void"; } - virtual u256 getStorageSize() const { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } - virtual bool canLiveOutsideStorage() const { return false; } + virtual bool canBeStored() const override { return false; } + virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } + virtual bool canLiveOutsideStorage() const override { return false; } }; /** @@ -267,8 +317,9 @@ public: std::shared_ptr const& getActualType() const { return m_actualType; } virtual bool operator==(Type const& _other) const override; - virtual u256 getStorageSize() const { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } - virtual bool canLiveOutsideStorage() const { return false; } + virtual bool canBeStored() const override { return false; } + virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual bool canLiveOutsideStorage() const override { return false; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } private: diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp index e0635b6ae..3e86919a6 100644 --- a/test/solidityCompiler.cpp +++ b/test/solidityCompiler.cpp @@ -48,11 +48,11 @@ bytes compileContract(const string& _sourceCode) Parser parser; ASTPointer contract; BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared(CharStream(_sourceCode)))); - NameAndTypeResolver resolver; + NameAndTypeResolver resolver({}); BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); Compiler compiler; - compiler.compileContract(*contract); + compiler.compileContract(*contract, {}); // debug //compiler.streamAssembly(cout); return compiler.getAssembledBytecode(); diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 4e68103ac..11535d4f6 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -45,17 +45,17 @@ class ExecutionFramework public: ExecutionFramework() { g_logVerbosity = 0; } - bytes const& compileAndRun(string const& _sourceCode) + bytes const& compileAndRun(string const& _sourceCode, u256 const& _value = 0) { bytes code = dev::solidity::CompilerStack::staticCompile(_sourceCode); - sendMessage(code, true); + sendMessage(code, true, _value); BOOST_REQUIRE(!m_output.empty()); return m_output; } - bytes const& callContractFunction(byte _index, bytes const& _data = bytes()) + bytes const& callContractFunction(byte _index, bytes const& _data = bytes(), u256 const& _value = 0) { - sendMessage(bytes(1, _index) + _data, false); + sendMessage(bytes(1, _index) + _data, false, _value); return m_output; } @@ -111,11 +111,11 @@ private: return toBigEndian(_cppFunction(_arguments...)); } - void sendMessage(bytes const& _data, bool _isCreation) + void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0) { eth::Executive executive(m_state); - eth::Transaction t = _isCreation ? eth::Transaction(0, m_gasPrice, m_gas, _data, 0, KeyPair::create().sec()) - : eth::Transaction(0, m_gasPrice, m_gas, m_contractAddress, _data, 0, KeyPair::create().sec()); + eth::Transaction t = _isCreation ? eth::Transaction(_value, m_gasPrice, m_gas, _data, 0, KeyPair::create().sec()) + : eth::Transaction(_value, m_gasPrice, m_gas, m_contractAddress, _data, 0, KeyPair::create().sec()); bytes transactionRLP = t.rlp(); try { @@ -125,7 +125,7 @@ private: catch (...) {} if (_isCreation) { - BOOST_REQUIRE(!executive.create(Address(), 0, m_gasPrice, m_gas, &_data, Address())); + BOOST_REQUIRE(!executive.create(Address(), _value, m_gasPrice, m_gas, &_data, Address())); m_contractAddress = executive.newAddress(); BOOST_REQUIRE(m_contractAddress); BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); @@ -133,7 +133,7 @@ private: else { BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); - BOOST_REQUIRE(!executive.call(m_contractAddress, Address(), 0, m_gasPrice, &_data, m_gas, Address())); + BOOST_REQUIRE(!executive.call(m_contractAddress, Address(), _value, m_gasPrice, &_data, m_gas, Address())); } BOOST_REQUIRE(executive.go()); executive.finalize(); @@ -700,6 +700,34 @@ BOOST_AUTO_TEST_CASE(structs) BOOST_CHECK(callContractFunction(0) == bytes({0x01})); } +BOOST_AUTO_TEST_CASE(struct_reference) +{ + char const* sourceCode = "contract test {\n" + " struct s2 {\n" + " uint32 z;\n" + " mapping(uint8 => s2) recursive;\n" + " }\n" + " s2 data;\n" + " function check() returns (bool ok) {\n" + " return data.z == 2 && \n" + " data.recursive[0].z == 3 && \n" + " data.recursive[0].recursive[1].z == 0 && \n" + " data.recursive[0].recursive[0].z == 1;\n" + " }\n" + " function set() {\n" + " data.z = 2;\n" + " var map = data.recursive;\n" + " s2 inner = map[0];\n" + " inner.z = 3;\n" + " inner.recursive[0].z = inner.recursive[1].z + 1;\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_CASE(constructor) { char const* sourceCode = "contract test {\n" @@ -722,6 +750,17 @@ BOOST_AUTO_TEST_CASE(constructor) testSolidityAgainstCpp(0, get, u256(7)); } +BOOST_AUTO_TEST_CASE(balance) +{ + char const* sourceCode = "contract test {\n" + " function getBalance() returns (uint256 balance) {\n" + " return address(this).balance;\n" + " }\n" + "}\n"; + compileAndRun(sourceCode, 23); + BOOST_CHECK(callContractFunction(0) == toBigEndian(u256(23))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/solidityExpressionCompiler.cpp b/test/solidityExpressionCompiler.cpp index 59a9e9336..6ea66badb 100644 --- a/test/solidityExpressionCompiler.cpp +++ b/test/solidityExpressionCompiler.cpp @@ -88,7 +88,7 @@ bytes compileFirstExpression(const string& _sourceCode, vector> _ Parser parser; ASTPointer contract; BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared(CharStream(_sourceCode)))); - NameAndTypeResolver resolver; + NameAndTypeResolver resolver({}); BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.getExpression() != nullptr); diff --git a/test/solidityNameAndTypeResolution.cpp b/test/solidityNameAndTypeResolution.cpp index 930bba0e3..a19e7450c 100644 --- a/test/solidityNameAndTypeResolution.cpp +++ b/test/solidityNameAndTypeResolution.cpp @@ -43,7 +43,7 @@ void parseTextAndResolveNames(std::string const& _source) Parser parser; ASTPointer contract = parser.parse( std::make_shared(CharStream(_source))); - NameAndTypeResolver resolver; + NameAndTypeResolver resolver({}); resolver.resolveNamesAndTypes(*contract); } } @@ -224,6 +224,26 @@ BOOST_AUTO_TEST_CASE(type_inference_explicit_conversion) BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); } +BOOST_AUTO_TEST_CASE(balance) +{ + char const* text = "contract test {\n" + " function fun() {\n" + " uint256 x = address(0).balance;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(balance_invalid) +{ + char const* text = "contract test {\n" + " function fun() {\n" + " address(0).balance = 7;\n" + " }\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() }