Browse Source

Contract compiler and also add ExpressionStatement to AST.

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
Christian 10 years ago
parent
commit
ea6d58a0d1
  1. 9
      libevmface/Instruction.h
  2. 2
      liblll/Assembly.cpp
  3. 4
      liblll/Assembly.h
  4. 43
      libsolidity/AST.cpp
  5. 65
      libsolidity/AST.h
  6. 1
      libsolidity/ASTForward.h
  7. 12
      libsolidity/ASTPrinter.cpp
  8. 2
      libsolidity/ASTPrinter.h
  9. 2
      libsolidity/ASTVisitor.h
  10. 4
      libsolidity/CMakeLists.txt
  11. 509
      libsolidity/Compiler.cpp
  12. 141
      libsolidity/Compiler.h
  13. 60
      libsolidity/CompilerUtilities.cpp
  14. 83
      libsolidity/CompilerUtilities.h
  15. 408
      libsolidity/ExpressionCompiler.cpp
  16. 79
      libsolidity/ExpressionCompiler.h
  17. 32
      libsolidity/NameAndTypeResolver.cpp
  18. 17
      libsolidity/NameAndTypeResolver.h
  19. 21
      libsolidity/Parser.cpp
  20. 1
      libsolidity/Parser.h
  21. 18
      libsolidity/Types.cpp
  22. 6
      libsolidity/Types.h
  23. 41
      solc/main.cpp
  24. 323
      test/solidityCompiler.cpp
  25. 348
      test/solidityExpressionCompiler.cpp
  26. 2
      test/solidityParser.cpp

9
libevmface/Instruction.h

@ -175,6 +175,15 @@ enum class Instruction: uint8_t
SUICIDE = 0xff ///< halt execution and register account for later deletion
};
/// Returs the PUSH<_number> instruction
inline Instruction pushInstruction(unsigned _number) { assert(1 <= _number && _number <= 32); return Instruction(unsigned(Instruction::PUSH1) + _number - 1); }
/// Returs the DUP<_number> instruction
inline Instruction dupInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::DUP1) + _number - 1); }
/// Returs the SWAP<_number> instruction
inline Instruction swapInstruction(unsigned _number) { assert(1 <= _number && _number <= 16); return Instruction(unsigned(Instruction::SWAP1) + _number - 1); }
/// Information structure for a particular instruction.
struct InstructionInfo
{

2
liblll/Assembly.cpp

@ -54,6 +54,7 @@ unsigned Assembly::bytesRequired() const
switch (i.m_type)
{
case Operation:
case Tag: // 1 byte for the JUMPDEST
ret++;
break;
case PushString:
@ -69,7 +70,6 @@ unsigned Assembly::bytesRequired() const
case PushData:
case PushSub:
ret += 1 + br;
case Tag:;
default:;
}
if (dev::bytesRequired(ret) <= br)

4
liblll/Assembly.h

@ -105,7 +105,11 @@ public:
void injectStart(AssemblyItem const& _i);
std::string out() const { std::stringstream ret; streamRLP(ret); return ret.str(); }
int deposit() const { return m_deposit; }
void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assert(m_deposit >= 0); }
void setDeposit(int _deposit) { m_deposit = _deposit; assert(m_deposit >= 0); }
bytes assemble() const;
Assembly& optimise(bool _enable);
std::ostream& streamRLP(std::ostream& _out, std::string const& _prefix = "") const;

43
libsolidity/AST.cpp

@ -167,6 +167,14 @@ void Return::accept(ASTVisitor& _visitor)
_visitor.endVisit(*this);
}
void ExpressionStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
if (m_expression)
m_expression->accept(_visitor);
_visitor.endVisit(*this);
}
void VariableDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
@ -255,14 +263,6 @@ TypeError ASTNode::createTypeError(string const& _description)
return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description);
}
void Statement::expectType(Expression& _expression, const Type& _expectedType)
{
_expression.checkTypeRequirements();
if (!_expression.getType()->isImplicitlyConvertibleTo(_expectedType))
BOOST_THROW_EXCEPTION(_expression.createTypeError("Type not implicitly convertible to expected type."));
//@todo provide more information to the exception
}
void Block::checkTypeRequirements()
{
for (shared_ptr<Statement> const& statement: m_statements)
@ -271,7 +271,7 @@ void Block::checkTypeRequirements()
void IfStatement::checkTypeRequirements()
{
expectType(*m_condition, BoolType());
m_condition->expectType(BoolType());
m_trueBody->checkTypeRequirements();
if (m_falseBody)
m_falseBody->checkTypeRequirements();
@ -279,7 +279,7 @@ void IfStatement::checkTypeRequirements()
void WhileStatement::checkTypeRequirements()
{
expectType(*m_condition, BoolType());
m_condition->expectType(BoolType());
m_body->checkTypeRequirements();
}
@ -301,7 +301,7 @@ void Return::checkTypeRequirements()
"than in returns declaration."));
// this could later be changed such that the paramaters type is an anonymous struct type,
// but for now, we only allow one return parameter
expectType(*m_expression, *m_returnParameters->getParameters().front()->getType());
m_expression->expectType(*m_returnParameters->getParameters().front()->getType());
}
void VariableDefinition::checkTypeRequirements()
@ -313,7 +313,7 @@ void VariableDefinition::checkTypeRequirements()
if (m_value)
{
if (m_variable->getType())
expectType(*m_value, *m_variable->getType());
m_value->expectType(*m_variable->getType());
else
{
// no type declared and no previous assignment, infer the type
@ -330,7 +330,7 @@ void Assignment::checkTypeRequirements()
m_leftHandSide->checkTypeRequirements();
if (!m_leftHandSide->isLvalue())
BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue."));
expectType(*m_rightHandSide, *m_leftHandSide->getType());
m_rightHandSide->expectType(*m_leftHandSide->getType());
m_type = m_leftHandSide->getType();
if (m_assigmentOperator != Token::ASSIGN)
{
@ -340,6 +340,19 @@ void Assignment::checkTypeRequirements()
}
}
void ExpressionStatement::checkTypeRequirements()
{
m_expression->checkTypeRequirements();
}
void Expression::expectType(const Type& _expectedType)
{
checkTypeRequirements();
if (!getType()->isImplicitlyConvertibleTo(_expectedType))
BOOST_THROW_EXCEPTION(createTypeError("Type not implicitly convertible to expected type."));
//@todo provide more information to the exception
}
void UnaryOperation::checkTypeRequirements()
{
// INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE
@ -411,10 +424,10 @@ void FunctionCall::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call."));
// @todo actually the return type should be an anonymous struct,
// but we change it to the type of the first return value until we have structs
if (fun.getReturnParameterList()->getParameters().empty())
if (fun.getReturnParameters().empty())
m_type = make_shared<VoidType>();
else
m_type = fun.getReturnParameterList()->getParameters().front()->getType();
m_type = fun.getReturnParameters().front()->getType();
}
}

