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 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. /// Information structure for a particular instruction.
struct InstructionInfo struct InstructionInfo
{ {

2
liblll/Assembly.cpp

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

4
liblll/Assembly.h

@ -105,7 +105,11 @@ public:
void injectStart(AssemblyItem const& _i); void injectStart(AssemblyItem const& _i);
std::string out() const { std::stringstream ret; streamRLP(ret); return ret.str(); } std::string out() const { std::stringstream ret; streamRLP(ret); return ret.str(); }
int deposit() const { return m_deposit; } 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; bytes assemble() const;
Assembly& optimise(bool _enable); Assembly& optimise(bool _enable);
std::ostream& streamRLP(std::ostream& _out, std::string const& _prefix = "") const; 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); _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) void VariableDefinition::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
@ -255,14 +263,6 @@ TypeError ASTNode::createTypeError(string const& _description)
return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_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() void Block::checkTypeRequirements()
{ {
for (shared_ptr<Statement> const& statement: m_statements) for (shared_ptr<Statement> const& statement: m_statements)
@ -271,7 +271,7 @@ void Block::checkTypeRequirements()
void IfStatement::checkTypeRequirements() void IfStatement::checkTypeRequirements()
{ {
expectType(*m_condition, BoolType()); m_condition->expectType(BoolType());
m_trueBody->checkTypeRequirements(); m_trueBody->checkTypeRequirements();
if (m_falseBody) if (m_falseBody)
m_falseBody->checkTypeRequirements(); m_falseBody->checkTypeRequirements();
@ -279,7 +279,7 @@ void IfStatement::checkTypeRequirements()
void WhileStatement::checkTypeRequirements() void WhileStatement::checkTypeRequirements()
{ {
expectType(*m_condition, BoolType()); m_condition->expectType(BoolType());
m_body->checkTypeRequirements(); m_body->checkTypeRequirements();
} }
@ -301,7 +301,7 @@ void Return::checkTypeRequirements()
"than in returns declaration.")); "than in returns declaration."));
// this could later be changed such that the paramaters type is an anonymous struct type, // this could later be changed such that the paramaters type is an anonymous struct type,
// but for now, we only allow one return parameter // 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() void VariableDefinition::checkTypeRequirements()
@ -313,7 +313,7 @@ void VariableDefinition::checkTypeRequirements()
if (m_value) if (m_value)
{ {
if (m_variable->getType()) if (m_variable->getType())
expectType(*m_value, *m_variable->getType()); m_value->expectType(*m_variable->getType());
else else
{ {
// no type declared and no previous assignment, infer the type // no type declared and no previous assignment, infer the type
@ -330,7 +330,7 @@ void Assignment::checkTypeRequirements()
m_leftHandSide->checkTypeRequirements(); m_leftHandSide->checkTypeRequirements();
if (!m_leftHandSide->isLvalue()) if (!m_leftHandSide->isLvalue())
BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue.")); 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(); m_type = m_leftHandSide->getType();
if (m_assigmentOperator != Token::ASSIGN) 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() void UnaryOperation::checkTypeRequirements()
{ {
// INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE // 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.")); BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call."));
// @todo actually the return type should be an anonymous struct, // @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 // 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>(); m_type = make_shared<VoidType>();
else 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) {} ASTNode(_location), m_parameters(_parameters) {}
virtual void accept(ASTVisitor& _visitor) override; 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: private:
std::vector<ASTPointer<VariableDeclaration>> m_parameters; std::vector<ASTPointer<VariableDeclaration>> m_parameters;
@ -167,15 +167,20 @@ public:
bool isDeclaredConst() const { return m_isDeclaredConst; } bool isDeclaredConst() const { return m_isDeclaredConst; }
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); } std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); }
ParameterList& getParameterList() { return *m_parameters; } ParameterList& getParameterList() { return *m_parameters; }
std::vector<ASTPointer<VariableDeclaration>> const& getReturnParameters() const { return m_returnParameters->getParameters(); }
ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; } ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; }
Block& getBody() { return *m_body; } 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: private:
bool m_isPublic; bool m_isPublic;
ASTPointer<ParameterList> m_parameters; ASTPointer<ParameterList> m_parameters;
bool m_isDeclaredConst; bool m_isDeclaredConst;
ASTPointer<ParameterList> m_returnParameters; ASTPointer<ParameterList> m_returnParameters;
ASTPointer<Block> m_body; 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 /// 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 //! 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. //! the number of function call arguments matches the number of formal parameters and so forth.
virtual void checkTypeRequirements() = 0; 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. /// Brace-enclosed block containing zero or more statements.
@ -318,6 +318,9 @@ public:
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() 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: private:
ASTPointer<Expression> m_condition; ASTPointer<Expression> m_condition;
ASTPointer<Statement> m_trueBody; ASTPointer<Statement> m_trueBody;
@ -342,6 +345,8 @@ public:
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements() override;
Expression& getCondition() const { return *m_condition; }
Statement& getBody() const { return *m_body; }
private: private:
ASTPointer<Expression> m_condition; ASTPointer<Expression> m_condition;
ASTPointer<Statement> m_body; ASTPointer<Statement> m_body;
@ -372,6 +377,8 @@ public:
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements() override;
void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; } 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: private:
ASTPointer<Expression> m_expression; //< value to return, optional ASTPointer<Expression> m_expression; //< value to return, optional
@ -392,21 +399,54 @@ public:
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements() override;
VariableDeclaration const& getDeclaration() const { return *m_variable; }
Expression* getExpression() const { return m_value.get(); }
private: private:
ASTPointer<VariableDeclaration> m_variable; ASTPointer<VariableDeclaration> m_variable;
ASTPointer<Expression> m_value; ///< the assigned value, can be missing 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). * A statement that contains only an expression (i.e. an assignment, function call, ...).
class Expression: public Statement */
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: 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; } std::shared_ptr<Type const> const& getType() const { return m_type; }
bool isLvalue() const { return m_isLvalue; } 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: protected:
//! Inferred type of the expression, only filled after a call to checkTypeRequirements(). //! Inferred type of the expression, only filled after a call to checkTypeRequirements().
std::shared_ptr<Type const> m_type; std::shared_ptr<Type const> m_type;
@ -415,11 +455,6 @@ protected:
bool m_isLvalue; bool m_isLvalue;
}; };
/// @}
/// Expressions
/// @{
/// Assignment, can also be a compound assignment. /// Assignment, can also be a compound assignment.
/// Examples: (a = 7 + 8) or (a *= 2) /// Examples: (a = 7 + 8) or (a *= 2)
class Assignment: public Expression class Assignment: public Expression

