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