65
libsolidity/AST.h

@ -144,7 +144,7 @@ public:
ASTNode(_location), m_parameters(_parameters) {}
virtual void accept(ASTVisitor& _visitor) override;
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() { return m_parameters; }
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters; }
private:
std::vector<ASTPointer<VariableDeclaration>> m_parameters;
@ -167,15 +167,20 @@ public:
bool isDeclaredConst() const { return m_isDeclaredConst; }
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); }
ParameterList& getParameterList() { return *m_parameters; }
std::vector<ASTPointer<VariableDeclaration>> const& getReturnParameters() const { return m_returnParameters->getParameters(); }
ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; }
Block& getBody() { return *m_body; }
void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); }
std::vector<VariableDeclaration const*>const& getLocalVariables() const { return m_localVariables; }
private:
bool m_isPublic;
ASTPointer<ParameterList> m_parameters;
bool m_isDeclaredConst;
ASTPointer<ParameterList> m_returnParameters;
ASTPointer<Block> m_body;
std::vector<VariableDeclaration const*> m_localVariables;
};
/// Declaration of a variable. This can be used in various places, e.g. in function parameter
@ -285,11 +290,6 @@ public:
//! This includes checking that operators are applicable to their arguments but also that
//! the number of function call arguments matches the number of formal parameters and so forth.
virtual void checkTypeRequirements() = 0;
protected:
//! Helper function, check that the inferred type for @a _expression is @a _expectedType or at
//! least implicitly convertible to @a _expectedType. If not, throw exception.
void expectType(Expression& _expression, Type const& _expectedType);
};
/// Brace-enclosed block containing zero or more statements.
@ -318,6 +318,9 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getCondition() const { return *m_condition; }
Statement& getTrueStatement() const { return *m_trueBody; }
Statement* getFalseStatement() const { return m_falseBody.get(); }
private:
ASTPointer<Expression> m_condition;
ASTPointer<Statement> m_trueBody;
@ -342,6 +345,8 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getCondition() const { return *m_condition; }
Statement& getBody() const { return *m_body; }
private:
ASTPointer<Expression> m_condition;
ASTPointer<Statement> m_body;
@ -372,6 +377,8 @@ public:
virtual void checkTypeRequirements() override;
void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; }
ParameterList const& getFunctionReturnParameters() const { assert(m_returnParameters); return *m_returnParameters; }
Expression* getExpression() const { return m_expression.get(); }
private:
ASTPointer<Expression> m_expression; //< value to return, optional
@ -392,21 +399,54 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
VariableDeclaration const& getDeclaration() const { return *m_variable; }
Expression* getExpression() const { return m_value.get(); }
private:
ASTPointer<VariableDeclaration> m_variable;
ASTPointer<Expression> m_value; ///< the assigned value, can be missing
};
/// An expression, i.e. something that has a value (which can also be of type "void" in case
/// of function calls).
class Expression: public Statement
/**
* A statement that contains only an expression (i.e. an assignment, function call, ...).
*/
class ExpressionStatement: public Statement
{
public:
ExpressionStatement(Location const& _location, ASTPointer<Expression> _expression):
Statement(_location), m_expression(_expression) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getExpression() const { return *m_expression; }
private:
ASTPointer<Expression> m_expression;
};
/// @}
/// Expressions
/// @{
/**
* An expression, i.e. something that has a value (which can also be of type "void" in case
* of some function calls).
* @abstract
*/
class Expression: public ASTNode
{
public:
Expression(Location const& _location): Statement(_location), m_isLvalue(false) {}
Expression(Location const& _location): ASTNode(_location), m_isLvalue(false) {}
virtual void checkTypeRequirements() = 0;
std::shared_ptr<Type const> const& getType() const { return m_type; }
bool isLvalue() const { return m_isLvalue; }
/// Helper function, infer the type via @ref checkTypeRequirements and then check that it
/// is implicitly convertible to @a _expectedType. If not, throw exception.
void expectType(Type const& _expectedType);
protected:
//! Inferred type of the expression, only filled after a call to checkTypeRequirements().
std::shared_ptr<Type const> m_type;
@ -415,11 +455,6 @@ protected:
bool m_isLvalue;
};
/// @}
/// Expressions
/// @{
/// Assignment, can also be a compound assignment.
/// Examples: (a = 7 + 8) or (a *= 2)
class Assignment: public Expression