1
libsolidity/ASTForward.h

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

12
libsolidity/ASTPrinter.cpp

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

2
libsolidity/ASTPrinter.h

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

2
libsolidity/ASTVisitor.h

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

4
libsolidity/CMakeLists.txt

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

509
libsolidity/Compiler.cpp

@ -17,455 +17,192 @@
/** /**
* @author Christian <c@ethdev.com> * @author Christian <c@ethdev.com>
* @date 2014 * @date 2014
* Solidity AST to EVM bytecode compiler. * Solidity compiler.
*/ */
#include <cassert> #include <algorithm>
#include <utility>
#include <libsolidity/AST.h> #include <libsolidity/AST.h>
#include <libsolidity/Compiler.h> #include <libsolidity/Compiler.h>
#include <libsolidity/ExpressionCompiler.h>
using namespace std;
namespace dev { namespace dev {
namespace solidity { namespace solidity {
bytes Compiler::compile(ContractDefinition& _contract)
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)
{ {
m_assemblyItems.clear(); Compiler compiler;
_expression.accept(*this); compiler.compileContract(_contract);
return compiler.m_context.getAssembledBytecode();
} }
bytes ExpressionCompiler::getAssembledBytecode() const void Compiler::compileContract(ContractDefinition& _contract)
{ {
bytes assembled; m_context = CompilerContext(); // clear it just in case
assembled.reserve(m_assemblyItems.size());
// resolve label references //@todo constructor
for (uint32_t pos = 0; pos < m_assemblyItems.size(); ++pos) //@todo register state variables
{
AssemblyItem const& item = m_assemblyItems[pos];
if (item.getType() == AssemblyItem::Type::LABEL)
m_context.setLabelPosition(item.getLabel(), pos + 1);
}
for (AssemblyItem const& item: m_assemblyItems) for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
{ m_context.addFunction(*function);
if (item.getType() == AssemblyItem::Type::LABELREF) appendFunctionSelector(_contract.getDefinedFunctions());
assembled.push_back(m_context.getLabelPosition(item.getLabel())); for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
else function->accept(*this);
assembled.push_back(item.getData());
}
return assembled;
} }
AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context, void Compiler::appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition>> const&)
Expression& _expression)
{ {
ExpressionCompiler compiler(_context); // filter public functions, and sort by name. Then select function from first byte,
compiler.compile(_expression); // unpack arguments from calldata, push to stack and jump. Pack return values to
return compiler.getAssemblyItems(); // output and return.
} }
bool ExpressionCompiler::visit(Assignment& _assignment) bool Compiler::visit(FunctionDefinition& _function)
{ {
m_currentLValue = nullptr; //@todo to simplify this, the colling convention could by changed such that
_assignment.getLeftHandSide().accept(*this); // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn]
// although note that this reduces the size of the visible stack
Expression& rightHandSide = _assignment.getRightHandSide(); m_context.startNewFunction();
Token::Value op = _assignment.getAssignmentOperator(); m_returnTag = m_context.newTag();
if (op != Token::ASSIGN) m_breakTags.clear();
{ m_continueTags.clear();
// 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);
}
storeInLValue(_assignment); m_context << m_context.getFunctionEntryLabel(_function);
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
}
}
bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation) // stack upon entry: [return address] [arg0] [arg1] ... [argn]
{ // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
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) unsigned const numArguments = _function.getParameters().size();
{ unsigned const numReturnValues = _function.getReturnParameters().size();
// special case: short-circuiting unsigned const numLocalVariables = _function.getLocalVariables().size();
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 for (ASTPointer<VariableDeclaration> const& variable: _function.getParameters() + _function.getReturnParameters())
assert(*leftExpression.getType() == *rightExpression.getType()); m_context.addVariable(*variable);
appendCompareOperatorCode(op, *leftExpression.getType()); for (VariableDeclaration const* localVariable: _function.getLocalVariables())
} m_context.addVariable(*localVariable);
else m_context.initializeLocalVariables(numReturnValues + numLocalVariables);
{
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 _function.getBody().accept(*this);
return false;
}
void ExpressionCompiler::endVisit(FunctionCall& _functionCall) m_context << m_returnTag;
{
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
}
}
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) return false;
{
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);
} }
void ExpressionCompiler::endVisit(Literal& _literal) bool Compiler::visit(IfStatement& _ifStatement)
{ {
switch (_literal.getType()->getCategory()) ExpressionCompiler::compileExpression(m_context, _ifStatement.getCondition());
{ eth::AssemblyItem trueTag = m_context.appendConditionalJump();
case Type::Category::INTEGER: if (_ifStatement.getFalseStatement())
case Type::Category::BOOL: _ifStatement.getFalseStatement()->accept(*this);
{ eth::AssemblyItem endTag = m_context.appendJump();
bytes value = _literal.getType()->literalToBigEndian(_literal); m_context << trueTag;
assert(value.size() <= 32); _ifStatement.getTrueStatement().accept(*this);
assert(!value.empty()); m_context << endTag;
appendPush(value.size()); return false;
append(value);
break;
}
default:
assert(false); // @todo
}
} }
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 eth::AssemblyItem loopStart = m_context.newTag();
// higher-order bits that we might have ignored in previous operations. eth::AssemblyItem loopEnd = m_context.newTag();
// @todo: store in the AST whether the operand might have "dirty" higher m_continueTags.push_back(loopStart);
// order bits m_breakTags.push_back(loopEnd);
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) m_context << loopStart;
{ ExpressionCompiler::compileExpression(m_context, _whileStatement.getCondition());
Token::Value const op = _binaryOperation.getOperator(); m_context << eth::Instruction::NOT;
assert(op == Token::OR || op == Token::AND); m_context.appendConditionalJumpTo(loopEnd);
_binaryOperation.getLeftExpression().accept(*this);
append(eth::Instruction::DUP1);
if (op == Token::AND)
append(eth::Instruction::NOT);
uint32_t endLabel = appendConditionalJump();
_binaryOperation.getRightExpression().accept(*this);
appendLabel(endLabel);
}
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) _whileStatement.getBody().accept(*this);
{
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();
// note that EVM opcodes compare like "stack[0] < stack[1]", m_context.appendJumpTo(loopStart);
// but our left value is at stack[1], so everyhing is reversed. m_context << loopEnd;
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);
}
}
}
void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type) m_continueTags.pop_back();
{ m_breakTags.pop_back();
if (Token::isArithmeticOp(_operator)) return false;
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) bool Compiler::visit(Continue&)
{ {
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type); assert(!m_continueTags.empty());
assert(type); m_context.appendJumpTo(m_continueTags.back());
bool const isSigned = type->isSigned(); return false;
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);
}
} }
void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) bool Compiler::visit(Break&)
{ {
switch (_operator) assert(!m_breakTags.empty());
{ m_context.appendJumpTo(m_breakTags.back());
case Token::BIT_OR: return false;
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);
}
} }
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: ExpressionCompiler::compileExpression(m_context, *expression);
assert(false); //@todo VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front();
break; ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), *firstVariable.getType());
case Token::SAR: int stackPosition = m_context.getStackPositionOfVariable(firstVariable);
assert(false); //@todo m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP;
break;
default:
assert(false);
} }
m_context.appendJumpTo(m_returnTag);
return false;
} }
uint32_t ExpressionCompiler::appendConditionalJump() bool Compiler::visit(VariableDefinition& _variableDefinition)
{
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)
{ {
assert(m_currentLValue); if (Expression* expression = _variableDefinition.getExpression())
unsigned stackPos = stackPositionOfLValue();
if (stackPos > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Stack too deep."));
else if (stackPos > 0)
{ {
appendSwap(stackPos); ExpressionCompiler::compileExpression(m_context, *expression);
append(eth::Instruction::POP); 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. * Solidity AST to EVM bytecode compiler.
*/ */
#include <libevmface/Instruction.h>
#include <libsolidity/ASTVisitor.h> #include <libsolidity/ASTVisitor.h>
#include <libsolidity/Types.h> #include <libsolidity/CompilerUtilities.h>
#include <libsolidity/Token.h>
namespace dev { namespace dev {
namespace solidity { namespace solidity {
/// A single item of compiled code that can be assembled to a single byte value in the final class Compiler: private ASTVisitor
/// bytecode. Its main purpose is to inject jump labels and label references into the opcode stream,
/// which can be resolved in the final step.
class AssemblyItem
{ {
public: public:
enum class Type /// Compile the given contract and return the EVM bytecode.
{ static bytes compile(ContractDefinition& _contract);
CODE, //< m_data is opcode, m_label is empty.
DATA, //< m_data is actual data, m_label is empty
LABEL, //< m_data is JUMPDEST opcode, m_label is id of label
LABELREF //< m_data is empty, m_label is id of label
};
explicit AssemblyItem(eth::Instruction _instruction) : m_type(Type::CODE), m_data(byte(_instruction)) {}
explicit AssemblyItem(byte _data): m_type(Type::DATA), m_data(_data) {}
/// Factory functions
static AssemblyItem labelRef(uint32_t _label) { return AssemblyItem(Type::LABELREF, 0, _label); }
static AssemblyItem label(uint32_t _label) { return AssemblyItem(Type::LABEL, byte(eth::Instruction::JUMPDEST), _label); }
Type getType() const { return m_type; }
byte getData() const { return m_data; }
uint32_t getLabel() const { return m_label; }
private: private:
AssemblyItem(Type _type, byte _data, uint32_t _label): m_type(_type), m_data(_data), m_label(_label) {} Compiler(): m_returnTag(m_context.newTag()) {}
Type m_type; void compileContract(ContractDefinition& _contract);
byte m_data; //< data to be written to the bytecode stream (or filled by a label if this is a LABELREF) void appendFunctionSelector(const std::vector<ASTPointer<FunctionDefinition> >& _functions);
uint32_t m_label; //< the id of a label either referenced or defined by this item
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()]; m_currentScope = &m_scopes[function.get()];
function->getBody().checkTypeRequirements(); function->getBody().checkTypeRequirements();
} }
m_currentScope = &m_scopes[nullptr];
} }
void NameAndTypeResolver::reset() Declaration* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const
{ {
m_scopes.clear(); auto iterator = m_scopes.find(_scope);
m_currentScope = nullptr; if (iterator == end(m_scopes))
return nullptr;
return iterator->second.resolveName(_name, false);
} }
Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive) Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive)
@ -68,8 +71,13 @@ Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name
return m_currentScope->resolveName(_name, _recursive); 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): ASTNode& _astRoot):
m_scopes(_scopes), m_currentScope(&m_scopes[nullptr]) m_scopes(_scopes), m_currentScope(&m_scopes[nullptr])
{ {
@ -101,27 +109,33 @@ void DeclarationRegistrationHelper::endVisit(StructDefinition&)
bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
{ {
registerDeclaration(_function, true); registerDeclaration(_function, true);
m_currentFunction = &_function;
return true; return true;
} }
void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
{ {
m_currentFunction = nullptr;
closeCurrentScope(); closeCurrentScope();
} }
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition)
{ {
registerDeclaration(_declaration, false); // Register the local variables with the function
return true; // 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) void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node)
{ {
map<ASTNode*, Scope>::iterator iter; map<ASTNode const*, Scope>::iterator iter;
bool newlyAdded; bool newlyAdded;
tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope)); tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope));
assert(newlyAdded); assert(newlyAdded);

17
libsolidity/NameAndTypeResolver.h

@ -41,6 +41,14 @@ public:
NameAndTypeResolver() {} NameAndTypeResolver() {}
void resolveNamesAndTypes(ContractDefinition& _contract); 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); Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true);
private: private:
@ -48,7 +56,7 @@ private:
//! Maps nodes declaring a scope to scopes, i.e. ContractDefinition, FunctionDeclaration and //! Maps nodes declaring a scope to scopes, i.e. ContractDefinition, FunctionDeclaration and
//! StructDefinition (@todo not yet implemented), where nullptr denotes the global scope. //! 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; Scope* m_currentScope;
}; };
@ -58,7 +66,7 @@ private:
class DeclarationRegistrationHelper: private ASTVisitor class DeclarationRegistrationHelper: private ASTVisitor
{ {
public: public:
DeclarationRegistrationHelper(std::map<ASTNode*, Scope>& _scopes, ASTNode& _astRoot); DeclarationRegistrationHelper(std::map<ASTNode const*, Scope>& _scopes, ASTNode& _astRoot);
private: private:
bool visit(ContractDefinition& _contract); bool visit(ContractDefinition& _contract);
@ -67,15 +75,16 @@ private:
void endVisit(StructDefinition& _struct); void endVisit(StructDefinition& _struct);
bool visit(FunctionDefinition& _function); bool visit(FunctionDefinition& _function);
void endVisit(FunctionDefinition& _function); void endVisit(FunctionDefinition& _function);
void endVisit(VariableDefinition& _variableDefinition);
bool visit(VariableDeclaration& _declaration); bool visit(VariableDeclaration& _declaration);
void endVisit(VariableDeclaration& _declaration);
void enterNewSubScope(ASTNode& _node); void enterNewSubScope(ASTNode& _node);
void closeCurrentScope(); void closeCurrentScope();
void registerDeclaration(Declaration& _declaration, bool _opensScope); void registerDeclaration(Declaration& _declaration, bool _opensScope);
std::map<ASTNode*, Scope>& m_scopes; std::map<ASTNode const*, Scope>& m_scopes;
Scope* m_currentScope; Scope* m_currentScope;
FunctionDefinition* m_currentFunction;
}; };
//! Resolves references to declarations (of variables and types) and also establishes the link //! 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 // starting from here, all statements must be terminated by a semicolon
case Token::CONTINUE: case Token::CONTINUE:
statement = ASTNodeFactory(*this).createNode<Continue>(); statement = ASTNodeFactory(*this).createNode<Continue>();
m_scanner->next();
break; break;
case Token::BREAK: case Token::BREAK:
statement = ASTNodeFactory(*this).createNode<Break>(); statement = ASTNodeFactory(*this).createNode<Break>();
m_scanner->next();
break; break;
case Token::RETURN: case Token::RETURN:
{ {
@ -283,9 +285,9 @@ ASTPointer<Statement> Parser::parseStatement()
} }
break; break;
default: 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) // (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. // in the case of a user-defined type, we have two identifiers following each other.
if (m_scanner->getCurrentToken() == Token::MAPPING || if (m_scanner->getCurrentToken() == Token::MAPPING ||
m_scanner->getCurrentToken() == Token::VAR || m_scanner->getCurrentToken() == Token::VAR ||
@ -293,8 +295,8 @@ ASTPointer<Statement> Parser::parseStatement()
(m_scanner->getCurrentToken() == Token::IDENTIFIER && (m_scanner->getCurrentToken() == Token::IDENTIFIER &&
m_scanner->peekNextToken() == Token::IDENTIFIER)) m_scanner->peekNextToken() == Token::IDENTIFIER))
statement = parseVariableDefinition(); statement = parseVariableDefinition();
else // "ordinary" expression else // "ordinary" expression statement
statement = parseExpression(); statement = parseExpressionStatement();
} }
expectToken(Token::SEMICOLON); expectToken(Token::SEMICOLON);
return statement; return statement;
@ -349,6 +351,14 @@ ASTPointer<VariableDefinition> Parser::parseVariableDefinition()
return nodeFactory.createNode<VariableDefinition>(variable, value); 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() ASTPointer<Expression> Parser::parseExpression()
{ {
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
@ -453,8 +463,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
{ {
case Token::TRUE_LITERAL: case Token::TRUE_LITERAL:
case Token::FALSE_LITERAL: case Token::FALSE_LITERAL:
expression = nodeFactory.createNode<Literal>(token, ASTPointer<ASTString>()); expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
m_scanner->next();
break; break;
case Token::NUMBER: case Token::NUMBER:
case Token::STRING_LITERAL: case Token::STRING_LITERAL:

1
libsolidity/Parser.h

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

18
libsolidity/Types.cpp

@ -159,14 +159,12 @@ std::string IntegerType::toString() const
return prefix + dev::toString(m_bits); return prefix + dev::toString(m_bits);
} }
bytes IntegerType::literalToBigEndian(const Literal& _literal) const u256 IntegerType::literalValue(const Literal& _literal) const
{ {
bigint value(_literal.getValue()); bigint value(_literal.getValue());
if (!isSigned() && value < 0) //@todo check that the number is not too large
return bytes(); // @todo this should already be caught by "smallestTypeforLiteral" //@todo does this work for signed numbers?
//@todo check that the number of bits is correct return u256(value);
//@todo does "toCompactBigEndian" work for signed numbers?
return toCompactBigEndian(value);
} }
bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
@ -182,14 +180,14 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
return isImplicitlyConvertibleTo(_convertTo); return isImplicitlyConvertibleTo(_convertTo);
} }
bytes BoolType::literalToBigEndian(const Literal& _literal) const u256 BoolType::literalValue(const Literal& _literal) const
{ {
if (_literal.getToken() == Token::TRUE_LITERAL) if (_literal.getToken() == Token::TRUE_LITERAL)
return bytes(1, 1); return u256(1);
else if (_literal.getToken() == Token::FALSE_LITERAL) else if (_literal.getToken() == Token::FALSE_LITERAL)
return bytes(1, 0); return u256(0);
else else
return NullBytes; assert(false);
} }
bool ContractType::operator==(const Type& _other) const 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 bool operator!=(Type const& _other) const { return !this->operator ==(_other); }
virtual std::string toString() const = 0; 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. /// Any kind of integer type including hash and address.
@ -94,7 +94,7 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual std::string toString() 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; } int getNumBits() const { return m_bits; }
bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; } 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 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. /// 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); 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) int main(int argc, char** argv)
{ {
string infile; string infile;
@ -143,13 +114,10 @@ int main(int argc, char** argv)
dev::solidity::ASTPrinter printer(ast, sourceCode); dev::solidity::ASTPrinter printer(ast, sourceCode);
printer.print(cout); printer.print(cout);
FirstExpressionExtractor extractor(*ast); bytes instructions;
CompilerContext context;
ExpressionCompiler compiler(context);
try try
{ {
compiler.compile(*extractor.getExpression()); instructions = Compiler::compile(*ast);
} }
catch (CompilerError const& exception) catch (CompilerError const& exception)
{ {
@ -157,10 +125,9 @@ int main(int argc, char** argv)
return -1; return -1;
} }
bytes instructions = compiler.getAssembledBytecode(); cout << "EVM assembly: " << endl;
cout << "Bytecode for the first expression: " << endl;
cout << eth::disassemble(instructions) << endl; cout << eth::disassemble(instructions) << endl;
cout << "Binary: " << toHex(instructions) << endl;
return 0; return 0;
} }

