Browse Source
ExpressionStatement functions as glue between Statements and Expressions. This way it is possible to detect when the border between statements and expressions is crossed while walking the AST. Note that ExpressionStatement is not the only border, almost every statement can contains expressions.cl-refactor
26 changed files with 1434 additions and 799 deletions
@ -0,0 +1,60 @@ |
|||
/*
|
|||
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 |
|||
* Utilities for the solidity compiler. |
|||
*/ |
|||
|
|||
#include <cassert> |
|||
#include <utility> |
|||
#include <numeric> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/Compiler.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev { |
|||
namespace solidity { |
|||
|
|||
void CompilerContext::initializeLocalVariables(unsigned _numVariables) |
|||
{ |
|||
if (_numVariables > 0) |
|||
{ |
|||
*this << u256(0); |
|||
for (unsigned i = 1; i < _numVariables; ++i) |
|||
*this << eth::Instruction::DUP1; |
|||
m_asm.adjustDeposit(-_numVariables); |
|||
} |
|||
} |
|||
|
|||
int CompilerContext::getStackPositionOfVariable(const Declaration& _declaration) |
|||
{ |
|||
auto res = find(begin(m_localVariables), end(m_localVariables), &_declaration); |
|||
assert(res != m_localVariables.end()); |
|||
return end(m_localVariables) - res - 1 + m_asm.deposit(); |
|||
} |
|||
|
|||
eth::AssemblyItem CompilerContext::getFunctionEntryLabel(const FunctionDefinition& _function) const |
|||
{ |
|||
auto res = m_functionEntryLabels.find(&_function); |
|||
assert(res != m_functionEntryLabels.end()); |
|||
return res->second.tag(); |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,83 @@ |
|||
/*
|
|||
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 |
|||
* Utilities for the solidity compiler. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libevmface/Instruction.h> |
|||
#include <liblll/Assembly.h> |
|||
#include <libsolidity/Types.h> |
|||
|
|||
namespace dev { |
|||
namespace solidity { |
|||
|
|||
|
|||
/**
|
|||
* Context to be shared by all units that compile the same contract. |
|||
* It stores the generated bytecode and the position of identifiers in memory and on the stack. |
|||
*/ |
|||
class CompilerContext |
|||
{ |
|||
public: |
|||
CompilerContext() {} |
|||
|
|||
void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); } |
|||
void initializeLocalVariables(unsigned _numVariables); |
|||
void addVariable(VariableDeclaration const& _declaration) { m_localVariables.push_back(&_declaration); } |
|||
/// Returns the distance of the given local variable from the top of the stack.
|
|||
int getStackPositionOfVariable(Declaration const& _declaration); |
|||
|
|||
void addFunction(FunctionDefinition const& _function) { m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag())); } |
|||
eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const; |
|||
|
|||
void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } |
|||
|
|||
/// Appends a JUMPI instruction to a new tag and @returns the tag
|
|||
eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } |
|||
/// Appends a JUMPI instruction to @a _tag
|
|||
CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } |
|||
/// Appends a JUMP to a new tag and @returns the tag
|
|||
eth::AssemblyItem appendJump() { return m_asm.appendJump().tag(); } |
|||
/// Appends a JUMP to a specific tag
|
|||
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } |
|||
/// Appends pushing of a new tag and @returns the new tag.
|
|||
eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } |
|||
/// @returns a new tag without pushing any opcodes or data
|
|||
eth::AssemblyItem newTag() { return m_asm.newTag(); } |
|||
|
|||
/// Append elements to the current instruction list and adjust @a m_stackOffset.
|
|||
CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } |
|||
CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; } |
|||
CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } |
|||
CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } |
|||
|
|||
bytes getAssembledBytecode() { return m_asm.assemble(); } |
|||
private: |
|||
eth::Assembly m_asm; |
|||
|
|||
/// Offsets of local variables on the stack.
|
|||
std::vector<Declaration const*> m_localVariables; |
|||
/// Labels pointing to the entry points of funcitons.
|
|||
std::map<FunctionDefinition const*, eth::AssemblyItem> m_functionEntryLabels; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -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 for expressions. |
|||
*/ |
|||
|
|||
#include <cassert> |
|||
#include <utility> |
|||
#include <numeric> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/ExpressionCompiler.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev { |
|||
namespace solidity { |
|||
|
|||
void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression& _expression) |
|||
{ |
|||
ExpressionCompiler compiler(_context); |
|||
_expression.accept(compiler); |
|||
} |
|||
|
|||
bool ExpressionCompiler::visit(Assignment& _assignment) |
|||
{ |
|||
m_currentLValue = nullptr; |
|||
|
|||
Expression& rightHandSide = _assignment.getRightHandSide(); |
|||
rightHandSide.accept(*this); |
|||
Type const& resultType = *_assignment.getType(); |
|||
cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType); |
|||
_assignment.getLeftHandSide().accept(*this); |
|||
|
|||
Token::Value op = _assignment.getAssignmentOperator(); |
|||
if (op != Token::ASSIGN) |
|||
{ |
|||
// compound assignment
|
|||
m_context << eth::Instruction::SWAP1; |
|||
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType); |
|||
} |
|||
else |
|||
m_context << eth::Instruction::POP; //@todo do not retrieve the value in the first place
|
|||
|
|||
storeInLValue(_assignment); |
|||
return false; |
|||
} |
|||
|
|||
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: // !
|
|||
m_context << eth::Instruction::NOT; |
|||
break; |
|||
case Token::BIT_NOT: // ~
|
|||
m_context << eth::Instruction::BNOT; |
|||
break; |
|||
case Token::DELETE: // delete
|
|||
{ |
|||
// a -> a xor a (= 0).
|
|||
// @todo semantics change for complex types
|
|||
m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; |
|||
storeInLValue(_unaryOperation); |
|||
break; |
|||
} |
|||
case Token::INC: // ++ (pre- or postfix)
|
|||
case Token::DEC: // -- (pre- or postfix)
|
|||
if (!_unaryOperation.isPrefixOperation()) |
|||
m_context << eth::Instruction::DUP1; |
|||
m_context << u256(1); |
|||
if (_unaryOperation.getOperator() == Token::INC) |
|||
m_context << eth::Instruction::ADD; |
|||
else |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap
|
|||
if (_unaryOperation.isPrefixOperation()) |
|||
storeInLValue(_unaryOperation); |
|||
else |
|||
moveToLValue(_unaryOperation); |
|||
break; |
|||
case Token::ADD: // +
|
|||
// unary add, so basically no-op
|
|||
break; |
|||
case Token::SUB: // -
|
|||
m_context << u256(0) << eth::Instruction::SUB; |
|||
break; |
|||
default: |
|||
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
|
|||
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; |
|||
} |
|||
|
|||
bool ExpressionCompiler::visit(FunctionCall& _functionCall) |
|||
{ |
|||
if (_functionCall.isTypeConversion()) |
|||
{ |
|||
//@todo we only have integers and bools for now which cannot be explicitly converted
|
|||
assert(_functionCall.getArguments().size() == 1); |
|||
Expression& firstArgument = *_functionCall.getArguments().front(); |
|||
firstArgument.accept(*this); |
|||
cleanHigherOrderBitsIfNeeded(*firstArgument.getType(), *_functionCall.getType()); |
|||
} |
|||
else |
|||
{ |
|||
// Calling convention: Caller pushes return address and arguments
|
|||
// Callee removes them and pushes return values
|
|||
m_currentLValue = nullptr; |
|||
_functionCall.getExpression().accept(*this); |
|||
FunctionDefinition const* function = dynamic_cast<FunctionDefinition*>(m_currentLValue); |
|||
assert(function); |
|||
|
|||
eth::AssemblyItem returnLabel = m_context.pushNewTag(); |
|||
std::vector<ASTPointer<Expression>> const& arguments = _functionCall.getArguments(); |
|||
assert(arguments.size() == function->getParameters().size()); |
|||
for (unsigned i = 0; i < arguments.size(); ++i) |
|||
{ |
|||
arguments[i]->accept(*this); |
|||
cleanHigherOrderBitsIfNeeded(*arguments[i]->getType(), |
|||
*function->getParameters()[i]->getType()); |
|||
} |
|||
|
|||
m_context.appendJumpTo(m_context.getFunctionEntryLabel(*function)); |
|||
m_context << returnLabel; |
|||
|
|||
// callee adds return parameters, but removes arguments and return label
|
|||
m_context.adjustStackOffset(function->getReturnParameters().size() - arguments.size() - 1); |
|||
|
|||
// @todo for now, the return value of a function is its first return value, so remove
|
|||
// all others
|
|||
for (unsigned i = 1; i < function->getReturnParameters().size(); ++i) |
|||
m_context << eth::Instruction::POP; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
void ExpressionCompiler::endVisit(MemberAccess&) |
|||
{ |
|||
|
|||
} |
|||
|
|||
void ExpressionCompiler::endVisit(IndexAccess&) |
|||
{ |
|||
|
|||
} |
|||
|
|||
void ExpressionCompiler::endVisit(Identifier& _identifier) |
|||
{ |
|||
m_currentLValue = _identifier.getReferencedDeclaration(); |
|||
switch (_identifier.getType()->getCategory()) |
|||
{ |
|||
case Type::Category::BOOL: |
|||
case Type::Category::INTEGER: |
|||
case Type::Category::REAL: |
|||
{ |
|||
//@todo we also have to check where to retrieve them from once we add storage variables
|
|||
unsigned stackPos = stackPositionOfLValue(); |
|||
if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory
|
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_identifier.getLocation()) |
|||
<< errinfo_comment("Stack too deep.")); |
|||
m_context << eth::dupInstruction(stackPos + 1); |
|||
break; |
|||
} |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void ExpressionCompiler::endVisit(Literal& _literal) |
|||
{ |
|||
switch (_literal.getType()->getCategory()) |
|||
{ |
|||
case Type::Category::INTEGER: |
|||
case Type::Category::BOOL: |
|||
m_context << _literal.getType()->literalValue(_literal); |
|||
break; |
|||
default: |
|||
assert(false); // @todo
|
|||
} |
|||
} |
|||
|
|||
void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _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.
|
|||
assert(!_typeOnStack.isExplicitlyConvertibleTo(_targetType)); |
|||
assert(false); // these types should not be convertible.
|
|||
} |
|||
} |
|||
|
|||
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation) |
|||
{ |
|||
Token::Value const op = _binaryOperation.getOperator(); |
|||
assert(op == Token::OR || op == Token::AND); |
|||
|
|||
_binaryOperation.getLeftExpression().accept(*this); |
|||
m_context << eth::Instruction::DUP1; |
|||
if (op == Token::AND) |
|||
m_context << eth::Instruction::NOT; |
|||
eth::AssemblyItem endLabel = m_context.appendConditionalJump(); |
|||
_binaryOperation.getRightExpression().accept(*this); |
|||
m_context << endLabel; |
|||
} |
|||
|
|||
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) |
|||
{ |
|||
if (_operator == Token::EQ || _operator == Token::NE) |
|||
{ |
|||
m_context << eth::Instruction::EQ; |
|||
if (_operator == Token::NE) |
|||
m_context << eth::Instruction::NOT; |
|||
} |
|||
else |
|||
{ |
|||
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type); |
|||
assert(type); |
|||
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: |
|||
m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT) |
|||
<< eth::Instruction::NOT; |
|||
break; |
|||
case Token::LTE: |
|||
m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT) |
|||
<< eth::Instruction::NOT; |
|||
break; |
|||
case Token::GT: |
|||
m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT); |
|||
break; |
|||
case Token::LT: |
|||
m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT); |
|||
break; |
|||
default: |
|||
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 |
|||
assert(false); // unknown binary operator
|
|||
} |
|||
|
|||
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) |
|||
{ |
|||
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type); |
|||
assert(type); |
|||
bool const isSigned = type->isSigned(); |
|||
|
|||
switch (_operator) |
|||
{ |
|||
case Token::ADD: |
|||
m_context << eth::Instruction::ADD; |
|||
break; |
|||
case Token::SUB: |
|||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; |
|||
break; |
|||
case Token::MUL: |
|||
m_context << eth::Instruction::MUL; |
|||
break; |
|||
case Token::DIV: |
|||
m_context << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); |
|||
break; |
|||
case Token::MOD: |
|||
m_context << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); |
|||
break; |
|||
default: |
|||
assert(false); |
|||
} |
|||
} |
|||
|
|||
void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) |
|||
{ |
|||
switch (_operator) |
|||
{ |
|||
case Token::BIT_OR: |
|||
m_context << eth::Instruction::OR; |
|||
break; |
|||
case Token::BIT_AND: |
|||
m_context << eth::Instruction::AND; |
|||
break; |
|||
case Token::BIT_XOR: |
|||
m_context << eth::Instruction::XOR; |
|||
break; |
|||
default: |
|||
assert(false); |
|||
} |
|||
} |
|||
|
|||
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) |
|||
{ |
|||
switch (_operator) |
|||
{ |
|||
case Token::SHL: |
|||
assert(false); //@todo
|
|||
break; |
|||
case Token::SAR: |
|||
assert(false); //@todo
|
|||
break; |
|||
default: |
|||
assert(false); |
|||
} |
|||
} |
|||
|
|||
void ExpressionCompiler::storeInLValue(Expression const& _expression) |
|||
{ |
|||
moveToLValue(_expression); |
|||
unsigned stackPos = stackPositionOfLValue(); |
|||
if (stackPos > 16) |
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) |
|||
<< errinfo_comment("Stack too deep.")); |
|||
m_context << eth::dupInstruction(stackPos + 1); |
|||
} |
|||
|
|||
void ExpressionCompiler::moveToLValue(Expression const& _expression) |
|||
{ |
|||
unsigned stackPos = stackPositionOfLValue(); |
|||
if (stackPos > 16) |
|||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) |
|||
<< errinfo_comment("Stack too deep.")); |
|||
else if (stackPos > 0) |
|||
m_context << eth::swapInstruction(stackPos) << eth::Instruction::POP; |
|||
} |
|||
|
|||
unsigned ExpressionCompiler::stackPositionOfLValue() const |
|||
{ |
|||
assert(m_currentLValue); |
|||
return m_context.getStackPositionOfVariable(*m_currentLValue); |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,79 @@ |
|||
/*
|
|||
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 for expressions. |
|||
*/ |
|||
|
|||
#include <libsolidity/ASTVisitor.h> |
|||
#include <libsolidity/CompilerUtilities.h> |
|||
|
|||
namespace dev { |
|||
namespace solidity { |
|||
|
|||
/// 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: private ASTVisitor |
|||
{ |
|||
public: |
|||
/// Compile the given @a _expression into the @a _context.
|
|||
static void compileExpression(CompilerContext& _context, Expression& _expression); |
|||
|
|||
/// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type.
|
|||
static void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType); |
|||
|
|||
private: |
|||
ExpressionCompiler(CompilerContext& _compilerContext): m_currentLValue(nullptr), m_context(_compilerContext) {} |
|||
|
|||
virtual bool visit(Assignment& _assignment) override; |
|||
virtual void endVisit(UnaryOperation& _unaryOperation) override; |
|||
virtual bool visit(BinaryOperation& _binaryOperation) override; |
|||
virtual bool visit(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; |
|||
|
|||
///@{
|
|||
///@name 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); |
|||
/// @}
|
|||
|
|||
/// Stores the value on top of the stack in the current lvalue and copies that value to the
|
|||
/// top of the stack again
|
|||
void storeInLValue(Expression const& _expression); |
|||
/// The same as storeInLValue but do not again retrieve the value to the top of the stack.
|
|||
void moveToLValue(Expression const& _expression); |
|||
/// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack.
|
|||
unsigned stackPositionOfLValue() const; |
|||
void adjustStackOffset(eth::Instruction _instruction); |
|||
|
|||
Declaration* m_currentLValue; |
|||
CompilerContext& m_context; |
|||
}; |
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,348 @@ |
|||
|
|||
/*
|
|||
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 solidity expression compiler. |
|||
*/ |
|||
|
|||
#include <string> |
|||
|
|||
#include <libdevcore/Log.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/ExpressionCompiler.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <boost/test/unit_test.hpp> |
|||
|
|||
using namespace std; |
|||
|
|||
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; |
|||
}; |
|||
|
|||
Declaration const& resolveDeclaration(vector<string> const& _namespacedName, |
|||
NameAndTypeResolver const& _resolver) |
|||
{ |
|||
Declaration const* declaration = nullptr; |
|||
for (string const& namePart: _namespacedName) |
|||
BOOST_REQUIRE(declaration = _resolver.resolveName(namePart, declaration)); |
|||
BOOST_REQUIRE(declaration); |
|||
return *declaration; |
|||
} |
|||
|
|||
bytes compileFirstExpression(const string& _sourceCode, vector<vector<string>> _functions = {}, |
|||
vector<vector<string>> _localVariables = {}) |
|||
{ |
|||
Parser parser; |
|||
ASTPointer<ContractDefinition> contract; |
|||
BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared<Scanner>(CharStream(_sourceCode)))); |
|||
NameAndTypeResolver resolver; |
|||
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); |
|||
FirstExpressionExtractor extractor(*contract); |
|||
BOOST_REQUIRE(extractor.getExpression() != nullptr); |
|||
|
|||
CompilerContext context; |
|||
for (vector<string> const& function: _functions) |
|||
context.addFunction(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver))); |
|||
for (vector<string> const& variable: _localVariables) |
|||
context.addVariable(dynamic_cast<VariableDeclaration const&>(resolveDeclaration(variable, resolver))); |
|||
|
|||
ExpressionCompiler::compileExpression(context, *extractor.getExpression()); |
|||
|
|||
for (vector<string> const& function: _functions) |
|||
context << context.getFunctionEntryLabel(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver))); |
|||
bytes instructions = context.getAssembledBytecode(); |
|||
// debug
|
|||
// cout << eth::disassemble(instructions) << 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), 0x0, |
|||
byte(eth::Instruction::SUB), |
|||
byte(eth::Instruction::BNOT), |
|||
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_CASE(unary_inc_dec) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) { var x = ((a++ ^ ++a) ^ a--) ^ --a; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}}); |
|||
|
|||
// Stack: a, x
|
|||
bytes expectation({byte(eth::Instruction::DUP2), |
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a x a (a+1)
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), // first ++
|
|||
// Stack here: (a+1) x a
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: (a+1) x a (a+2)
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), |
|||
// Stack here: (a+2) x a
|
|||
byte(eth::Instruction::DUP3), // second ++
|
|||
byte(eth::Instruction::XOR), |
|||
// Stack here: (a+2) x a^(a+2)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::SUB), |
|||
// Stack here: (a+2) x a^(a+2) (a+2) (a+1)
|
|||
byte(eth::Instruction::SWAP4), |
|||
byte(eth::Instruction::POP), // first --
|
|||
byte(eth::Instruction::XOR), |
|||
// Stack here: (a+1) x a^(a+2)^(a+2)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::SUB), |
|||
// Stack here: (a+1) x a^(a+2)^(a+2) a
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), // second ++
|
|||
// Stack here: a x a^(a+2)^(a+2)
|
|||
byte(eth::Instruction::DUP3), // will change
|
|||
byte(eth::Instruction::XOR)}); |
|||
// Stack here: a x a^(a+2)^(a+2)^a
|
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(assignment) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a, uint b) { (a += b) * 2; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); |
|||
|
|||
// Stack: a, b
|
|||
bytes expectation({byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b a+b
|
|||
byte(eth::Instruction::SWAP2), |
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::DUP2), |
|||
// Stack here: a+b b a+b
|
|||
byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::MUL)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(function_call) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n" |
|||
" function g(uint a, uint b) returns (uint c) {}\n" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {{"test", "g"}}, |
|||
{{"test", "f", "a"}, {"test", "f", "b"}}); |
|||
|
|||
// Stack: a, b
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x0b, |
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x01, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b <ret label> (a+1)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x15, |
|||
byte(eth::Instruction::JUMP), |
|||
byte(eth::Instruction::JUMPDEST), |
|||
// Stack here: a b g(a+1, b)
|
|||
byte(eth::Instruction::PUSH1), 0x02, |
|||
byte(eth::Instruction::MUL), |
|||
// Stack here: a b g(a+1, b)*2
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b a+g(a+1, b)*2
|
|||
byte(eth::Instruction::SWAP2), |
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::DUP2), |
|||
byte(eth::Instruction::JUMPDEST)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
|||
|
Loading…
Reference in new issue