1
libsolidity/ASTForward.h

@ -53,6 +53,7 @@ class Continue;
class Break;
class Return;
class VariableDefinition;
class ExpressionStatement;
class Expression;
class Assignment;
class UnaryOperation;

12
libsolidity/ASTPrinter.cpp

@ -171,6 +171,13 @@ bool ASTPrinter::visit(VariableDefinition& _node)
return goDeeper();
}
bool ASTPrinter::visit(ExpressionStatement& _node)
{
writeLine("ExpressionStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Expression& _node)
{
writeLine("Expression");
@ -358,6 +365,11 @@ void ASTPrinter::endVisit(VariableDefinition&)
m_indentation--;
}
void ASTPrinter::endVisit(ExpressionStatement&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Expression&)
{
m_indentation--;

2
libsolidity/ASTPrinter.h

@ -58,6 +58,7 @@ public:
bool visit(Break& _node) override;
bool visit(Return& _node) override;
bool visit(VariableDefinition& _node) override;
bool visit(ExpressionStatement& _node) override;
bool visit(Expression& _node) override;
bool visit(Assignment& _node) override;
bool visit(UnaryOperation& _node) override;
@ -89,6 +90,7 @@ public:
void endVisit(Break&) override;
void endVisit(Return&) override;
void endVisit(VariableDefinition&) override;
void endVisit(ExpressionStatement&) override;
void endVisit(Expression&) override;
void endVisit(Assignment&) override;
void endVisit(UnaryOperation&) override;

2
libsolidity/ASTVisitor.h

@ -58,6 +58,7 @@ public:
virtual bool visit(Break&) { return true; }
virtual bool visit(Return&) { return true; }
virtual bool visit(VariableDefinition&) { return true; }
virtual bool visit(ExpressionStatement&) { return true; }
virtual bool visit(Expression&) { return true; }
virtual bool visit(Assignment&) { return true; }
virtual bool visit(UnaryOperation&) { return true; }
@ -89,6 +90,7 @@ public:
virtual void endVisit(Break&) { }
virtual void endVisit(Return&) { }
virtual void endVisit(VariableDefinition&) { }
virtual void endVisit(ExpressionStatement&) { }
virtual void endVisit(Expression&) { }
virtual void endVisit(Assignment&) { }
virtual void endVisit(UnaryOperation&) { }

4
libsolidity/CMakeLists.txt

@ -16,8 +16,8 @@ file(GLOB HEADERS "*.h")
include_directories(..)
target_link_libraries(${EXECUTABLE} devcore)
target_link_libraries(${EXECUTABLE} evmface)
# @todo we only depend on Assembly, not on all of lll
target_link_libraries(${EXECUTABLE} evmface devcore lll)
install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib )
install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} )

509
libsolidity/Compiler.cpp

@ -17,455 +17,192 @@
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Solidity AST to EVM bytecode compiler.
* Solidity compiler.
*/
#include <cassert>
#include <utility>
#include <algorithm>
#include <libsolidity/AST.h>
#include <libsolidity/Compiler.h>
#include <libsolidity/ExpressionCompiler.h>
using namespace std;
namespace dev {
namespace solidity {
void CompilerContext::setLabelPosition(uint32_t _label, uint32_t _position)
{
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);
assert(iter != m_labelPositions.end());
return iter->second;
}
void ExpressionCompiler::compile(Expression& _expression)
bytes Compiler::compile(ContractDefinition& _contract)
{
m_assemblyItems.clear();
_expression.accept(*this);
Compiler compiler;
compiler.compileContract(_contract);
return compiler.m_context.getAssembledBytecode();
}
bytes ExpressionCompiler::getAssembledBytecode() const
void Compiler::compileContract(ContractDefinition& _contract)
{
bytes assembled;
assembled.reserve(m_assemblyItems.size());
m_context = CompilerContext(); // clear it just in case
// 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);
}
//@todo constructor
//@todo register state variables
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;
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
m_context.addFunction(*function);
appendFunctionSelector(_contract.getDefinedFunctions());
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
function->accept(*this);
}
AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context,
Expression& _expression)
void Compiler::appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition>> const&)
{
ExpressionCompiler compiler(_context);
compiler.compile(_expression);
return compiler.getAssemblyItems();
// filter public functions, and sort by name. Then select function from first byte,
// unpack arguments from calldata, push to stack and jump. Pack return values to
// output and return.
}
bool ExpressionCompiler::visit(Assignment& _assignment)
bool Compiler::visit(FunctionDefinition& _function)
{
m_currentLValue = nullptr;
_assignment.getLeftHandSide().accept(*this);
//@todo to simplify this, the colling convention could by changed such that
// caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn]
// although note that this reduces the size of the visible stack
Expression& rightHandSide = _assignment.getRightHandSide();
Token::Value op = _assignment.getAssignmentOperator();
if (op != Token::ASSIGN)
{
// compound assignment
rightHandSide.accept(*this);
Type const& resultType = *_assignment.getType();
cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType);
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType);
}
else
{
append(eth::Instruction::POP); //@todo do not retrieve the value in the first place
rightHandSide.accept(*this);
}
m_context.startNewFunction();
m_returnTag = m_context.newTag();
m_breakTags.clear();
m_continueTags.clear();
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: // !
append(eth::Instruction::NOT);
break;
case Token::BIT_NOT: // ~
append(eth::Instruction::BNOT);
break;
case Token::DELETE: // delete
{
// a -> a xor a (= 0).
// @todo semantics change for complex types
append(eth::Instruction::DUP1);
append(eth::Instruction::XOR);
storeInLValue(_unaryOperation);
break;
}
case Token::INC: // ++ (pre- or postfix)
case Token::DEC: // -- (pre- or postfix)
if (!_unaryOperation.isPrefixOperation())
append(eth::Instruction::DUP1);
append(eth::Instruction::PUSH1);
append(1);
if (_unaryOperation.getOperator() == Token::INC)
append(eth::Instruction::ADD);
else
{
append(eth::Instruction::SWAP1); //@todo avoid this
append(eth::Instruction::SUB);
}
if (_unaryOperation.isPrefixOperation())
storeInLValue(_unaryOperation);
else
moveToLValue(_unaryOperation);
break;
case Token::ADD: // +
// unary add, so basically no-op
break;
case Token::SUB: // -
append(eth::Instruction::PUSH1);
append(0);
append(eth::Instruction::SUB);
break;
default:
assert(false); // invalid operation
}
}
m_context << m_context.getFunctionEntryLabel(_function);
bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation)
{
Expression& leftExpression = _binaryOperation.getLeftExpression();
Expression& rightExpression = _binaryOperation.getRightExpression();
Type const& resultType = *_binaryOperation.getType();
Token::Value const op = _binaryOperation.getOperator();
// stack upon entry: [return address] [arg0] [arg1] ... [argn]
// reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
if (op == Token::AND || op == Token::OR)
{
// special case: short-circuiting
appendAndOrOperatorCode(_binaryOperation);
}
else if (Token::isCompareOp(op))
{
leftExpression.accept(*this);
rightExpression.accept(*this);
unsigned const numArguments = _function.getParameters().size();
unsigned const numReturnValues = _function.getReturnParameters().size();
unsigned const numLocalVariables = _function.getLocalVariables().size();
// 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);
}
for (ASTPointer<VariableDeclaration> const& variable: _function.getParameters() + _function.getReturnParameters())
m_context.addVariable(*variable);
for (VariableDeclaration const* localVariable: _function.getLocalVariables())
m_context.addVariable(*localVariable);
m_context.initializeLocalVariables(numReturnValues + numLocalVariables);
// do not visit the child nodes, we already did that explicitly
return false;
}
_function.getBody().accept(*this);
void ExpressionCompiler::endVisit(FunctionCall& _functionCall)
{
if (_functionCall.isTypeConversion())
{
//@todo we only have integers and bools for now which cannot be explicitly converted
assert(_functionCall.getArguments().size() == 1);
cleanHigherOrderBitsIfNeeded(*_functionCall.getArguments().front()->getType(),
*_functionCall.getType());
}
else
{
//@todo: arguments are already on the stack
// push return label (below arguments?)
// jump to function label
// discard all but the first function return argument
}
}
m_context << m_returnTag;
void ExpressionCompiler::endVisit(MemberAccess&)
{
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
// that shows the target positions of the elements, where "-1" denotes that this element needs
// to be removed from the stack.
// Note that the fact that the return arguments are of increasing index is vital for this
// algorithm to work.
}
vector<int> stackLayout;
stackLayout.push_back(numReturnValues); // target of return address
stackLayout += vector<int>(numArguments, -1); // discard all arguments
for (unsigned i = 0; i < numReturnValues; ++i)
stackLayout.push_back(i);
stackLayout += vector<int>(numLocalVariables, -1);
void ExpressionCompiler::endVisit(IndexAccess&)
{
while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
m_context << eth::Instruction::POP;
stackLayout.pop_back();
}
else
{
m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1);
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
}
m_context << eth::Instruction::JUMP;
void ExpressionCompiler::endVisit(Identifier& _identifier)
{
m_currentLValue = _identifier.getReferencedDeclaration();
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."));
appendDup(stackPos + 1);
return false;
}
void ExpressionCompiler::endVisit(Literal& _literal)
bool Compiler::visit(IfStatement& _ifStatement)
{
switch (_literal.getType()->getCategory())
{
case Type::Category::INTEGER:
case Type::Category::BOOL:
{
bytes value = _literal.getType()->literalToBigEndian(_literal);
assert(value.size() <= 32);
assert(!value.empty());
appendPush(value.size());
append(value);
break;
}
default:
assert(false); // @todo
}
ExpressionCompiler::compileExpression(m_context, _ifStatement.getCondition());
eth::AssemblyItem trueTag = m_context.appendConditionalJump();
if (_ifStatement.getFalseStatement())
_ifStatement.getFalseStatement()->accept(*this);
eth::AssemblyItem endTag = m_context.appendJump();
m_context << trueTag;
_ifStatement.getTrueStatement().accept(*this);
m_context << endTag;
return false;
}
void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(const Type& _typeOnStack, const Type& _targetType)
bool Compiler::visit(WhileStatement& _whileStatement)
{
// 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.
}
}
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
m_continueTags.push_back(loopStart);
m_breakTags.push_back(loopEnd);
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation)
{
Token::Value const op = _binaryOperation.getOperator();
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);
}
m_context << loopStart;
ExpressionCompiler::compileExpression(m_context, _whileStatement.getCondition());
m_context << eth::Instruction::NOT;
m_context.appendConditionalJumpTo(loopEnd);
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);
assert(type);
bool const isSigned = type->isSigned();
_whileStatement.getBody().accept(*this);
// 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:
assert(false);
}
}
}
m_context.appendJumpTo(loopStart);
m_context << loopEnd;
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
m_continueTags.pop_back();
m_breakTags.pop_back();
return false;
}
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
bool Compiler::visit(Continue&)
{
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
assert(type);
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:
assert(false);
}
assert(!m_continueTags.empty());
m_context.appendJumpTo(m_continueTags.back());
return false;
}
void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
bool Compiler::visit(Break&)
{
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:
assert(false);
}
assert(!m_breakTags.empty());
m_context.appendJumpTo(m_breakTags.back());
return false;
}
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
bool Compiler::visit(Return& _return)
{
switch (_operator)
//@todo modifications are needed to make this work with functions returning multiple values
if (Expression* expression = _return.getExpression())
{
case Token::SHL:
assert(false); //@todo
break;
case Token::SAR:
assert(false); //@todo
break;
default:
assert(false);
ExpressionCompiler::compileExpression(m_context, *expression);
VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front();
ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), *firstVariable.getType());
int stackPosition = m_context.getStackPositionOfVariable(firstVariable);
m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP;
}
m_context.appendJumpTo(m_returnTag);
return 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::appendPush(unsigned _number)
{
assert(1 <= _number && _number <= 32);
append(eth::Instruction(unsigned(eth::Instruction::PUSH1) + _number - 1));
}
void ExpressionCompiler::appendDup(unsigned _number)
{
assert(1 <= _number && _number <= 16);
append(eth::Instruction(unsigned(eth::Instruction::DUP1) + _number - 1));
}
void ExpressionCompiler::appendSwap(unsigned _number)
{
assert(1 <= _number && _number <= 16);
append(eth::Instruction(unsigned(eth::Instruction::SWAP1) + _number - 1));
}
void ExpressionCompiler::append(bytes const& _data)
{
m_assemblyItems.reserve(m_assemblyItems.size() + _data.size());
for (byte b: _data)
append(b);
}
void ExpressionCompiler::storeInLValue(Expression const& _expression)
{
assert(m_currentLValue);
moveToLValue(_expression);
unsigned stackPos = stackPositionOfLValue();
if (stackPos > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Stack too deep."));
if (stackPos >= 1)
appendDup(stackPos);
}
void ExpressionCompiler::moveToLValue(Expression const& _expression)
bool Compiler::visit(VariableDefinition& _variableDefinition)
{
assert(m_currentLValue);
unsigned stackPos = stackPositionOfLValue();
if (stackPos > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Stack too deep."));
else if (stackPos > 0)
if (Expression* expression = _variableDefinition.getExpression())
{
appendSwap(stackPos);
append(eth::Instruction::POP);
ExpressionCompiler::compileExpression(m_context, *expression);
ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(),
*_variableDefinition.getDeclaration().getType());
int stackPosition = m_context.getStackPositionOfVariable(_variableDefinition.getDeclaration());
m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP;
}
return false;
}
unsigned ExpressionCompiler::stackPositionOfLValue() const
bool Compiler::visit(ExpressionStatement& _expressionStatement)
{
return 8; // @todo ask the context and track stack changes due to m_assemblyItems
Expression& expression = _expressionStatement.getExpression();
ExpressionCompiler::compileExpression(m_context, expression);
if (expression.getType()->getCategory() != Type::Category::VOID)
m_context << eth::Instruction::POP;
return false;
}
}
}

141
libsolidity/Compiler.h

@ -20,133 +20,40 @@
* Solidity AST to EVM bytecode compiler.
*/
#include <libevmface/Instruction.h>
#include <libsolidity/ASTVisitor.h>
#include <libsolidity/Types.h>
#include <libsolidity/Token.h>
#include <libsolidity/CompilerUtilities.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
class Compiler: private ASTVisitor
{
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; }
/// Compile the given contract and return the EVM bytecode.
static bytes compile(ContractDefinition& _contract);
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
Compiler(): m_returnTag(m_context.newTag()) {}
void compileContract(ContractDefinition& _contract);
void appendFunctionSelector(const std::vector<ASTPointer<FunctionDefinition> >& _functions);
virtual bool visit(FunctionDefinition& _function) override;
virtual bool visit(IfStatement& _ifStatement) override;
virtual bool visit(WhileStatement& _whileStatement) override;
virtual bool visit(Continue& _continue) override;
virtual bool visit(Break& _break) override;
virtual bool visit(Return& _return) override;
virtual bool visit(VariableDefinition& _variableDefinition) override;
virtual bool visit(ExpressionStatement& _expressionStatement) override;
bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); }
CompilerContext m_context;
std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement
};
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_currentLValue(nullptr), 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 bool visit(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);
///@{
///@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);
/// @}
/// Appends a JUMPI instruction to a new label and returns the label
uint32_t appendConditionalJump();
void appendPush(unsigned _number);
void appendDup(unsigned _number);
void appendSwap(unsigned _number);
/// 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)); }
/// 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;
Declaration* m_currentLValue;
AssemblyItems m_assemblyItems;
CompilerContext& m_context;
};
}
}

60
libsolidity/CompilerUtilities.cpp

@ -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();
}
}
}

83
libsolidity/CompilerUtilities.h

@ -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;
};
}
}

408
libsolidity/ExpressionCompiler.cpp

@ -0,0 +1,408 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <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);
}
}
}

79
libsolidity/ExpressionCompiler.h

@ -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;
};
}
}

32
libsolidity/NameAndTypeResolver.cpp

@ -55,12 +55,15 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
m_currentScope = &m_scopes[function.get()];
function->getBody().checkTypeRequirements();
}
m_currentScope = &m_scopes[nullptr];
}
void NameAndTypeResolver::reset()
Declaration* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const
{
m_scopes.clear();
m_currentScope = nullptr;
auto iterator = m_scopes.find(_scope);
if (iterator == end(m_scopes))
return nullptr;
return iterator->second.resolveName(_name, false);
}
Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive)
@ -68,8 +71,13 @@ Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name
return m_currentScope->resolveName(_name, _recursive);
}
void NameAndTypeResolver::reset()
{
m_scopes.clear();
m_currentScope = nullptr;
}
DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode*, Scope>& _scopes,
DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode const*, Scope>& _scopes,
ASTNode& _astRoot):
m_scopes(_scopes), m_currentScope(&m_scopes[nullptr])
{
@ -101,27 +109,33 @@ void DeclarationRegistrationHelper::endVisit(StructDefinition&)
bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
{
registerDeclaration(_function, true);
m_currentFunction = &_function;
return true;
}
void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
{
m_currentFunction = nullptr;
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration)
void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition)
{
registerDeclaration(_declaration, false);
return true;
// Register the local variables with the function
// This does not fit here perfectly, but it saves us another AST visit.
assert(m_currentFunction);
m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration());
}
void DeclarationRegistrationHelper::endVisit(VariableDeclaration&)
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration)
{
registerDeclaration(_declaration, false);
return true;
}
void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node)
{
map<ASTNode*, Scope>::iterator iter;
map<ASTNode const*, Scope>::iterator iter;
bool newlyAdded;
tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope));
assert(newlyAdded);

17
libsolidity/NameAndTypeResolver.h

@ -41,6 +41,14 @@ public:
NameAndTypeResolver() {}
void resolveNamesAndTypes(ContractDefinition& _contract);
/// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted,
/// the global scope is used (i.e. the one containing only the contract).
/// @returns a pointer to the declaration on success or nullptr on failure.
Declaration* resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const;
/// Resolves a name in the "current" scope. Should only be called during the initial
/// resolving phase.
Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true);
private:
@ -48,7 +56,7 @@ private:
//! Maps nodes declaring a scope to scopes, i.e. ContractDefinition, FunctionDeclaration and
//! StructDefinition (@todo not yet implemented), where nullptr denotes the global scope.
std::map<ASTNode*, Scope> m_scopes;
std::map<ASTNode const*, Scope> m_scopes;
Scope* m_currentScope;
};
@ -58,7 +66,7 @@ private:
class DeclarationRegistrationHelper: private ASTVisitor
{
public:
DeclarationRegistrationHelper(std::map<ASTNode*, Scope>& _scopes, ASTNode& _astRoot);
DeclarationRegistrationHelper(std::map<ASTNode const*, Scope>& _scopes, ASTNode& _astRoot);
private:
bool visit(ContractDefinition& _contract);
@ -67,15 +75,16 @@ private:
void endVisit(StructDefinition& _struct);
bool visit(FunctionDefinition& _function);
void endVisit(FunctionDefinition& _function);
void endVisit(VariableDefinition& _variableDefinition);
bool visit(VariableDeclaration& _declaration);
void endVisit(VariableDeclaration& _declaration);
void enterNewSubScope(ASTNode& _node);
void closeCurrentScope();
void registerDeclaration(Declaration& _declaration, bool _opensScope);
std::map<ASTNode*, Scope>& m_scopes;
std::map<ASTNode const*, Scope>& m_scopes;
Scope* m_currentScope;
FunctionDefinition* m_currentFunction;
};
//! Resolves references to declarations (of variables and types) and also establishes the link

21
libsolidity/Parser.cpp

@ -266,9 +266,11 @@ ASTPointer<Statement> Parser::parseStatement()
// starting from here, all statements must be terminated by a semicolon
case Token::CONTINUE:
statement = ASTNodeFactory(*this).createNode<Continue>();
m_scanner->next();
break;
case Token::BREAK:
statement = ASTNodeFactory(*this).createNode<Break>();
m_scanner->next();
break;
case Token::RETURN:
{
@ -283,9 +285,9 @@ ASTPointer<Statement> Parser::parseStatement()
}
break;
default:
// distinguish between variable definition (and potentially assignment) and expressions
// distinguish between variable definition (and potentially assignment) and expression statement
// (which include assignments to other expressions and pre-declared variables)
// We have a variable definition if we ge a keyword that specifies a type name, or
// We have a variable definition if we get a keyword that specifies a type name, or
// in the case of a user-defined type, we have two identifiers following each other.
if (m_scanner->getCurrentToken() == Token::MAPPING ||
m_scanner->getCurrentToken() == Token::VAR ||
@ -293,8 +295,8 @@ ASTPointer<Statement> Parser::parseStatement()
(m_scanner->getCurrentToken() == Token::IDENTIFIER &&
m_scanner->peekNextToken() == Token::IDENTIFIER))
statement = parseVariableDefinition();
else // "ordinary" expression
statement = parseExpression();
else // "ordinary" expression statement
statement = parseExpressionStatement();
}
expectToken(Token::SEMICOLON);
return statement;
@ -349,6 +351,14 @@ ASTPointer<VariableDefinition> Parser::parseVariableDefinition()
return nodeFactory.createNode<VariableDefinition>(variable, value);
}
ASTPointer<ExpressionStatement> Parser::parseExpressionStatement()
{
ASTNodeFactory nodeFactory(*this);
ASTPointer<Expression> expression = parseExpression();
nodeFactory.setEndPositionFromNode(expression);
return nodeFactory.createNode<ExpressionStatement>(expression);
}
ASTPointer<Expression> Parser::parseExpression()
{
ASTNodeFactory nodeFactory(*this);
@ -453,8 +463,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
{
case Token::TRUE_LITERAL:
case Token::FALSE_LITERAL:
expression = nodeFactory.createNode<Literal>(token, ASTPointer<ASTString>());
m_scanner->next();
expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
break;
case Token::NUMBER:
case Token::STRING_LITERAL:

1
libsolidity/Parser.h

@ -58,6 +58,7 @@ private:
ASTPointer<IfStatement> parseIfStatement();
ASTPointer<WhileStatement> parseWhileStatement();
ASTPointer<VariableDefinition> parseVariableDefinition();
ASTPointer<ExpressionStatement> parseExpressionStatement();
ASTPointer<Expression> parseExpression();
ASTPointer<Expression> parseBinaryExpression(int _minPrecedence = 4);
ASTPointer<Expression> parseUnaryExpression();

18
libsolidity/Types.cpp

@ -159,14 +159,12 @@ std::string IntegerType::toString() const
return prefix + dev::toString(m_bits);
}
bytes IntegerType::literalToBigEndian(const Literal& _literal) const
u256 IntegerType::literalValue(const Literal& _literal) const
{
bigint value(_literal.getValue());
if (!isSigned() && value < 0)
return bytes(); // @todo this should already be caught by "smallestTypeforLiteral"
//@todo check that the number of bits is correct
//@todo does "toCompactBigEndian" work for signed numbers?
return toCompactBigEndian(value);
//@todo check that the number is not too large
//@todo does this work for signed numbers?
return u256(value);
}
bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
@ -182,14 +180,14 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
return isImplicitlyConvertibleTo(_convertTo);
}
bytes BoolType::literalToBigEndian(const Literal& _literal) const
u256 BoolType::literalValue(const Literal& _literal) const
{
if (_literal.getToken() == Token::TRUE_LITERAL)
return bytes(1, 1);
return u256(1);
else if (_literal.getToken() == Token::FALSE_LITERAL)
return bytes(1, 0);
return u256(0);
else
return NullBytes;
assert(false);
}
bool ContractType::operator==(const Type& _other) const