323
test/solidityCompiler.cpp

@ -18,7 +18,7 @@
/** /**
* @author Christian <c@ethdev.com> * @author Christian <c@ethdev.com>
* @date 2014 * @date 2014
* Unit tests for the name and type resolution of the solidity parser. * Unit tests for the solidity compiler.
*/ */
#include <string> #include <string>
@ -31,6 +31,9 @@
#include <libsolidity/AST.h> #include <libsolidity/AST.h>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
using namespace std;
using namespace dev::eth;
namespace dev namespace dev
{ {
namespace solidity namespace solidity
@ -41,233 +44,167 @@ namespace test
namespace namespace
{ {
/// Helper class that extracts the first expression in an AST. bytes compileContract(const string& _sourceCode)
class FirstExpressionExtractor: private ASTVisitor
{
public:
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); }
Expression* getExpression() const { return m_expression; }
private:
virtual bool visit(Expression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); }
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); }
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); }
bool checkExpression(Expression& _expression)
{
if (m_expression == nullptr)
m_expression = &_expression;
return false;
}
private:
Expression* m_expression;
};
bytes compileFirstExpression(const std::string& _sourceCode)
{ {
Parser parser; Parser parser;
ASTPointer<ContractDefinition> contract; 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; NameAndTypeResolver resolver;
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract));
FirstExpressionExtractor extractor(*contract);
BOOST_REQUIRE(extractor.getExpression() != nullptr);
CompilerContext context; bytes instructions = Compiler::compile(*contract);
ExpressionCompiler compiler(context);
compiler.compile(*extractor.getExpression());
bytes instructions = compiler.getAssembledBytecode();
// debug // debug
//std::cout << eth::disassemble(instructions) << std::endl; //cout << eth::disassemble(instructions) << endl;
return instructions; return instructions;
} }
} // end anonymous namespace } // 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" char const* sourceCode = "contract test {\n"
" function f() { var x = true; }" " function f() { var x = 2; }\n"
"}\n"; "}\n";
bytes code = compileFirstExpression(sourceCode); bytes code = compileContract(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x1}); 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_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" 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"; "}\n";
bytes code = compileFirstExpression(sourceCode); bytes code = compileContract(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x0}); 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_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" 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"; "}\n";
bytes code = compileFirstExpression(sourceCode); bytes code = compileContract(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90, bytes expectation({byte(Instruction::JUMPDEST),
0x12, 0x34, 0x56, 0x78, 0x90}); byte(Instruction::PUSH1), 0x0,
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); byte(Instruction::DUP1),
} byte(Instruction::PUSH1), 0x1b, // "true" target
byte(Instruction::JUMPI),
BOOST_AUTO_TEST_CASE(comparison) // new check "else if" condition
{ byte(Instruction::DUP1),
char const* sourceCode = "contract test {\n" byte(Instruction::NOT),
" function f() { var x = (0x10aa < 0x11aa) != true; }" byte(Instruction::PUSH1), 0x13,
"}\n"; byte(Instruction::JUMPI),
bytes code = compileFirstExpression(sourceCode); // "else" body
byte(Instruction::PUSH1), 0x4f,
bytes expectation({byte(eth::Instruction::PUSH2), 0x10, 0xaa, byte(Instruction::POP),
byte(eth::Instruction::PUSH2), 0x11, 0xaa, byte(Instruction::PUSH1), 0x17, // exit path of second part
byte(eth::Instruction::GT), byte(Instruction::JUMP),
byte(eth::Instruction::PUSH1), 0x1, // "else if" body
byte(eth::Instruction::EQ), byte(Instruction::JUMPDEST),
byte(eth::Instruction::NOT)}); byte(Instruction::PUSH1), 0x4e,
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); byte(Instruction::POP),
} byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x1f,
BOOST_AUTO_TEST_CASE(short_circuiting) byte(Instruction::JUMP),
{ // "if" body
char const* sourceCode = "contract test {\n" byte(Instruction::JUMPDEST),
" function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }" byte(Instruction::PUSH1), 0x4d,
"}\n"; byte(Instruction::POP),
bytes code = compileFirstExpression(sourceCode); byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
bytes expectation({byte(eth::Instruction::PUSH1), 0xa, byte(Instruction::POP),
byte(eth::Instruction::PUSH1), 0x8, byte(Instruction::JUMP)});
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)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); 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" 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"; "}\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()); 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" char const* text = "contract test {\n"
" function fun(uint256 a) {\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"
"}\n"; "}\n";
BOOST_CHECK_NO_THROW(parseText(text)); BOOST_CHECK_NO_THROW(parseText(text));

Loading…
Cancel
Save