Christian
10 years ago
9 changed files with 941 additions and 30 deletions
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @author Christian <c@ethdev.com> |
||||
|
* @date 2014 |
||||
|
* Solidity AST to EVM bytecode compiler. |
||||
|
*/ |
||||
|
|
||||
|
#include <boost/assert.hpp> |
||||
|
#include <utility> |
||||
|
#include <libsolidity/AST.h> |
||||
|
#include <libsolidity/Compiler.h> |
||||
|
|
||||
|
|
||||
|
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<byte>(eth::Instruction::PUSH1) + static_cast<byte>(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<IntegerType const*>(&_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<IntegerType const*>(&_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); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @author Christian <c@ethdev.com> |
||||
|
* @date 2014 |
||||
|
* Solidity AST to EVM bytecode compiler. |
||||
|
*/ |
||||
|
|
||||
|
#include <libevmface/Instruction.h> |
||||
|
#include <libsolidity/ASTVisitor.h> |
||||
|
#include <libsolidity/Types.h> |
||||
|
#include <libsolidity/Token.h> |
||||
|
|
||||
|
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<AssemblyItem>; |
||||
|
|
||||
|
|
||||
|
/// 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<uint32_t, uint32_t> 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; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @author Christian <c@ethdev.com> |
||||
|
* @date 2014 |
||||
|
* Unit tests for the name and type resolution of the solidity parser. |
||||
|
*/ |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include <libdevcore/Log.h> |
||||
|
#include <libsolidity/Scanner.h> |
||||
|
#include <libsolidity/Parser.h> |
||||
|
#include <libsolidity/NameAndTypeResolver.h> |
||||
|
#include <libsolidity/Compiler.h> |
||||
|
#include <libsolidity/AST.h> |
||||
|
#include <boost/test/unit_test.hpp> |
||||
|
|
||||
|
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<ContractDefinition> contract; |
||||
|
BOOST_REQUIRE_NO_THROW(contract = parser.parse(std::make_shared<Scanner>(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
|
||||
|
|
Loading…
Reference in new issue