6
libsolidity/Types.h

@ -69,7 +69,7 @@ public:
virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); }
virtual std::string toString() const = 0;
virtual bytes literalToBigEndian(Literal const&) const { return NullBytes; }
virtual u256 literalValue(Literal const&) const { assert(false); }
};
/// Any kind of integer type including hash and address.
@ -94,7 +94,7 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override;
virtual bytes literalToBigEndian(Literal const& _literal) const override;
virtual u256 literalValue(Literal const& _literal) const override;
int getNumBits() const { return m_bits; }
bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; }
@ -122,7 +122,7 @@ public:
}
virtual std::string toString() const override { return "bool"; }
virtual bytes literalToBigEndian(Literal const& _literal) const override;
virtual u256 literalValue(Literal const& _literal) const override;
};
/// The type of a contract instance, there is one distinct type for each contract definition.

41
solc/main.cpp

@ -55,35 +55,6 @@ void version()
exit(0);
}
/// Helper class that extracts the first expression in an AST.
class FirstExpressionExtractor: private ASTVisitor
{
public:
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); }
Expression* getExpression() const { return m_expression; }
private:
virtual bool visit(Expression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); }
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); }
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); }
bool checkExpression(Expression& _expression)
{
if (m_expression == nullptr)
m_expression = &_expression;
return false;
}
private:
Expression* m_expression;
};
int main(int argc, char** argv)
{
string infile;
@ -143,13 +114,10 @@ int main(int argc, char** argv)
dev::solidity::ASTPrinter printer(ast, sourceCode);
printer.print(cout);
FirstExpressionExtractor extractor(*ast);
CompilerContext context;
ExpressionCompiler compiler(context);
bytes instructions;
try
{
compiler.compile(*extractor.getExpression());
instructions = Compiler::compile(*ast);
}
catch (CompilerError const& exception)
{
@ -157,10 +125,9 @@ int main(int argc, char** argv)
return -1;
}
bytes instructions = compiler.getAssembledBytecode();
cout << "Bytecode for the first expression: " << endl;
cout << "EVM assembly: " << endl;
cout << eth::disassemble(instructions) << endl;
cout << "Binary: " << toHex(instructions) << endl;
return 0;
}

323
test/solidityCompiler.cpp

@ -18,7 +18,7 @@
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Unit tests for the name and type resolution of the solidity parser.
* Unit tests for the solidity compiler.
*/
#include <string>
@ -31,6 +31,9 @@
#include <libsolidity/AST.h>
#include <boost/test/unit_test.hpp>
using namespace std;
using namespace dev::eth;
namespace dev
{
namespace solidity
@ -41,233 +44,167 @@ 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)
bytes compileContract(const string& _sourceCode)
{
Parser parser;
ASTPointer<ContractDefinition> contract;
BOOST_REQUIRE_NO_THROW(contract = parser.parse(std::make_shared<Scanner>(CharStream(_sourceCode))));
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;
ExpressionCompiler compiler(context);
compiler.compile(*extractor.getExpression());
bytes instructions = compiler.getAssembledBytecode();
bytes instructions = Compiler::compile(*contract);
// debug
//std::cout << eth::disassemble(instructions) << std::endl;
//cout << eth::disassemble(instructions) << endl;
return instructions;
}
} // end anonymous namespace
BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler)
BOOST_AUTO_TEST_SUITE(SolidityCompiler)
BOOST_AUTO_TEST_CASE(literal_true)
BOOST_AUTO_TEST_CASE(smoke_test)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = true; }"
" function f() { var x = 2; }\n"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x1});
bytes code = compileContract(sourceCode);
bytes expectation({byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x0, // initialize local variable x
byte(Instruction::PUSH1), 0x2,
byte(Instruction::SWAP1),
byte(Instruction::POP),
byte(Instruction::JUMPDEST),
byte(Instruction::POP),
byte(Instruction::JUMP)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(literal_false)
BOOST_AUTO_TEST_CASE(different_argument_numbers)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = false; }"
" function f(uint a, uint b, uint c) returns(uint d) { return b; }\n"
" function g() returns (uint e, uint h) { h = f(1, 2, 3); }\n"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x0});
bytes code = compileContract(sourceCode);
bytes expectation({byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x0, // initialize return variable d
byte(Instruction::DUP3),
byte(Instruction::SWAP1), // assign b to d
byte(Instruction::POP),
byte(Instruction::PUSH1), 0xa, // jump to return
byte(Instruction::JUMP),
byte(Instruction::JUMPDEST),
byte(Instruction::SWAP4), // store d and fetch return address
byte(Instruction::SWAP3), // store return address
byte(Instruction::POP),
byte(Instruction::POP),
byte(Instruction::POP),
byte(Instruction::JUMP), // end of f
byte(Instruction::JUMPDEST), // beginning of g
byte(Instruction::PUSH1), 0x0,
byte(Instruction::DUP1), // initialized e and h
byte(Instruction::PUSH1), 0x20, // ret address
byte(Instruction::PUSH1), 0x1,
byte(Instruction::PUSH1), 0x2,
byte(Instruction::PUSH1), 0x3,
byte(Instruction::PUSH1), 0x1,
// stack here: ret e h 0x20 1 2 3 0x1
byte(Instruction::JUMP),
byte(Instruction::JUMPDEST),
// stack here: ret e h f(1,2,3)
byte(Instruction::DUP2),
byte(Instruction::POP),
byte(Instruction::SWAP1),
// stack here: ret e f(1,2,3) h
byte(Instruction::POP),
byte(Instruction::DUP1), // retrieve it again as "value of expression"
byte(Instruction::POP), // end of assignment
// stack here: ret e f(1,2,3)
byte(Instruction::JUMPDEST),
byte(Instruction::SWAP1),
// ret e f(1,2,3)
byte(Instruction::SWAP2),
// f(1,2,3) e ret
byte(Instruction::JUMP) // end of g
});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(int_literal)
BOOST_AUTO_TEST_CASE(ifStatement)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = 0x12345678901234567890; }"
" function f() { bool x; if (x) 77; else if (!x) 78; else 79; }"
"}\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);
bytes expectation({byte(eth::Instruction::DUP9), // will change as soon as we have real stack tracking
byte(eth::Instruction::DUP1),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::ADD),
byte(eth::Instruction::SWAP8), // will change
byte(eth::Instruction::POP), // first ++
byte(eth::Instruction::DUP9),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::ADD),
byte(eth::Instruction::SWAP8), // will change
byte(eth::Instruction::POP), // second ++
byte(eth::Instruction::DUP8), // will change
byte(eth::Instruction::XOR),
byte(eth::Instruction::DUP9), // will change
byte(eth::Instruction::DUP1),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::SWAP1),
byte(eth::Instruction::SUB),
byte(eth::Instruction::SWAP8), // will change
byte(eth::Instruction::POP), // first --
byte(eth::Instruction::XOR),
byte(eth::Instruction::DUP9),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::SWAP1),
byte(eth::Instruction::SUB),
byte(eth::Instruction::SWAP8), // will change
byte(eth::Instruction::POP), // second ++
byte(eth::Instruction::DUP8), // will change
byte(eth::Instruction::XOR)});
bytes code = compileContract(sourceCode);
bytes expectation({byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x0,
byte(Instruction::DUP1),
byte(Instruction::PUSH1), 0x1b, // "true" target
byte(Instruction::JUMPI),
// new check "else if" condition
byte(Instruction::DUP1),
byte(Instruction::NOT),
byte(Instruction::PUSH1), 0x13,
byte(Instruction::JUMPI),
// "else" body
byte(Instruction::PUSH1), 0x4f,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x17, // exit path of second part
byte(Instruction::JUMP),
// "else if" body
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x4e,
byte(Instruction::POP),
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x1f,
byte(Instruction::JUMP),
// "if" body
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x4d,
byte(Instruction::POP),
byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
byte(Instruction::POP),
byte(Instruction::JUMP)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(assignment)
BOOST_AUTO_TEST_CASE(loops)
{
char const* sourceCode = "contract test {\n"
" function f(uint a, uint b) { (a += b) * 2; }"
" function f() { while(true){1;break;2;continue;3;return;4;} }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes code = compileContract(sourceCode);
bytes expectation({byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x1,
byte(Instruction::NOT),
byte(Instruction::PUSH1), 0x21,
byte(Instruction::JUMPI),
byte(Instruction::PUSH1), 0x1,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x21,
byte(Instruction::JUMP), // break
byte(Instruction::PUSH1), 0x2,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x2,
byte(Instruction::JUMP), // continue
byte(Instruction::PUSH1), 0x3,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x22,
byte(Instruction::JUMP), // return
byte(Instruction::PUSH1), 0x4,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x2,
byte(Instruction::JUMP),
byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
byte(Instruction::JUMP)});
bytes expectation({byte(eth::Instruction::DUP9), // will change as soon as we have real stack tracking
byte(eth::Instruction::DUP9),
byte(eth::Instruction::ADD),
byte(eth::Instruction::SWAP8), // will change
byte(eth::Instruction::POP), // first ++
byte(eth::Instruction::DUP8),
byte(eth::Instruction::PUSH1), 0x2,
byte(eth::Instruction::MUL)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}

348
test/solidityExpressionCompiler.cpp

@ -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

2
test/solidityParser.cpp

@ -185,7 +185,7 @@ BOOST_AUTO_TEST_CASE(while_loop)
{
char const* text = "contract test {\n"
" function fun(uint256 a) {\n"
" uint256 x = (1 + 4).member(++67) || true;\n"
" while (true) { uint256 x = 1; break; continue; } x = 9;\n"
" }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseText(text));

Loading…
Cancel
Save