diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 91b4a42b1..414de9b2b 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -328,7 +328,7 @@ void Assignment::checkTypeRequirements() m_type = m_leftHandSide->getType(); if (m_assigmentOperator != Token::ASSIGN) { - // complex assignment + // compound assignment if (!m_type->acceptsBinaryOperator(Token::AssignmentToBinaryOp(m_assigmentOperator))) BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type.")); } @@ -339,7 +339,7 @@ void UnaryOperation::checkTypeRequirements() // INC, DEC, NOT, BIT_NOT, DELETE m_subExpression->checkTypeRequirements(); m_type = m_subExpression->getType(); - if (m_type->acceptsUnaryOperator(m_operator)) + if (!m_type->acceptsUnaryOperator(m_operator)) BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type.")); } @@ -369,11 +369,11 @@ void FunctionCall::checkTypeRequirements() m_expression->checkTypeRequirements(); for (ASTPointer const& argument: m_arguments) argument->checkTypeRequirements(); - Type const& expressionType = *m_expression->getType(); - Type::Category const category = expressionType.getCategory(); - if (category == Type::Category::TYPE) + + Type const* expressionType = m_expression->getType().get(); + if (isTypeConversion()) { - TypeType const* type = dynamic_cast(&expressionType); + TypeType const* type = dynamic_cast(expressionType); BOOST_ASSERT(type); //@todo for structs, we have to check the number of arguments to be equal to the // number of non-mapping members @@ -384,12 +384,12 @@ void FunctionCall::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed.")); m_type = type->getActualType(); } - else if (category == Type::Category::FUNCTION) + else { //@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 - FunctionType const* function = dynamic_cast(&expressionType); + FunctionType const* function = dynamic_cast(expressionType); BOOST_ASSERT(function); FunctionDefinition const& fun = function->getFunction(); std::vector> const& parameters = fun.getParameters(); @@ -405,8 +405,11 @@ void FunctionCall::checkTypeRequirements() else m_type = fun.getReturnParameterList()->getParameters().front()->getType(); } - else - BOOST_THROW_EXCEPTION(createTypeError("Type does not support invocation.")); +} + +bool FunctionCall::isTypeConversion() const +{ + return m_expression->getType()->getCategory() == Type::Category::TYPE; } void MemberAccess::checkTypeRequirements() diff --git a/libsolidity/AST.h b/libsolidity/AST.h index df146ab10..a55f58c16 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -61,6 +61,12 @@ public: /// the given description TypeError createTypeError(std::string const& _description); + ///@{ + /// Equality relies on the fact that nodes cannot be copied. + bool operator==(ASTNode const& _other) const { return this == &_other; } + bool operator!=(ASTNode const& _other) const { return !operator==(_other); } + ///@} + private: Location m_location; }; @@ -386,7 +392,9 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getLeftHandSide() const { return *m_leftHandSide; } Token::Value getAssignmentOperator() const { return m_assigmentOperator; } + Expression& getRightHandSide() const { return *m_rightHandSide; } private: ASTPointer m_leftHandSide; @@ -422,6 +430,8 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getLeftExpression() const { return *m_left; } + Expression& getRightExpression() const { return *m_right; } Token::Value getOperator() const { return m_operator; } private: @@ -441,6 +451,9 @@ public: Expression(_location), m_expression(_expression), m_arguments(_arguments) {} virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + /// Returns true if this is not an actual function call, but an explicit type conversion + /// or constructor call. + bool isTypeConversion() const; private: ASTPointer m_expression; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp new file mode 100644 index 000000000..319f9b1cf --- /dev/null +++ b/libsolidity/Compiler.cpp @@ -0,0 +1,408 @@ +/* + 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 + * Solidity AST to EVM bytecode compiler. + */ + +#include +#include +#include +#include + + +namespace dev { +namespace solidity { + + +void CompilerContext::setLabelPosition(uint32_t _label, uint32_t _position) +{ + BOOST_ASSERT(m_labelPositions.find(_label) == m_labelPositions.end()); + m_labelPositions[_label] = _position; +} + +uint32_t CompilerContext::getLabelPosition(uint32_t _label) const +{ + auto iter = m_labelPositions.find(_label); + BOOST_ASSERT(iter != m_labelPositions.end()); + return iter->second; +} + +void ExpressionCompiler::compile(Expression& _expression) +{ + m_assemblyItems.clear(); + _expression.accept(*this); +} + +bytes ExpressionCompiler::getAssembledBytecode() const +{ + bytes assembled; + assembled.reserve(m_assemblyItems.size()); + + // resolve label references + for (uint32_t pos = 0; pos < m_assemblyItems.size(); ++pos) + { + AssemblyItem const& item = m_assemblyItems[pos]; + if (item.getType() == AssemblyItem::Type::LABEL) + m_context.setLabelPosition(item.getLabel(), pos + 1); + } + + for (AssemblyItem const& item: m_assemblyItems) + { + if (item.getType() == AssemblyItem::Type::LABELREF) + assembled.push_back(m_context.getLabelPosition(item.getLabel())); + else + assembled.push_back(item.getData()); + } + + return assembled; +} + +AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context, + Expression& _expression) +{ + ExpressionCompiler compiler(_context); + compiler.compile(_expression); + return compiler.getAssemblyItems(); +} + +void ExpressionCompiler::endVisit(Assignment& _assignment) +{ + Expression& rightHandSide = _assignment.getRightHandSide(); + Token::Value op = _assignment.getAssignmentOperator(); + if (op != Token::ASSIGN) + { + // compound assignment + // @todo retrieve lvalue value + rightHandSide.accept(*this); + Type const& resultType = *_assignment.getType(); + cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType); + appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType); + } + else + rightHandSide.accept(*this); + // @todo store value +} + +void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) +{ + //@todo type checking and creating code for an operator should be in the same place: + // the operator should know how to convert itself and to which types it applies, so + // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that + // represents the operator + switch (_unaryOperation.getOperator()) + { + case Token::NOT: // ! + append(eth::Instruction::NOT); + break; + case Token::BIT_NOT: // ~ + // ~a modeled as "a xor (0 - 1)" for now + append(eth::Instruction::PUSH1); + append(1); + append(eth::Instruction::PUSH1); + append(0); + append(eth::Instruction::SUB); + append(eth::Instruction::XOR); + break; + case Token::DELETE: // delete + // a -> a xor a (= 0). + // @todo this should also be an assignment + // @todo semantics change for complex types + append(eth::Instruction::DUP1); + append(eth::Instruction::XOR); + break; + case Token::INC: // ++ (pre- or postfix) + // @todo this should also be an assignment + if (_unaryOperation.isPrefixOperation()) + { + append(eth::Instruction::PUSH1); + append(1); + append(eth::Instruction::ADD); + } + break; + case Token::DEC: // -- (pre- or postfix) + // @todo this should also be an assignment + if (_unaryOperation.isPrefixOperation()) + { + append(eth::Instruction::PUSH1); + append(1); + append(eth::Instruction::SWAP1); //@todo avoid this + append(eth::Instruction::SUB); + } + break; + case Token::ADD: // + + // unary add, so basically no-op + break; + case Token::SUB: // - + append(eth::Instruction::NEG); + break; + default: + BOOST_ASSERT(false); // invalid operation + } +} + +bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation) +{ + Expression& leftExpression = _binaryOperation.getLeftExpression(); + Expression& rightExpression = _binaryOperation.getRightExpression(); + Type const& resultType = *_binaryOperation.getType(); + Token::Value const op = _binaryOperation.getOperator(); + + if (op == Token::AND || op == Token::OR) + { + // special case: short-circuiting + appendAndOrOperatorCode(_binaryOperation); + } + else if (Token::isCompareOp(op)) + { + leftExpression.accept(*this); + rightExpression.accept(*this); + + // the types to compare have to be the same, but the resulting type is always bool + BOOST_ASSERT(*leftExpression.getType() == *rightExpression.getType()); + appendCompareOperatorCode(op, *leftExpression.getType()); + } + else + { + leftExpression.accept(*this); + cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType); + rightExpression.accept(*this); + cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType); + appendOrdinaryBinaryOperatorCode(op, resultType); + } + + // do not visit the child nodes, we already did that explicitly + return false; +} + +void ExpressionCompiler::endVisit(FunctionCall& _functionCall) +{ + if (_functionCall.isTypeConversion()) + { + //@todo binary representation for all supported types (bool and int) is the same, so no-op + // here for now. + } + else + { + //@todo + } +} + +void ExpressionCompiler::endVisit(MemberAccess& _memberAccess) +{ + +} + +void ExpressionCompiler::endVisit(IndexAccess& _indexAccess) +{ + +} + +void ExpressionCompiler::endVisit(Identifier& _identifier) +{ + +} + +void ExpressionCompiler::endVisit(Literal& _literal) +{ + switch (_literal.getType()->getCategory()) + { + case Type::Category::INTEGER: + case Type::Category::BOOL: + { + bytes value = _literal.getType()->literalToBigEndian(_literal); + BOOST_ASSERT(value.size() <= 32); + BOOST_ASSERT(!value.empty()); + append(static_cast(eth::Instruction::PUSH1) + static_cast(value.size() - 1)); + append(value); + break; + } + default: + BOOST_ASSERT(false); // @todo + } +} + +void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(const Type& _typeOnStack, const Type& _targetType) +{ + // If the type of one of the operands is extended, we need to remove all + // higher-order bits that we might have ignored in previous operations. + // @todo: store in the AST whether the operand might have "dirty" higher + // order bits + + if (_typeOnStack == _targetType) + return; + if (_typeOnStack.getCategory() == Type::Category::INTEGER && + _targetType.getCategory() == Type::Category::INTEGER) + { + //@todo + } + else + { + // If we get here, there is either an implementation missing to clean higher oder bits + // for non-integer types that are explicitly convertible or we got here in error. + BOOST_ASSERT(!_typeOnStack.isExplicitlyConvertibleTo(_targetType)); + BOOST_ASSERT(false); // these types should not be convertible. + } +} + +void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation) +{ + Token::Value const op = _binaryOperation.getOperator(); + BOOST_ASSERT(op == Token::OR || op == Token::AND); + + _binaryOperation.getLeftExpression().accept(*this); + append(eth::Instruction::DUP1); + if (op == Token::AND) + append(eth::Instruction::NOT); + uint32_t endLabel = appendConditionalJump(); + _binaryOperation.getRightExpression().accept(*this); + appendLabel(endLabel); +} + +void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) +{ + if (_operator == Token::EQ || _operator == Token::NE) + { + append(eth::Instruction::EQ); + if (_operator == Token::NE) + append(eth::Instruction::NOT); + } + else + { + IntegerType const* type = dynamic_cast(&_type); + BOOST_ASSERT(type != nullptr); + bool const isSigned = type->isSigned(); + + // note that EVM opcodes compare like "stack[0] < stack[1]", + // but our left value is at stack[1], so everyhing is reversed. + switch (_operator) + { + case Token::GTE: + append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT); + append(eth::Instruction::NOT); + break; + case Token::LTE: + append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT); + append(eth::Instruction::NOT); + break; + case Token::GT: + append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT); + break; + case Token::LT: + append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT); + break; + default: + BOOST_ASSERT(false); + } + } +} + +void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type) +{ + if (Token::isArithmeticOp(_operator)) + appendArithmeticOperatorCode(_operator, _type); + else if (Token::isBitOp(_operator)) + appendBitOperatorCode(_operator); + else if (Token::isShiftOp(_operator)) + appendShiftOperatorCode(_operator); + else + BOOST_ASSERT(false); // unknown binary operator +} + +void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) +{ + IntegerType const* type = dynamic_cast(&_type); + BOOST_ASSERT(type != nullptr); + bool const isSigned = type->isSigned(); + + switch (_operator) + { + case Token::ADD: + append(eth::Instruction::ADD); + break; + case Token::SUB: + append(eth::Instruction::SWAP1); + append(eth::Instruction::SUB); + break; + case Token::MUL: + append(eth::Instruction::MUL); + break; + case Token::DIV: + append(isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); + break; + case Token::MOD: + append(isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + break; + default: + BOOST_ASSERT(false); + } +} + +void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) +{ + switch (_operator) + { + case Token::BIT_OR: + append(eth::Instruction::OR); + break; + case Token::BIT_AND: + append(eth::Instruction::AND); + break; + case Token::BIT_XOR: + append(eth::Instruction::XOR); + break; + default: + BOOST_ASSERT(false); + } +} + +void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) +{ + switch (_operator) + { + case Token::SHL: + BOOST_ASSERT(false); //@todo + break; + case Token::SAR: + BOOST_ASSERT(false); //@todo + break; + default: + BOOST_ASSERT(false); + } +} + +uint32_t ExpressionCompiler::appendConditionalJump() +{ + uint32_t label = m_context.dispenseNewLabel(); + append(eth::Instruction::PUSH1); + appendLabelref(label); + append(eth::Instruction::JUMPI); + return label; +} + +void ExpressionCompiler::append(bytes const& _data) +{ + m_assemblyItems.reserve(m_assemblyItems.size() + _data.size()); + for (byte b: _data) + append(b); +} + + + +} +} diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h new file mode 100644 index 000000000..bddc4bef3 --- /dev/null +++ b/libsolidity/Compiler.h @@ -0,0 +1,140 @@ +/* + 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 + * Solidity AST to EVM bytecode compiler. + */ + +#include +#include +#include +#include + +namespace dev { +namespace solidity { + +/// A single item of compiled code that can be assembled to a single byte value in the final +/// bytecode. Its main purpose is to inject jump labels and label references into the opcode stream, +/// which can be resolved in the final step. +class AssemblyItem +{ +public: + enum class Type + { + CODE, //< m_data is opcode, m_label is empty. + DATA, //< m_data is actual data, m_label is empty + LABEL, //< m_data is JUMPDEST opcode, m_label is id of label + LABELREF //< m_data is empty, m_label is id of label + }; + + explicit AssemblyItem(eth::Instruction _instruction) : m_type(Type::CODE), m_data(byte(_instruction)) {} + explicit AssemblyItem(byte _data): m_type(Type::DATA), m_data(_data) {} + + /// Factory functions + static AssemblyItem labelRef(uint32_t _label) { return AssemblyItem(Type::LABELREF, 0, _label); } + static AssemblyItem label(uint32_t _label) { return AssemblyItem(Type::LABEL, byte(eth::Instruction::JUMPDEST), _label); } + + Type getType() const { return m_type; } + byte getData() const { return m_data; } + uint32_t getLabel() const { return m_label; } + +private: + AssemblyItem(Type _type, byte _data, uint32_t _label): m_type(_type), m_data(_data), m_label(_label) {} + + Type m_type; + byte m_data; //< data to be written to the bytecode stream (or filled by a label if this is a LABELREF) + uint32_t m_label; //< the id of a label either referenced or defined by this item +}; + +using AssemblyItems = std::vector; + + +/// Context to be shared by all units that compile the same contract. Its current usage only +/// concerns dispensing unique jump label IDs and storing their actual positions in the bytecode +/// stream. +class CompilerContext +{ +public: + CompilerContext(): m_nextLabel(0) {} + uint32_t dispenseNewLabel() { return m_nextLabel++; } + void setLabelPosition(uint32_t _label, uint32_t _position); + uint32_t getLabelPosition(uint32_t _label) const; + +private: + uint32_t m_nextLabel; + + std::map m_labelPositions; +}; + +/// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream +/// of EVM instructions. It needs a compiler context that is the same for the whole compilation +/// unit. +class ExpressionCompiler: public ASTVisitor +{ +public: + ExpressionCompiler(CompilerContext& _compilerContext): m_context(_compilerContext) {} + + /// Compile the given expression and (re-)populate the assembly item list. + void compile(Expression& _expression); + AssemblyItems const& getAssemblyItems() const { return m_assemblyItems; } + bytes getAssembledBytecode() const; + + /// Compile the given expression and return the assembly items right away. + static AssemblyItems compileExpression(CompilerContext& _context, Expression& _expression); + +private: + virtual void endVisit(Assignment& _assignment) override; + virtual void endVisit(UnaryOperation& _unaryOperation) override; + virtual bool visit(BinaryOperation& _binaryOperation) override; + virtual void endVisit(FunctionCall& _functionCall) override; + virtual void endVisit(MemberAccess& _memberAccess) override; + virtual void endVisit(IndexAccess& _indexAccess) override; + virtual void endVisit(Identifier& _identifier) override; + virtual void endVisit(Literal& _literal) override; + + /// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type. + void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType); + + /// Append code for various operator types + /// @{ + void appendAndOrOperatorCode(BinaryOperation& _binaryOperation); + void appendCompareOperatorCode(Token::Value _operator, Type const& _type); + void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type); + + void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); + void appendBitOperatorCode(Token::Value _operator); + void appendShiftOperatorCode(Token::Value _operator); + /// @} + + /// Appends a JUMPI instruction to a new label and returns the label + uint32_t appendConditionalJump(); + + /// Append elements to the current instruction list. + void append(eth::Instruction const& _instruction) { m_assemblyItems.push_back(AssemblyItem(_instruction)); } + void append(byte _value) { m_assemblyItems.push_back(AssemblyItem(_value)); } + void append(bytes const& _data); + void appendLabelref(byte _label) { m_assemblyItems.push_back(AssemblyItem::labelRef(_label)); } + void appendLabel(byte _label) { m_assemblyItems.push_back(AssemblyItem::label(_label)); } + + AssemblyItems m_assemblyItems; + CompilerContext& m_context; +}; + + +} +} diff --git a/libsolidity/Token.h b/libsolidity/Token.h index 2db6e05de..7949d2c65 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -236,6 +236,7 @@ public: static bool isAssignmentOp(Value tok) { return ASSIGN <= tok && tok <= ASSIGN_MOD; } static bool isBinaryOp(Value op) { return COMMA <= op && op <= MOD; } static bool isTruncatingBinaryOp(Value op) { return BIT_OR <= op && op <= SHR; } + static bool isArithmeticOp(Value op) { return ADD <= op && op <= MOD; } static bool isCompareOp(Value op) { return EQ <= op && op <= IN; } static bool isOrderedRelationalCompareOp(Value op) { diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 62324f8c2..f0307a7cd 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include @@ -96,7 +97,7 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const { - if (_convertTo.getCategory() != Category::INTEGER) + if (_convertTo.getCategory() != getCategory()) return false; IntegerType const& convertTo = dynamic_cast(_convertTo); if (convertTo.m_bits < m_bits) @@ -113,7 +114,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return _convertTo.getCategory() == Category::INTEGER; + return _convertTo.getCategory() == getCategory(); } bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const @@ -128,7 +129,24 @@ bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const bool IntegerType::acceptsUnaryOperator(Token::Value _operator) const { - return _operator == Token::DELETE || (!isAddress() && _operator == Token::BIT_NOT); + if (_operator == Token::DELETE) + return true; + if (isAddress()) + return false; + if (_operator == Token::BIT_NOT) + return true; + if (isHash()) + return false; + return _operator == Token::ADD || _operator == Token::SUB || + _operator == Token::INC || _operator == Token::DEC; +} + +bool IntegerType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + IntegerType const& other = dynamic_cast(_other); + return other.m_bits == m_bits && other.m_modifier == m_modifier; } std::string IntegerType::toString() const @@ -139,11 +157,21 @@ std::string IntegerType::toString() const return prefix + dev::toString(m_bits); } +bytes IntegerType::literalToBigEndian(const Literal& _literal) const +{ + bigint value(_literal.getValue()); + if (!isSigned() && value < 0) + return bytes(); // @todo this should already be caught by "smallestTypeforLiteral" + //@todo check that the number of bits is correct + //@todo does "toCompactBigEndian" work for signed numbers? + return toCompactBigEndian(value); +} + bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const { // conversion to integer is fine, but not to address // this is an example of explicit conversions being not transitive (though implicit should be) - if (_convertTo.getCategory() == Category::INTEGER) + if (_convertTo.getCategory() == getCategory()) { IntegerType const& convertTo = dynamic_cast(_convertTo); if (!convertTo.isAddress()) @@ -152,22 +180,55 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const return isImplicitlyConvertibleTo(_convertTo); } -bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const +bytes BoolType::literalToBigEndian(const Literal& _literal) const { - if (_convertTo.getCategory() != Category::CONTRACT) + if (_literal.getToken() == Token::TRUE_LITERAL) + return bytes(1, 1); + else if (_literal.getToken() == Token::FALSE_LITERAL) + return bytes(1, 0); + else + return NullBytes; +} + +bool ContractType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) return false; - ContractType const& convertTo = dynamic_cast(_convertTo); - return &m_contract == &convertTo.m_contract; + ContractType const& other = dynamic_cast(_other); + return other.m_contract == m_contract; } -bool StructType::isImplicitlyConvertibleTo(Type const& _convertTo) const +bool StructType::operator==(const Type& _other) const { - if (_convertTo.getCategory() != Category::STRUCT) + if (_other.getCategory() != getCategory()) return false; - StructType const& convertTo = dynamic_cast(_convertTo); - return &m_struct == &convertTo.m_struct; + StructType const& other = dynamic_cast(_other); + return other.m_struct == m_struct; } +bool FunctionType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + FunctionType const& other = dynamic_cast(_other); + return other.m_function == m_function; +} + +bool MappingType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + MappingType const& other = dynamic_cast(_other); + return *other.m_keyType == *m_keyType && *other.m_valueType == *m_valueType; +} + +bool TypeType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + TypeType const& other = dynamic_cast(_other); + return *getActualType() == *other.getActualType(); +} } } diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 82b549433..db4b05a5a 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -52,7 +53,7 @@ public: static std::shared_ptr forLiteral(Literal const& _literal); virtual Category getCategory() const = 0; - virtual bool isImplicitlyConvertibleTo(Type const&) const { return false; } + virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const { return isImplicitlyConvertibleTo(_convertTo); @@ -60,7 +61,11 @@ public: virtual bool acceptsBinaryOperator(Token::Value) const { return false; } virtual bool acceptsUnaryOperator(Token::Value) const { return false; } + virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); } + virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } + virtual std::string toString() const = 0; + virtual bytes literalToBigEndian(Literal const&) const { return NullBytes; } }; class IntegerType: public Type @@ -81,7 +86,10 @@ public: virtual bool acceptsBinaryOperator(Token::Value _operator) const override; virtual bool acceptsUnaryOperator(Token::Value _operator) const override; + virtual bool operator==(Type const& _other) const override; + virtual std::string toString() const override; + virtual bytes literalToBigEndian(Literal const& _literal) const override; int getNumBits() const { return m_bits; } bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; } @@ -97,10 +105,6 @@ class BoolType: public Type { public: virtual Category getCategory() const { return Category::BOOL; } - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override - { - return _convertTo.getCategory() == Category::BOOL; - } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool acceptsBinaryOperator(Token::Value _operator) const override { @@ -110,7 +114,9 @@ public: { return _operator == Token::NOT || _operator == Token::DELETE; } + virtual std::string toString() const override { return "bool"; } + virtual bytes literalToBigEndian(Literal const& _literal) const override; }; class ContractType: public Type @@ -118,7 +124,8 @@ class ContractType: public Type public: virtual Category getCategory() const override { return Category::CONTRACT; } ContractType(ContractDefinition const& _contract): m_contract(_contract) {} - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const; + + virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override { return "contract{...}"; } @@ -131,12 +138,12 @@ class StructType: public Type public: virtual Category getCategory() const override { return Category::STRUCT; } StructType(StructDefinition const& _struct): m_struct(_struct) {} - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const; virtual bool acceptsUnaryOperator(Token::Value _operator) const override { return _operator == Token::DELETE; } + virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override { return "struct{...}"; } @@ -154,6 +161,8 @@ public: virtual std::string toString() const override { return "function(...)returns(...)"; } + virtual bool operator==(Type const& _other) const override; + private: FunctionDefinition const& m_function; }; @@ -165,8 +174,11 @@ public: MappingType() {} virtual std::string toString() const override { return "mapping(...=>...)"; } + virtual bool operator==(Type const& _other) const override; + private: - //@todo + std::shared_ptr m_keyType; + std::shared_ptr m_valueType; }; //@todo should be changed into "empty anonymous struct" @@ -175,6 +187,7 @@ class VoidType: public Type public: virtual Category getCategory() const override { return Category::VOID; } VoidType() {} + virtual std::string toString() const override { return "void"; } }; @@ -186,6 +199,8 @@ public: std::shared_ptr const& getActualType() const { return m_actualType; } + virtual bool operator==(Type const& _other) const override; + virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } private: diff --git a/solc/main.cpp b/solc/main.cpp index ba0b6ccf7..221ab962e 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include using namespace dev; @@ -34,6 +35,35 @@ void version() exit(0); } + +/// Helper class that extracts the first expression in an AST. +class FirstExpressionExtractor: private ASTVisitor +{ +public: + FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); } + Expression* getExpression() const { return m_expression; } +private: + virtual bool visit(Expression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); } + virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); } + virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); } + virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); } + virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); } + virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); } + virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); } + virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Literal& _expression) override { return checkExpression(_expression); } + bool checkExpression(Expression& _expression) + { + if (m_expression == nullptr) + m_expression = &_expression; + return false; + } +private: + Expression* m_expression; +}; + int main(int argc, char** argv) { std::string infile; @@ -92,5 +122,16 @@ int main(int argc, char** argv) std::cout << "Syntax tree for the contract:" << std::endl; dev::solidity::ASTPrinter printer(ast, sourceCode); printer.print(std::cout); + + FirstExpressionExtractor extractor(*ast); + + CompilerContext context; + ExpressionCompiler compiler(context); + compiler.compile(*extractor.getExpression()); + bytes instructions = compiler.getAssembledBytecode(); + // debug + std::cout << "Bytecode for the first expression: " << std::endl; + std::cout << eth::disassemble(instructions) << std::endl; + return 0; } diff --git a/test/solidityCompiler.cpp b/test/solidityCompiler.cpp new file mode 100644 index 000000000..6e12fecf8 --- /dev/null +++ b/test/solidityCompiler.cpp @@ -0,0 +1,229 @@ + +/* + 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 + * Unit tests for the name and type resolution of the solidity parser. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ + +/// Helper class that extracts the first expression in an AST. +class FirstExpressionExtractor: private ASTVisitor +{ +public: + FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); } + Expression* getExpression() const { return m_expression; } +private: + virtual bool visit(Expression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); } + virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); } + virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); } + virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); } + virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); } + virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); } + virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); } + virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); } + virtual bool visit(Literal& _expression) override { return checkExpression(_expression); } + bool checkExpression(Expression& _expression) + { + if (m_expression == nullptr) + m_expression = &_expression; + return false; + } +private: + Expression* m_expression; +}; + +bytes compileFirstExpression(const std::string& _sourceCode) +{ + Parser parser; + ASTPointer contract; + BOOST_REQUIRE_NO_THROW(contract = parser.parse(std::make_shared(CharStream(_sourceCode)))); + NameAndTypeResolver resolver; + BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); + FirstExpressionExtractor extractor(*contract); + BOOST_REQUIRE(extractor.getExpression() != nullptr); + + CompilerContext context; + ExpressionCompiler compiler(context); + compiler.compile(*extractor.getExpression()); + bytes instructions = compiler.getAssembledBytecode(); + // debug + //std::cout << eth::disassemble(instructions) << std::endl; + return instructions; +} + +} // end anonymous namespace + +BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler) + +BOOST_AUTO_TEST_CASE(literal_true) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = true; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x1}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(literal_false) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = false; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x0}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(int_literal) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = 0x12345678901234567890; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90, + 0x12, 0x34, 0x56, 0x78, 0x90}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(comparison) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = (0x10aa < 0x11aa) != true; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH2), 0x10, 0xaa, + byte(eth::Instruction::PUSH2), 0x11, 0xaa, + byte(eth::Instruction::GT), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(short_circuiting) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0xa, + byte(eth::Instruction::PUSH1), 0x8, + byte(eth::Instruction::ADD), + byte(eth::Instruction::PUSH1), 0x4, + byte(eth::Instruction::GT), + byte(eth::Instruction::NOT), // after this we have 10 + 8 >= 4 + byte(eth::Instruction::DUP1), + byte(eth::Instruction::PUSH1), 0x14, + byte(eth::Instruction::JUMPI), // short-circuit if it is true + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::PUSH1), 0x9, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT), // after this we have 2 != 9 + byte(eth::Instruction::JUMPDEST), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(arithmetics) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = (1 * (2 / (3 % (4 + (5 - (6 | (7 & (8 ^ 9)))))))); }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::PUSH1), 0x3, + byte(eth::Instruction::PUSH1), 0x4, + byte(eth::Instruction::PUSH1), 0x5, + byte(eth::Instruction::PUSH1), 0x6, + byte(eth::Instruction::PUSH1), 0x7, + byte(eth::Instruction::PUSH1), 0x8, + byte(eth::Instruction::PUSH1), 0x9, + byte(eth::Instruction::XOR), + byte(eth::Instruction::AND), + byte(eth::Instruction::OR), + byte(eth::Instruction::SWAP1), + byte(eth::Instruction::SUB), + byte(eth::Instruction::ADD), + byte(eth::Instruction::MOD), + byte(eth::Instruction::DIV), + byte(eth::Instruction::MUL)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(unary_operators) +{ + char const* sourceCode = "contract test {\n" + " function f() { var x = !(~+-(--(++1++)--) == 2); }" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation({byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::ADD), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::SWAP1), + byte(eth::Instruction::SUB), + byte(eth::Instruction::NEG), + byte(eth::Instruction::PUSH1), 0x1, + byte(eth::Instruction::PUSH1), 0x0, + byte(eth::Instruction::SUB), + byte(eth::Instruction::XOR), // bitwise not + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::EQ), + byte(eth::Instruction::NOT)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces +