Browse Source

Merge pull request #461 from chriseth/sol_contractCompiler

Solidity contract compiler
cl-refactor
Gav Wood 10 years ago
parent
commit
2bf96a639c
  1. 2
      alethzero/CMakeLists.txt
  2. 24
      alethzero/MainWin.cpp
  3. 26
      libevmface/Instruction.h
  4. 10
      liblll/Assembly.h
  5. 81
      libsolidity/AST.cpp
  6. 104
      libsolidity/AST.h
  7. 1
      libsolidity/ASTForward.h
  8. 12
      libsolidity/ASTPrinter.cpp
  9. 2
      libsolidity/ASTPrinter.h
  10. 2
      libsolidity/ASTVisitor.h
  11. 4
      libsolidity/CMakeLists.txt
  12. 506
      libsolidity/Compiler.cpp
  13. 138
      libsolidity/Compiler.h
  14. 61
      libsolidity/CompilerContext.cpp
  15. 89
      libsolidity/CompilerContext.h
  16. 49
      libsolidity/CompilerStack.cpp
  17. 43
      libsolidity/CompilerStack.h
  18. 2
      libsolidity/Exceptions.h
  19. 410
      libsolidity/ExpressionCompiler.cpp
  20. 79
      libsolidity/ExpressionCompiler.h
  21. 46
      libsolidity/NameAndTypeResolver.cpp
  22. 17
      libsolidity/NameAndTypeResolver.h
  23. 19
      libsolidity/Parser.cpp
  24. 1
      libsolidity/Parser.h
  25. 38
      libsolidity/Scanner.cpp
  26. 5
      libsolidity/Scanner.h
  27. 64
      libsolidity/Token.h
  28. 30
      libsolidity/Types.cpp
  29. 19
      libsolidity/Types.h
  30. 124
      solc/main.cpp
  31. 307
      test/solidityCompiler.cpp
  32. 229
      test/solidityEndToEndTest.cpp
  33. 352
      test/solidityExpressionCompiler.cpp

2
alethzero/CMakeLists.txt

@ -53,7 +53,7 @@ else ()
endif ()
qt5_use_modules(${EXECUTEABLE} Core)# Gui Widgets Network WebKit WebKitWidgets)
target_link_libraries(${EXECUTEABLE} webthree qethereum ethereum evm ethcore devcrypto secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore web3jsonrpc jsqrc)
target_link_libraries(${EXECUTEABLE} webthree qethereum ethereum evm ethcore devcrypto secp256k1 gmp ${CRYPTOPP_LS} serpent lll solidity evmface devcore web3jsonrpc jsqrc)
if (APPLE)
# First have qt5 install plugins and frameworks

24
alethzero/MainWin.cpp

@ -36,6 +36,9 @@
#include <libdevcore/CommonJS.h>
#include <liblll/Compiler.h>
#include <liblll/CodeFragment.h>
#include <libsolidity/Scanner.h>
#include <libsolidity/CompilerStack.h>
#include <libsolidity/SourceReferenceFormatter.h>
#include <libevm/VM.h>
#include <libethereum/BlockChain.h>
#include <libethereum/ExtVM.h>
@ -1566,10 +1569,29 @@ void Main::on_data_textChanged()
string src = ui->data->toPlainText().toStdString();
vector<string> errors;
QString lll;
QString solidity;
if (src.find_first_not_of("1234567890abcdefABCDEF") == string::npos && src.size() % 2 == 0)
{
m_data = fromHex(src);
}
else if (src.substr(0, 8) == "contract") // improve this heuristic
{
shared_ptr<solidity::Scanner> scanner = make_shared<solidity::Scanner>();
try
{
m_data = dev::solidity::CompilerStack::compile(src, scanner);
}
catch (dev::Exception const& exception)
{
ostringstream error;
solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", *scanner);
solidity = "<h4>Solidity</h4><pre>" + QString::fromStdString(error.str()).toHtmlEscaped() + "</pre>";
}
catch (...)
{
solidity = "<h4>Solidity</h4><pre>Uncaught exception.</pre>";
}
}
else
{
m_data = dev::eth::compileLLL(src, m_enableOptimizer, &errors);
@ -1604,7 +1626,7 @@ void Main::on_data_textChanged()
for (auto const& i: errors)
errs.append("<div style=\"border-left: 6px solid #c00; margin-top: 2px\">" + QString::fromStdString(i).toHtmlEscaped() + "</div>");
}
ui->code->setHtml(errs + lll + "<h4>Code</h4>" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped());
ui->code->setHtml(errs + lll + solidity + "<h4>Code</h4>" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped());
ui->gas->setMinimum((qint64)Client::txGas(m_data.size(), 0));
if (!ui->gas->isEnabled())
ui->gas->setValue(m_backupGas);

26
libevmface/Instruction.h

@ -32,6 +32,8 @@ namespace dev
namespace eth
{
struct InvalidOpcode: virtual Exception {};
/// Virtual machine bytecode instruction.
enum class Instruction: uint8_t
{
@ -176,6 +178,30 @@ enum class Instruction: uint8_t
SUICIDE = 0xff ///< halt execution and register account for later deletion
};
/// @returns the PUSH<_number> instruction
inline Instruction pushInstruction(unsigned _number)
{
if (asserts(1 <= _number && _number <= 32))
BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid PUSH instruction requested."));
return Instruction(unsigned(Instruction::PUSH1) + _number - 1);
}
/// @returns the DUP<_number> instruction
inline Instruction dupInstruction(unsigned _number)
{
if (asserts(1 <= _number && _number <= 16))
BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid DUP instruction requested."));
return Instruction(unsigned(Instruction::DUP1) + _number - 1);
}
/// @returns the SWAP<_number> instruction
inline Instruction swapInstruction(unsigned _number)
{
if (asserts(1 <= _number && _number <= 16))
BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid SWAP instruction requested."));
return Instruction(unsigned(Instruction::SWAP1) + _number - 1);
}
/// Information structure for a particular instruction.
struct InstructionInfo
{

10
liblll/Assembly.h

@ -45,8 +45,8 @@ public:
AssemblyItem(Instruction _i): m_type(Operation), m_data((byte)_i) {}
AssemblyItem(AssemblyItemType _type, u256 _data = 0): m_type(_type), m_data(_data) {}
AssemblyItem tag() const { assert(m_type == PushTag || m_type == Tag); return AssemblyItem(Tag, m_data); }
AssemblyItem pushTag() const { assert(m_type == PushTag || m_type == Tag); return AssemblyItem(PushTag, m_data); }
AssemblyItem tag() const { if (asserts(m_type == PushTag || m_type == Tag)) BOOST_THROW_EXCEPTION(Exception()); return AssemblyItem(Tag, m_data); }
AssemblyItem pushTag() const { if (asserts(m_type == PushTag || m_type == Tag)) BOOST_THROW_EXCEPTION(Exception()); return AssemblyItem(PushTag, m_data); }
AssemblyItemType type() const { return m_type; }
u256 data() const { return m_data; }
@ -94,7 +94,7 @@ public:
AssemblyItem const& back() { return m_items.back(); }
std::string backString() const { return m_items.size() && m_items.back().m_type == PushString ? m_strings.at((h256)m_items.back().m_data) : std::string(); }
void onePath() { assert(!m_totalDeposit && !m_baseDeposit); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; }
void onePath() { if (asserts(!m_totalDeposit && !m_baseDeposit)) BOOST_THROW_EXCEPTION(InvalidDeposit()); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; }
void otherPath() { donePath(); m_totalDeposit = m_deposit; m_deposit = m_baseDeposit; }
void donePaths() { donePath(); m_totalDeposit = m_baseDeposit = 0; }
void ignored() { m_baseDeposit = m_deposit; }
@ -105,7 +105,11 @@ public:
void injectStart(AssemblyItem const& _i);
std::string out() const { std::stringstream ret; streamRLP(ret); return ret.str(); }
int deposit() const { return m_deposit; }
void adjustDeposit(int _adjustment) { m_deposit += _adjustment; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
void setDeposit(int _deposit) { m_deposit = _deposit; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
bytes assemble() const;
Assembly& optimise(bool _enable);
std::ostream& streamRLP(std::ostream& _out, std::string const& _prefix = "") const;

81
libsolidity/AST.cpp

@ -167,6 +167,14 @@ void Return::accept(ASTVisitor& _visitor)
_visitor.endVisit(*this);
}
void ExpressionStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
if (m_expression)
m_expression->accept(_visitor);
_visitor.endVisit(*this);
}
void VariableDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
@ -255,14 +263,6 @@ TypeError ASTNode::createTypeError(string const& _description)
return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description);
}
void Statement::expectType(Expression& _expression, Type const& _expectedType)
{
_expression.checkTypeRequirements();
if (!_expression.getType()->isImplicitlyConvertibleTo(_expectedType))
BOOST_THROW_EXCEPTION(_expression.createTypeError("Type not implicitly convertible to expected type."));
//@todo provide more information to the exception
}
void Block::checkTypeRequirements()
{
for (shared_ptr<Statement> const& statement: m_statements)
@ -271,7 +271,7 @@ void Block::checkTypeRequirements()
void IfStatement::checkTypeRequirements()
{
expectType(*m_condition, BoolType());
m_condition->expectType(BoolType());
m_trueBody->checkTypeRequirements();
if (m_falseBody)
m_falseBody->checkTypeRequirements();
@ -279,7 +279,7 @@ void IfStatement::checkTypeRequirements()
void WhileStatement::checkTypeRequirements()
{
expectType(*m_condition, BoolType());
m_condition->expectType(BoolType());
m_body->checkTypeRequirements();
}
@ -293,13 +293,16 @@ void Break::checkTypeRequirements()
void Return::checkTypeRequirements()
{
assert(m_returnParameters);
if (!m_expression)
return;
if (asserts(m_returnParameters))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not assigned."));
if (m_returnParameters->getParameters().size() != 1)
BOOST_THROW_EXCEPTION(createTypeError("Different number of arguments in return statement "
"than in returns declaration."));
// this could later be changed such that the paramaters type is an anonymous struct type,
// but for now, we only allow one return parameter
expectType(*m_expression, *m_returnParameters->getParameters().front()->getType());
m_expression->expectType(*m_returnParameters->getParameters().front()->getType());
}
void VariableDefinition::checkTypeRequirements()
@ -311,7 +314,7 @@ void VariableDefinition::checkTypeRequirements()
if (m_value)
{
if (m_variable->getType())
expectType(*m_value, *m_variable->getType());
m_value->expectType(*m_variable->getType());
else
{
// no type declared and no previous assignment, infer the type
@ -326,20 +329,36 @@ void Assignment::checkTypeRequirements()
//@todo lefthandside actually has to be assignable
// add a feature to the type system to check that
m_leftHandSide->checkTypeRequirements();
expectType(*m_rightHandSide, *m_leftHandSide->getType());
if (!m_leftHandSide->isLvalue())
BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue."));
m_rightHandSide->expectType(*m_leftHandSide->getType());
m_type = m_leftHandSide->getType();
if (m_assigmentOperator != Token::ASSIGN)
{
// compound assignment
if (!m_type->acceptsBinaryOperator(Token::AssignmentToBinaryOp(m_assigmentOperator)))
BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type."));
}
void ExpressionStatement::checkTypeRequirements()
{
m_expression->checkTypeRequirements();
}
void Expression::expectType(Type const& _expectedType)
{
checkTypeRequirements();
if (!getType()->isImplicitlyConvertibleTo(_expectedType))
BOOST_THROW_EXCEPTION(createTypeError("Type not implicitly convertible to expected type."));
//@todo provide more information to the exception
}
void UnaryOperation::checkTypeRequirements()
{
// INC, DEC, NOT, BIT_NOT, DELETE
// INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE
m_subExpression->checkTypeRequirements();
if (m_operator == Token::Value::INC || m_operator == Token::Value::DEC || m_operator == Token::Value::DELETE)
if (!m_subExpression->isLvalue())
BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue."));
m_type = m_subExpression->getType();
if (!m_type->acceptsUnaryOperator(m_operator))
BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type."));
@ -359,7 +378,6 @@ void BinaryOperation::checkTypeRequirements()
m_type = make_shared<BoolType>();
else
{
assert(Token::isBinaryOp(m_operator));
m_type = m_commonType;
if (!m_commonType->acceptsBinaryOperator(m_operator))
BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type."));
@ -375,25 +393,22 @@ void FunctionCall::checkTypeRequirements()
Type const* expressionType = m_expression->getType().get();
if (isTypeConversion())
{
TypeType const* type = dynamic_cast<TypeType const*>(expressionType);
assert(type);
TypeType const& type = dynamic_cast<TypeType const&>(*expressionType);
//@todo for structs, we have to check the number of arguments to be equal to the
// number of non-mapping members
if (m_arguments.size() != 1)
BOOST_THROW_EXCEPTION(createTypeError("More than one argument for "
"explicit type conersion."));
if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type->getActualType()))
if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type.getActualType()))
BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed."));
m_type = type->getActualType();
m_type = type.getActualType();
}
else
{
//@todo would be nice to create a struct type from the arguments
// and then ask if that is implicitly convertible to the struct represented by the
// function parameters
FunctionType const* function = dynamic_cast<FunctionType const*>(expressionType);
assert(function);
FunctionDefinition const& fun = function->getFunction();
FunctionDefinition const& fun = dynamic_cast<FunctionType const&>(*expressionType).getFunction();
vector<ASTPointer<VariableDeclaration>> const& parameters = fun.getParameters();
if (parameters.size() != m_arguments.size())
BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call."));
@ -402,10 +417,10 @@ void FunctionCall::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call."));
// @todo actually the return type should be an anonymous struct,
// but we change it to the type of the first return value until we have structs
if (fun.getReturnParameterList()->getParameters().empty())
if (fun.getReturnParameters().empty())
m_type = make_shared<VoidType>();
else
m_type = fun.getReturnParameterList()->getParameters().front()->getType();
m_type = fun.getReturnParameters().front()->getType();
}
}
@ -416,19 +431,21 @@ bool FunctionCall::isTypeConversion() const
void MemberAccess::checkTypeRequirements()
{
assert(false); // not yet implemented
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access not yet implemented."));
// m_type = ;
}
void IndexAccess::checkTypeRequirements()
{
assert(false); // not yet implemented
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Index access not yet implemented."));
// m_type = ;
}
void Identifier::checkTypeRequirements()
{
assert(m_referencedDeclaration);
if (asserts(m_referencedDeclaration))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier not resolved."));
//@todo these dynamic casts here are not really nice...
// is i useful to have an AST visitor here?
// or can this already be done in NameAndTypeResolver?
@ -441,9 +458,9 @@ void Identifier::checkTypeRequirements()
if (variable)
{
if (!variable->getType())
BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type "
"could be determined."));
BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type could be determined."));
m_type = variable->getType();
m_isLvalue = true;
return;
}
//@todo can we unify these with TypeName::toType()?
@ -469,7 +486,7 @@ void Identifier::checkTypeRequirements()
m_type = make_shared<TypeType>(make_shared<ContractType>(*contractDef));
return;
}
assert(false); // declaration reference of unknown/forbidden type
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration reference of unknown/forbidden type."));
}
void ElementaryTypeNameExpression::checkTypeRequirements()

104
libsolidity/AST.h

@ -152,7 +152,7 @@ public:
ASTNode(_location), m_parameters(_parameters) {}
virtual void accept(ASTVisitor& _visitor) override;
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() { return m_parameters; }
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters; }
private:
std::vector<ASTPointer<VariableDeclaration>> m_parameters;
@ -175,15 +175,21 @@ public:
bool isDeclaredConst() const { return m_isDeclaredConst; }
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); }
ParameterList& getParameterList() { return *m_parameters; }
std::vector<ASTPointer<VariableDeclaration>> const& getReturnParameters() const { return m_returnParameters->getParameters(); }
ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; }
Block& getBody() { return *m_body; }
void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); }
std::vector<VariableDeclaration const*> const& getLocalVariables() const { return m_localVariables; }
private:
bool m_isPublic;
ASTPointer<ParameterList> m_parameters;
bool m_isDeclaredConst;
ASTPointer<ParameterList> m_returnParameters;
ASTPointer<Block> m_body;
std::vector<VariableDeclaration const*> m_localVariables;
};
/**
@ -237,7 +243,10 @@ class ElementaryTypeName: public TypeName
{
public:
explicit ElementaryTypeName(Location const& _location, Token::Value _type):
TypeName(_location), m_type(_type) {}
TypeName(_location), m_type(_type)
{
if (asserts(Token::isElementaryTypeName(_type))) BOOST_THROW_EXCEPTION(InternalCompilerError());
}
virtual void accept(ASTVisitor& _visitor) override;
virtual std::shared_ptr<Type> toType() override { return Type::fromElementaryTypeName(m_type); }
@ -305,11 +314,6 @@ public:
/// This includes checking that operators are applicable to their arguments but also that
/// the number of function call arguments matches the number of formal parameters and so forth.
virtual void checkTypeRequirements() = 0;
protected:
/// Helper function, check that the inferred type for @a _expression is @a _expectedType or at
/// least implicitly convertible to @a _expectedType. If not, throw exception.
void expectType(Expression& _expression, Type const& _expectedType);
};
/**
@ -342,6 +346,11 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getCondition() const { return *m_condition; }
Statement& getTrueStatement() const { return *m_trueBody; }
/// @returns the "else" part of the if statement or nullptr if there is no "else" part.
Statement* getFalseStatement() const { return m_falseBody.get(); }
private:
ASTPointer<Expression> m_condition;
ASTPointer<Statement> m_trueBody;
@ -368,6 +377,9 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getCondition() const { return *m_condition; }
Statement& getBody() const { return *m_body; }
private:
ASTPointer<Expression> m_condition;
ASTPointer<Statement> m_body;
@ -398,6 +410,13 @@ public:
virtual void checkTypeRequirements() override;
void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; }
ParameterList const& getFunctionReturnParameters() const
{
if (asserts(m_returnParameters))
BOOST_THROW_EXCEPTION(InternalCompilerError());
return *m_returnParameters;
}
Expression* getExpression() const { return m_expression.get(); }
private:
ASTPointer<Expression> m_expression; ///< value to return, optional
@ -420,25 +439,29 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
VariableDeclaration const& getDeclaration() const { return *m_variable; }
Expression* getExpression() const { return m_value.get(); }
private:
ASTPointer<VariableDeclaration> m_variable;
ASTPointer<Expression> m_value; ///< the assigned value, can be missing
};
/**
* An expression, i.e. something that has a value (which can also be of type "void" in case
* of function calls).
* A statement that contains only an expression (i.e. an assignment, function call, ...).
*/
class Expression: public Statement
class ExpressionStatement: public Statement
{
public:
Expression(Location const& _location): Statement(_location) {}
ExpressionStatement(Location const& _location, ASTPointer<Expression> _expression):
Statement(_location), m_expression(_expression) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
std::shared_ptr<Type const> const& getType() const { return m_type; }
Expression& getExpression() const { return *m_expression; }
protected:
/// Inferred type of the expression, only filled after a call to checkTypeRequirements().
std::shared_ptr<Type const> m_type;
private:
ASTPointer<Expression> m_expression;
};
/// @}
@ -447,16 +470,43 @@ protected:
/// @{
/**
* Assignment, can also be a compound assignment.
* Examples: (a = 7 + 8) or (a *= 2)
* An expression, i.e. something that has a value (which can also be of type "void" in case
* of some function calls).
* @abstract
*/
class Expression: public ASTNode
{
public:
Expression(Location const& _location): ASTNode(_location), m_isLvalue(false) {}
virtual void checkTypeRequirements() = 0;
std::shared_ptr<Type const> const& getType() const { return m_type; }
bool isLvalue() const { return m_isLvalue; }
/// Helper function, infer the type via @ref checkTypeRequirements and then check that it
/// is implicitly convertible to @a _expectedType. If not, throw exception.
void expectType(Type const& _expectedType);
protected:
//! Inferred type of the expression, only filled after a call to checkTypeRequirements().
std::shared_ptr<Type const> m_type;
//! Whether or not this expression is an lvalue, i.e. something that can be assigned to.
//! This is set during calls to @a checkTypeRequirements()
bool m_isLvalue;
};
/// Assignment, can also be a compound assignment.
/// Examples: (a = 7 + 8) or (a *= 2)
class Assignment: public Expression
{
public:
Assignment(Location const& _location, ASTPointer<Expression> const& _leftHandSide,
Token::Value _assignmentOperator, ASTPointer<Expression> const& _rightHandSide):
Expression(_location), m_leftHandSide(_leftHandSide),
m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) {}
m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide)
{
if (asserts(Token::isAssignmentOp(_assignmentOperator))) BOOST_THROW_EXCEPTION(InternalCompilerError());
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
@ -480,7 +530,10 @@ public:
UnaryOperation(Location const& _location, Token::Value _operator,
ASTPointer<Expression> const& _subExpression, bool _isPrefix):
Expression(_location), m_operator(_operator),
m_subExpression(_subExpression), m_isPrefix(_isPrefix) {}
m_subExpression(_subExpression), m_isPrefix(_isPrefix)
{
if (asserts(Token::isUnaryOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError());
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
@ -502,7 +555,10 @@ class BinaryOperation: public Expression
public:
BinaryOperation(Location const& _location, ASTPointer<Expression> const& _left,
Token::Value _operator, ASTPointer<Expression> const& _right):
Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) {}
Expression(_location), m_left(_left), m_operator(_operator), m_right(_right)
{
if (asserts(Token::isBinaryOp(_operator) || Token::isCompareOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError());
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
@ -530,6 +586,9 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getExpression() const { return *m_expression; }
std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; }
/// Returns true if this is not an actual function call, but an explicit type conversion
/// or constructor call.
bool isTypeConversion() const;
@ -616,7 +675,10 @@ class ElementaryTypeNameExpression: public PrimaryExpression
{
public:
ElementaryTypeNameExpression(Location const& _location, Token::Value _typeToken):
PrimaryExpression(_location), m_typeToken(_typeToken) {}
PrimaryExpression(_location), m_typeToken(_typeToken)
{
if (asserts(Token::isElementaryTypeName(_typeToken))) BOOST_THROW_EXCEPTION(InternalCompilerError());
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;

1
libsolidity/ASTForward.h

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

12
libsolidity/ASTPrinter.cpp

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

2
libsolidity/ASTPrinter.h

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

2
libsolidity/ASTVisitor.h

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

4
libsolidity/CMakeLists.txt

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

506
libsolidity/Compiler.cpp

@ -17,387 +17,291 @@
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Solidity AST to EVM bytecode compiler.
* Solidity compiler.
*/
#include <cassert>
#include <utility>
#include <algorithm>
#include <libsolidity/AST.h>
#include <libsolidity/Compiler.h>
#include <libsolidity/ExpressionCompiler.h>
using namespace std;
namespace dev {
namespace solidity {
void CompilerContext::setLabelPosition(uint32_t _label, uint32_t _position)
bytes Compiler::compile(ContractDefinition& _contract)
{
assert(m_labelPositions.find(_label) == m_labelPositions.end());
m_labelPositions[_label] = _position;
Compiler compiler;
compiler.compileContract(_contract);
return compiler.m_context.getAssembledBytecode();
}
uint32_t CompilerContext::getLabelPosition(uint32_t _label) const
void Compiler::compileContract(ContractDefinition& _contract)
{
auto iter = m_labelPositions.find(_label);
assert(iter != m_labelPositions.end());
return iter->second;
m_context = CompilerContext(); // clear it just in case
//@todo constructor
//@todo register state variables
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
m_context.addFunction(*function);
appendFunctionSelector(_contract.getDefinedFunctions());
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
function->accept(*this);
packIntoContractCreator();
}
void ExpressionCompiler::compile(Expression& _expression)
void Compiler::packIntoContractCreator()
{
m_assemblyItems.clear();
_expression.accept(*this);
CompilerContext creatorContext;
eth::AssemblyItem sub = creatorContext.addSubroutine(m_context.getAssembly());
// stack contains sub size
creatorContext << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY;
creatorContext << u256(0) << eth::Instruction::RETURN;
swap(m_context, creatorContext);
}
bytes ExpressionCompiler::getAssembledBytecode() const
void Compiler::appendFunctionSelector(vector<ASTPointer<FunctionDefinition>> const& _functions)
{
bytes assembled;
assembled.reserve(m_assemblyItems.size());
// sort all public functions and store them together with a tag for their argument decoding section
map<string, pair<FunctionDefinition const*, eth::AssemblyItem>> publicFunctions;
for (ASTPointer<FunctionDefinition> const& f: _functions)
if (f->isPublic())
publicFunctions.insert(make_pair(f->getName(), make_pair(f.get(), m_context.newTag())));
// resolve label references
for (uint32_t pos = 0; pos < m_assemblyItems.size(); ++pos)
{
AssemblyItem const& item = m_assemblyItems[pos];
if (item.getType() == AssemblyItem::Type::LABEL)
m_context.setLabelPosition(item.getLabel(), pos + 1);
}
//@todo remove constructor
for (AssemblyItem const& item: m_assemblyItems)
if (item.getType() == AssemblyItem::Type::LABELREF)
assembled.push_back(m_context.getLabelPosition(item.getLabel()));
else
assembled.push_back(item.getData());
if (publicFunctions.size() > 255)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("More than 255 public functions for contract."));
return assembled;
}
//@todo check for calldatasize?
// retrieve the first byte of the call data
m_context << u256(0) << eth::Instruction::CALLDATALOAD << u256(0) << eth::Instruction::BYTE;
// check that it is not too large
m_context << eth::Instruction::DUP1 << u256(publicFunctions.size() - 1) << eth::Instruction::LT;
eth::AssemblyItem returnTag = m_context.appendConditionalJump();
AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context,
Expression& _expression)
{
ExpressionCompiler compiler(_context);
compiler.compile(_expression);
return compiler.getAssemblyItems();
}
// otherwise, jump inside jump table (each entry of the table has size 4)
m_context << u256(4) << eth::Instruction::MUL;
eth::AssemblyItem jumpTableStart = m_context.pushNewTag();
m_context << eth::Instruction::ADD << eth::Instruction::JUMP;
void ExpressionCompiler::endVisit(Assignment& _assignment)
{
Expression& rightHandSide = _assignment.getRightHandSide();
Token::Value op = _assignment.getAssignmentOperator();
if (op != Token::ASSIGN)
{
// compound assignment
// @todo retrieve lvalue value
rightHandSide.accept(*this);
Type const& resultType = *_assignment.getType();
cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType);
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType);
}
else
rightHandSide.accept(*this);
// @todo store value
}
// jump table @todo it could be that the optimizer destroys this
m_context << jumpTableStart;
for (pair<string, pair<FunctionDefinition const*, eth::AssemblyItem>> const& f: publicFunctions)
m_context.appendJumpTo(f.second.second) << eth::Instruction::JUMPDEST;
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::ISZERO);
break;
case Token::BIT_NOT: // ~
append(eth::Instruction::NOT);
break;
case Token::DELETE: // delete
// a -> a xor a (= 0).
// @todo this should also be an assignment
// @todo semantics change for complex types
append(eth::Instruction::DUP1);
append(eth::Instruction::XOR);
break;
case Token::INC: // ++ (pre- or postfix)
// @todo this should also be an assignment
if (_unaryOperation.isPrefixOperation())
{
append(eth::Instruction::PUSH1);
append(1);
append(eth::Instruction::ADD);
}
break;
case Token::DEC: // -- (pre- or postfix)
// @todo this should also be an assignment
if (_unaryOperation.isPrefixOperation())
m_context << returnTag << eth::Instruction::STOP;
for (pair<string, pair<FunctionDefinition const*, eth::AssemblyItem>> const& f: publicFunctions)
{
append(eth::Instruction::PUSH1);
append(1);
append(eth::Instruction::SWAP1); //@todo avoid this
append(eth::Instruction::SUB);
}
break;
case Token::ADD: // +
// unary add, so basically no-op
break;
case Token::SUB: // -
// unary -x translates into "0-x"
append(eth::Instruction::PUSH1);
append(0);
append(eth::Instruction::SUB);
break;
default:
assert(false); // invalid operation
FunctionDefinition const& function = *f.second.first;
m_context << f.second.second;
eth::AssemblyItem returnTag = m_context.pushNewTag();
appendCalldataUnpacker(function);
m_context.appendJumpTo(m_context.getFunctionEntryLabel(function));
m_context << returnTag;
appendReturnValuePacker(function);
}
}
bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation)
void Compiler::appendCalldataUnpacker(FunctionDefinition const& _function)
{
Expression& leftExpression = _binaryOperation.getLeftExpression();
Expression& rightExpression = _binaryOperation.getRightExpression();
Type const& resultType = *_binaryOperation.getType();
Token::Value const op = _binaryOperation.getOperator();
// We do not check the calldata size, everything is zero-padded.
unsigned dataOffset = 1;
if (op == Token::AND || op == Token::OR)
{
// special case: short-circuiting
appendAndOrOperatorCode(_binaryOperation);
}
else if (Token::isCompareOp(op))
//@todo this can be done more efficiently, saving some CALLDATALOAD calls
for (ASTPointer<VariableDeclaration> const& var: _function.getParameters())
{
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());
}
unsigned const numBytes = var->getType()->getCalldataEncodedSize();
if (numBytes == 0)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_sourceLocation(var->getLocation())
<< errinfo_comment("Type not yet supported."));
if (numBytes == 32)
m_context << u256(dataOffset) << eth::Instruction::CALLDATALOAD;
else
{
leftExpression.accept(*this);
cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType);
rightExpression.accept(*this);
cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType);
appendOrdinaryBinaryOperatorCode(op, resultType);
m_context << (u256(1) << ((32 - numBytes) * 8)) << u256(dataOffset)
<< eth::Instruction::CALLDATALOAD << eth::Instruction::DIV;
dataOffset += numBytes;
}
// do not visit the child nodes, we already did that explicitly
return false;
}
void ExpressionCompiler::endVisit(FunctionCall& _functionCall)
void Compiler::appendReturnValuePacker(FunctionDefinition const& _function)
{
if (_functionCall.isTypeConversion())
//@todo this can be also done more efficiently
unsigned dataOffset = 0;
vector<ASTPointer<VariableDeclaration>> const& parameters = _function.getReturnParameters();
for (unsigned i = 0; i < parameters.size(); ++i)
{
//@todo binary representation for all supported types (bool and int) is the same, so no-op
// here for now.
}
else
{
//@todo
unsigned numBytes = parameters[i]->getType()->getCalldataEncodedSize();
if (numBytes == 0)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_sourceLocation(parameters[i]->getLocation())
<< errinfo_comment("Type not yet supported."));
m_context << eth::dupInstruction(parameters.size() - i);
if (numBytes != 32)
m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL;
m_context << u256(dataOffset) << eth::Instruction::MSTORE;
dataOffset += numBytes;
}
// note that the stack is not cleaned up here
m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN;
}
void ExpressionCompiler::endVisit(MemberAccess&)
bool Compiler::visit(FunctionDefinition& _function)
{
//@todo to simplify this, the calling convention could by changed such that
// caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn]
// although note that this reduces the size of the visible stack
}
m_context.startNewFunction();
m_returnTag = m_context.newTag();
m_breakTags.clear();
m_continueTags.clear();
void ExpressionCompiler::endVisit(IndexAccess&)
{
m_context << m_context.getFunctionEntryLabel(_function);
}
// stack upon entry: [return address] [arg0] [arg1] ... [argn]
// reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
void ExpressionCompiler::endVisit(Identifier&)
{
unsigned const numArguments = _function.getParameters().size();
unsigned const numReturnValues = _function.getReturnParameters().size();
unsigned const numLocalVariables = _function.getLocalVariables().size();
}
for (ASTPointer<VariableDeclaration> const& variable: _function.getParameters() + _function.getReturnParameters())
m_context.addVariable(*variable);
for (VariableDeclaration const* localVariable: _function.getLocalVariables())
m_context.addVariable(*localVariable);
m_context.initializeLocalVariables(numReturnValues + numLocalVariables);
void ExpressionCompiler::endVisit(Literal& _literal)
{
switch (_literal.getType()->getCategory())
{
case Type::Category::INTEGER:
case Type::Category::BOOL:
{
bytes value = _literal.getType()->literalToBigEndian(_literal);
assert(value.size() <= 32);
assert(!value.empty());
append(static_cast<byte>(eth::Instruction::PUSH1) + static_cast<byte>(value.size() - 1));
append(value);
break;
}
default:
assert(false); // @todo
}
}
_function.getBody().accept(*this);
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)
m_context << m_returnTag;
// 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);
while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
//@todo
m_context << eth::Instruction::POP;
stackLayout.pop_back();
}
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.
}
m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1);
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
//@todo assert that everything is in place now
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation)
{
Token::Value const op = _binaryOperation.getOperator();
assert(op == Token::OR || op == Token::AND);
_binaryOperation.getLeftExpression().accept(*this);
append(eth::Instruction::DUP1);
if (op == Token::AND)
append(eth::Instruction::NOT);
uint32_t endLabel = appendConditionalJump();
_binaryOperation.getRightExpression().accept(*this);
appendLabel(endLabel);
}
m_context << eth::Instruction::JUMP;
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
{
if (_operator == Token::EQ || _operator == Token::NE)
{
append(eth::Instruction::EQ);
if (_operator == Token::NE)
append(eth::Instruction::NOT);
return false;
}
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)
bool Compiler::visit(IfStatement& _ifStatement)
{
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);
}
}
ExpressionCompiler::compileExpression(m_context, _ifStatement.getCondition());
eth::AssemblyItem trueTag = m_context.appendConditionalJump();
if (_ifStatement.getFalseStatement())
_ifStatement.getFalseStatement()->accept(*this);
eth::AssemblyItem endTag = m_context.appendJump();
m_context << trueTag;
_ifStatement.getTrueStatement().accept(*this);
m_context << endTag;
return false;
}
void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type)
bool Compiler::visit(WhileStatement& _whileStatement)
{
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
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
m_continueTags.push_back(loopStart);
m_breakTags.push_back(loopEnd);
m_context << loopStart;
ExpressionCompiler::compileExpression(m_context, _whileStatement.getCondition());
m_context << eth::Instruction::ISZERO;
m_context.appendConditionalJumpTo(loopEnd);
_whileStatement.getBody().accept(*this);
m_context.appendJumpTo(loopStart);
m_context << loopEnd;
m_continueTags.pop_back();
m_breakTags.pop_back();
return false;
}
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
bool Compiler::visit(Continue&)
{
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
assert(type);
bool const isSigned = type->isSigned();
if (asserts(!m_continueTags.empty()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Jump tag not available for \"continue\"."));
m_context.appendJumpTo(m_continueTags.back());
return false;
}
switch (_operator)
bool Compiler::visit(Break&)
{
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(eth::Instruction::SWAP1);
append(isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV);
break;
case Token::MOD:
append(eth::Instruction::SWAP1);
append(isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD);
break;
default:
assert(false);
}
if (asserts(!m_breakTags.empty()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Jump tag not available for \"break\"."));
m_context.appendJumpTo(m_breakTags.back());
return false;
}
void ExpressionCompiler::appendBitOperatorCode(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::BIT_OR:
append(eth::Instruction::OR);
break;
case Token::BIT_AND:
append(eth::Instruction::AND);
break;
case Token::BIT_XOR:
append(eth::Instruction::XOR);
break;
default:
assert(false);
ExpressionCompiler::compileExpression(m_context, *expression);
VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front();
ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), *firstVariable.getType());
int stackPosition = m_context.getStackPositionOfVariable(firstVariable);
m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP;
}
m_context.appendJumpTo(m_returnTag);
return false;
}
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
bool Compiler::visit(VariableDefinition& _variableDefinition)
{
switch (_operator)
if (Expression* expression = _variableDefinition.getExpression())
{
case Token::SHL:
assert(false); //@todo
break;
case Token::SAR:
assert(false); //@todo
break;
default:
assert(false);
}
ExpressionCompiler::compileExpression(m_context, *expression);
ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(),
*_variableDefinition.getDeclaration().getType());
int stackPosition = m_context.getStackPositionOfVariable(_variableDefinition.getDeclaration());
m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP;
}
uint32_t ExpressionCompiler::appendConditionalJump()
{
uint32_t label = m_context.dispenseNewLabel();
append(eth::Instruction::PUSH1);
appendLabelref(label);
append(eth::Instruction::JUMPI);
return label;
return false;
}
void ExpressionCompiler::append(bytes const& _data)
bool Compiler::visit(ExpressionStatement& _expressionStatement)
{
m_assemblyItems.reserve(m_assemblyItems.size() + _data.size());
for (byte b: _data)
append(b);
Expression& expression = _expressionStatement.getExpression();
ExpressionCompiler::compileExpression(m_context, expression);
if (expression.getType()->getCategory() != Type::Category::VOID)
m_context << eth::Instruction::POP;
return false;
}
}
}

138
libsolidity/Compiler.h

@ -20,127 +20,47 @@
* Solidity AST to EVM bytecode compiler.
*/
#include <libevmface/Instruction.h>
#include <ostream>
#include <libsolidity/ASTVisitor.h>
#include <libsolidity/Types.h>
#include <libsolidity/Token.h>
#include <libsolidity/CompilerContext.h>
namespace dev {
namespace solidity {
/**
* A single item of compiled code that can be assembled to a single byte value in the final
* bytecode. Its main purpose is to inject jump labels and label references into the opcode stream,
* which can be resolved in the final step.
*/
class AssemblyItem
{
public:
enum class Type
{
CODE, ///< m_data is opcode, m_label is empty.
DATA, ///< m_data is actual data, m_label is empty
LABEL, ///< m_data is JUMPDEST opcode, m_label is id of label
LABELREF ///< m_data is empty, m_label is id of label
};
explicit AssemblyItem(eth::Instruction _instruction) : m_type(Type::CODE), m_data(byte(_instruction)) {}
explicit AssemblyItem(byte _data): m_type(Type::DATA), m_data(_data) {}
/// Factory functions
static AssemblyItem labelRef(uint32_t _label) { return AssemblyItem(Type::LABELREF, 0, _label); }
static AssemblyItem label(uint32_t _label) { return AssemblyItem(Type::LABEL, byte(eth::Instruction::JUMPDEST), _label); }
Type getType() const { return m_type; }
byte getData() const { return m_data; }
uint32_t getLabel() const { return m_label; }
private:
AssemblyItem(Type _type, byte _data, uint32_t _label): m_type(_type), m_data(_data), m_label(_label) {}
Type m_type;
byte m_data; ///< data to be written to the bytecode stream (or filled by a label if this is a LABELREF)
uint32_t m_label; ///< the id of a label either referenced or defined by this item
};
using AssemblyItems = std::vector<AssemblyItem>;
/**
* Context to be shared by all units that compile the same contract. Its current usage only
* concerns dispensing unique jump label IDs and storing their actual positions in the bytecode
* stream.
*/
class CompilerContext
class Compiler: private ASTVisitor
{
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(): m_returnTag(m_context.newTag()) {}
/**
* Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream
* of EVM instructions. It needs a compiler context that is the same for the whole compilation
* unit.
*/
class ExpressionCompiler: public ASTVisitor
{
public:
ExpressionCompiler(CompilerContext& _compilerContext): m_context(_compilerContext) {}
void compileContract(ContractDefinition& _contract);
bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); }
void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); }
/// 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);
/// Compile the given contract and return the EVM bytecode.
static bytes compile(ContractDefinition& _contract);
private:
virtual void endVisit(Assignment& _assignment) override;
virtual void endVisit(UnaryOperation& _unaryOperation) override;
virtual bool visit(BinaryOperation& _binaryOperation) override;
virtual void endVisit(FunctionCall& _functionCall) override;
virtual void endVisit(MemberAccess& _memberAccess) override;
virtual void endVisit(IndexAccess& _indexAccess) override;
virtual void endVisit(Identifier& _identifier) override;
virtual void endVisit(Literal& _literal) override;
/// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type.
void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType);
///@{
///@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();
/// Append elements to the current instruction list.
void append(eth::Instruction const& _instruction) { m_assemblyItems.push_back(AssemblyItem(_instruction)); }
void append(byte _value) { m_assemblyItems.push_back(AssemblyItem(_value)); }
void append(bytes const& _data);
void appendLabelref(byte _label) { m_assemblyItems.push_back(AssemblyItem::labelRef(_label)); }
void appendLabel(byte _label) { m_assemblyItems.push_back(AssemblyItem::label(_label)); }
AssemblyItems m_assemblyItems;
CompilerContext& m_context;
/// Creates a new compiler context / assembly and packs the current code into the data part.
void packIntoContractCreator();
void appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition> > const& _functions);
void appendCalldataUnpacker(FunctionDefinition const& _function);
void appendReturnValuePacker(FunctionDefinition const& _function);
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;
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
};
}
}

61
libsolidity/CompilerContext.cpp

@ -0,0 +1,61 @@
/*
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 <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(Declaration const& _declaration)
{
auto res = find(begin(m_localVariables), end(m_localVariables), &_declaration);
if (asserts(res != m_localVariables.end()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable not found on stack."));
return end(m_localVariables) - res - 1 + m_asm.deposit();
}
eth::AssemblyItem CompilerContext::getFunctionEntryLabel(FunctionDefinition const& _function) const
{
auto res = m_functionEntryLabels.find(&_function);
if (asserts(res != m_functionEntryLabels.end()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Function entry label not found."));
return res->second.tag();
}
}
}

89
libsolidity/CompilerContext.h

@ -0,0 +1,89 @@
/*
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 <ostream>
#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(); }
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
/// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset.
eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); }
/// 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; }
eth::Assembly const& getAssembly() const { return m_asm; }
void streamAssembly(std::ostream& _stream) const { _stream << m_asm; }
bytes getAssembledBytecode() const { 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;
};
}
}

49
libsolidity/CompilerStack.cpp

@ -0,0 +1,49 @@
/*
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
* Full-stack compiler that converts a source code string to bytecode.
*/
#include <libsolidity/AST.h>
#include <libsolidity/Scanner.h>
#include <libsolidity/Parser.h>
#include <libsolidity/NameAndTypeResolver.h>
#include <libsolidity/Compiler.h>
#include <libsolidity/CompilerStack.h>
using namespace std;
namespace dev
{
namespace solidity
{
bytes CompilerStack::compile(std::string const& _sourceCode, shared_ptr<Scanner> _scanner)
{
if (!_scanner)
_scanner = make_shared<Scanner>();
_scanner->reset(CharStream(_sourceCode));
ASTPointer<ContractDefinition> contract = Parser().parse(_scanner);
NameAndTypeResolver().resolveNamesAndTypes(*contract);
return Compiler::compile(*contract);
}
}
}

43
libsolidity/CompilerStack.h

@ -0,0 +1,43 @@
/*
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
* Full-stack compiler that converts a source code string to bytecode.
*/
#pragma once
#include <string>
#include <memory>
#include <libdevcore/Common.h>
namespace dev {
namespace solidity {
class Scanner; // forward
class CompilerStack
{
public:
/// Compile the given @a _sourceCode to bytecode. If a scanner is provided, it is used for
/// scanning the source code - this is useful for printing exception information.
static bytes compile(std::string const& _sourceCode, std::shared_ptr<Scanner> _scanner = std::shared_ptr<Scanner>());
};
}
}

2
libsolidity/Exceptions.h

@ -34,6 +34,8 @@ namespace solidity
struct ParserError: virtual Exception {};
struct TypeError: virtual Exception {};
struct DeclarationError: virtual Exception {};
struct CompilerError: virtual Exception {};
struct InternalCompilerError: virtual Exception {};
typedef boost::error_info<struct tag_sourcePosition, int> errinfo_sourcePosition;
typedef boost::error_info<struct tag_sourceLocation, Location> errinfo_sourceLocation;

410
libsolidity/ExpressionCompiler.cpp

@ -0,0 +1,410 @@
/*
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 <utility>
#include <numeric>
#include <libsolidity/AST.h>
#include <libsolidity/ExpressionCompiler.h>
#include <libsolidity/CompilerContext.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::ISZERO;
break;
case Token::BIT_NOT: // ~
m_context << eth::Instruction::NOT;
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:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " +
string(Token::toString(_unaryOperation.getOperator()))));
}
}
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
if (asserts(*leftExpression.getType() == *rightExpression.getType()))
BOOST_THROW_EXCEPTION(InternalCompilerError());
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
if (asserts(_functionCall.getArguments().size() == 1))
BOOST_THROW_EXCEPTION(InternalCompilerError());
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);
eth::AssemblyItem returnLabel = m_context.pushNewTag();
std::vector<ASTPointer<Expression>> const& arguments = _functionCall.getArguments();
if (asserts(arguments.size() == function.getParameters().size()))
BOOST_THROW_EXCEPTION(InternalCompilerError());
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:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer and boolean literals implemented for now."));
}
}
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.
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested."));
}
}
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation)
{
Token::Value const op = _binaryOperation.getOperator();
if (asserts(op == Token::OR || op == Token::AND))
BOOST_THROW_EXCEPTION(InternalCompilerError());
_binaryOperation.getLeftExpression().accept(*this);
m_context << eth::Instruction::DUP1;
if (op == Token::AND)
m_context << eth::Instruction::ISZERO;
eth::AssemblyItem endLabel = m_context.appendConditionalJump();
m_context << eth::Instruction::POP;
_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::ISZERO;
}
else
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_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::ISZERO;
break;
case Token::LTE:
m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT)
<< eth::Instruction::ISZERO;
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:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator."));
}
}
}
void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type)
{
if (Token::isArithmeticOp(_operator))
appendArithmeticOperatorCode(_operator, _type);
else if (Token::isBitOp(_operator))
appendBitOperatorCode(_operator);
else if (Token::isShiftOp(_operator))
appendShiftOperatorCode(_operator);
else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator."));
}
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_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 << eth::Instruction::SWAP1 << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV);
break;
case Token::MOD:
m_context << eth::Instruction::SWAP1 << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD);
break;
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator."));
}
}
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:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown bit operator."));
}
}
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
{
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Shift operators not yet implemented."));
switch (_operator)
{
case Token::SHL:
break;
case Token::SAR:
break;
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator."));
}
}
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
{
if (asserts(m_currentLValue))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not available on request."));
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>
namespace dev {
namespace solidity {
class CompilerContext; // forward
/// 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;
Declaration* m_currentLValue;
CompilerContext& m_context;
};
}
}

46
libsolidity/NameAndTypeResolver.cpp

@ -20,7 +20,6 @@
* Parser part that determines the declarations corresponding to names and the types of expressions.
*/
#include <cassert>
#include <libsolidity/NameAndTypeResolver.h>
#include <libsolidity/AST.h>
#include <libsolidity/Exceptions.h>
@ -55,12 +54,15 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
m_currentScope = &m_scopes[function.get()];
function->getBody().checkTypeRequirements();
}
m_currentScope = &m_scopes[nullptr];
}
void NameAndTypeResolver::reset()
Declaration* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const
{
m_scopes.clear();
m_currentScope = nullptr;
auto iterator = m_scopes.find(_scope);
if (iterator == end(m_scopes))
return nullptr;
return iterator->second.resolveName(_name, false);
}
Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive)
@ -68,8 +70,13 @@ Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name
return m_currentScope->resolveName(_name, _recursive);
}
void NameAndTypeResolver::reset()
{
m_scopes.clear();
m_currentScope = nullptr;
}
DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode*, Scope>& _scopes,
DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode const*, Scope>& _scopes,
ASTNode& _astRoot):
m_scopes(_scopes), m_currentScope(&m_scopes[nullptr])
{
@ -101,42 +108,52 @@ void DeclarationRegistrationHelper::endVisit(StructDefinition&)
bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
{
registerDeclaration(_function, true);
m_currentFunction = &_function;
return true;
}
void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
{
m_currentFunction = nullptr;
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration)
void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition)
{
registerDeclaration(_declaration, false);
return true;
// Register the local variables with the function
// This does not fit here perfectly, but it saves us another AST visit.
if (asserts(m_currentFunction))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable definition without function."));
m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration());
}
void DeclarationRegistrationHelper::endVisit(VariableDeclaration&)
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration)
{
registerDeclaration(_declaration, false);
return true;
}
void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node)
{
map<ASTNode*, Scope>::iterator iter;
map<ASTNode const*, Scope>::iterator iter;
bool newlyAdded;
tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope));
assert(newlyAdded);
if (asserts(newlyAdded))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to add new scope."));
m_currentScope = &iter->second;
}
void DeclarationRegistrationHelper::closeCurrentScope()
{
assert(m_currentScope);
if (asserts(m_currentScope))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Closed non-existing scope."));
m_currentScope = m_currentScope->getEnclosingScope();
}
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope)
{
assert(m_currentScope);
if (asserts(m_currentScope))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration registered without scope."));
if (!m_currentScope->registerDeclaration(_declaration))
BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_declaration.getLocation())
<< errinfo_comment("Identifier already declared."));
@ -163,7 +180,8 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable)
bool ReferencesResolver::visit(Return& _return)
{
assert(m_returnParameters);
if (asserts(m_returnParameters))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not set."));
_return.setFunctionReturnParameters(*m_returnParameters);
return true;
}

17
libsolidity/NameAndTypeResolver.h

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

19
libsolidity/Parser.cpp

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

1
libsolidity/Parser.h

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

38
libsolidity/Scanner.cpp

@ -50,7 +50,6 @@
* Solidity scanner.
*/
#include <cassert>
#include <algorithm>
#include <tuple>
#include <libsolidity/Scanner.h>
@ -103,11 +102,6 @@ int HexValue(char c)
}
} // end anonymous namespace
Scanner::Scanner(CharStream const& _source)
{
reset(_source);
}
void Scanner::reset(CharStream const& _source)
{
m_source = _source;
@ -118,11 +112,10 @@ void Scanner::reset(CharStream const& _source)
}
bool Scanner::scanHexNumber(char& o_scannedNumber, int _expectedLength)
bool Scanner::scanHexByte(char& o_scannedByte)
{
assert(_expectedLength <= 4); // prevent overflow
char x = 0;
for (int i = 0; i < _expectedLength; i++)
for (int i = 0; i < 2; i++)
{
int d = HexValue(m_char);
if (d < 0)
@ -133,7 +126,7 @@ bool Scanner::scanHexNumber(char& o_scannedNumber, int _expectedLength)
x = x * 16 + d;
advance();
}
o_scannedNumber = x;
o_scannedByte = x;
return true;
}
@ -180,7 +173,8 @@ Token::Value Scanner::skipSingleLineComment()
Token::Value Scanner::skipMultiLineComment()
{
assert(m_char == '*');
if (asserts(m_char == '*'))
BOOST_THROW_EXCEPTION(InternalCompilerError());
advance();
while (!isSourcePastEndOfInput())
{
@ -423,15 +417,11 @@ bool Scanner::scanEscape()
case 't':
c = '\t';
break;
case 'u':
if (!scanHexNumber(c, 4))
return false;
break;
case 'v':
c = '\v';
break;
case 'x':
if (!scanHexNumber(c, 2))
if (!scanHexByte(c))
return false;
break;
}
@ -473,7 +463,9 @@ void Scanner::scanDecimalDigits()
Token::Value Scanner::scanNumber(bool _periodSeen)
{
assert(IsDecimalDigit(m_char)); // the first digit of the number or the fraction
// the first digit of the number or the fraction
if (asserts(IsDecimalDigit(m_char)))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Number does not start with decimal digit."));
enum { DECIMAL, HEX, OCTAL, IMPLICIT_OCTAL, BINARY } kind = DECIMAL;
LiteralScope literal(this);
if (_periodSeen)
@ -515,7 +507,8 @@ Token::Value Scanner::scanNumber(bool _periodSeen)
// scan exponent, if any
if (m_char == 'e' || m_char == 'E')
{
assert(kind != HEX); // 'e'/'E' must be scanned as part of the hex number
if (asserts(kind != HEX)) // 'e'/'E' must be scanned as part of the hex number
BOOST_THROW_EXCEPTION(InternalCompilerError());
if (kind != DECIMAL) return Token::ILLEGAL;
// scan exponent
addLiteralCharAndAdvance();
@ -611,7 +604,8 @@ Token::Value Scanner::scanNumber(bool _periodSeen)
static Token::Value KeywordOrIdentifierToken(string const& input)
{
assert(!input.empty());
if (asserts(!input.empty()))
BOOST_THROW_EXCEPTION(InternalCompilerError());
int const kMinLength = 2;
int const kMaxLength = 10;
if (input.size() < kMinLength || input.size() > kMaxLength)
@ -639,7 +633,8 @@ case ch:
Token::Value Scanner::scanIdentifierOrKeyword()
{
assert(IsIdentifierStart(m_char));
if (asserts(IsIdentifierStart(m_char)))
BOOST_THROW_EXCEPTION(InternalCompilerError());
LiteralScope literal(this);
addLiteralCharAndAdvance();
// Scan the rest of the identifier characters.
@ -661,7 +656,8 @@ char CharStream::advanceAndGet()
char CharStream::rollback(size_t _amount)
{
assert(m_pos >= _amount);
if (asserts(m_pos >= _amount))
BOOST_THROW_EXCEPTION(InternalCompilerError());
m_pos -= _amount;
return get();
}

5
libsolidity/Scanner.h

@ -110,7 +110,8 @@ public:
bool complete_;
};
explicit Scanner(CharStream const& _source);
Scanner() { reset(CharStream()); }
explicit Scanner(CharStream const& _source) { reset(_source); }
/// Resets the scanner as if newly constructed with _input as input.
void reset(CharStream const& _source);
@ -168,7 +169,7 @@ private:
/// If the next character is _next, advance and return _then, otherwise return _else.
inline Token::Value selectToken(char _next, Token::Value _then, Token::Value _else);
bool scanHexNumber(char& o_scannedNumber, int _expectedLength);
bool scanHexByte(char& o_scannedByte);
/// Scans a single JavaScript token.
void scanToken();

64
libsolidity/Token.h

@ -42,9 +42,9 @@
#pragma once
#include <cassert>
#include <libdevcore/Common.h>
#include <libdevcore/Log.h>
#include <libsolidity/Exceptions.h>
namespace dev
{
@ -81,8 +81,6 @@ namespace solidity
T(SEMICOLON, ";", 0) \
T(PERIOD, ".", 0) \
T(CONDITIONAL, "?", 3) \
T(INC, "++", 0) \
T(DEC, "--", 0) \
T(ARROW, "=>", 0) \
\
/* Assignment operators. */ \
@ -136,6 +134,8 @@ namespace solidity
/* being contiguous and sorted in the same order! */ \
T(NOT, "!", 0) \
T(BIT_NOT, "~", 0) \
T(INC, "++", 0) \
T(DEC, "--", 0) \
K(DELETE, "delete", 0) \
\
/* Keywords */ \
@ -224,7 +224,8 @@ public:
// (e.g. "LT" for the token LT).
static char const* getName(Value tok)
{
assert(tok < NUM_TOKENS); // tok is unsigned
if (asserts(tok < NUM_TOKENS))
BOOST_THROW_EXCEPTION(InternalCompilerError());
return m_name[tok];
}
@ -249,55 +250,10 @@ public:
isEqualityOp(op) || isInequalityOp(op);
}
static Value negateCompareOp(Value op)
{
assert(isArithmeticCompareOp(op));
switch (op)
{
case EQ:
return NE;
case NE:
return EQ;
case LT:
return GTE;
case GT:
return LTE;
case LTE:
return GT;
case GTE:
return LT;
default:
assert(false); // should not get here
return op;
}
}
static Value reverseCompareOp(Value op)
{
assert(isArithmeticCompareOp(op));
switch (op)
{
case EQ:
return EQ;
case NE:
return NE;
case LT:
return GT;
case GT:
return LT;
case LTE:
return GTE;
case GTE:
return LTE;
default:
assert(false); // should not get here
return op;
}
}
static Value AssignmentToBinaryOp(Value op)
{
assert(isAssignmentOp(op) && op != ASSIGN);
if (asserts(isAssignmentOp(op) && op != ASSIGN))
BOOST_THROW_EXCEPTION(InternalCompilerError());
return Token::Value(op + (BIT_OR - ASSIGN_BIT_OR));
}
@ -311,7 +267,8 @@ public:
// have a (unique) string (e.g. an IDENTIFIER).
static char const* toString(Value tok)
{
assert(tok < NUM_TOKENS); // tok is unsigned.
if (asserts(tok < NUM_TOKENS))
BOOST_THROW_EXCEPTION(InternalCompilerError());
return m_string[tok];
}
@ -319,7 +276,8 @@ public:
// operators; returns 0 otherwise.
static int precedence(Value tok)
{
assert(tok < NUM_TOKENS); // tok is unsigned.
if (asserts(tok < NUM_TOKENS))
BOOST_THROW_EXCEPTION(InternalCompilerError());
return m_precedence[tok];
}

30
libsolidity/Types.cpp

@ -20,7 +20,6 @@
* Solidity data types
*/
#include <cassert>
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <libsolidity/Types.h>
@ -33,6 +32,9 @@ namespace solidity
std::shared_ptr<Type> Type::fromElementaryTypeName(Token::Value _typeToken)
{
if (asserts(Token::isElementaryTypeName(_typeToken)))
BOOST_THROW_EXCEPTION(InternalCompilerError());
if (Token::INT <= _typeToken && _typeToken <= Token::HASH256)
{
int offset = _typeToken - Token::INT;
@ -52,7 +54,8 @@ std::shared_ptr<Type> Type::fromElementaryTypeName(Token::Value _typeToken)
else if (_typeToken == Token::BOOL)
return std::make_shared<BoolType>();
else
assert(false); // @todo add other tyes
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " +
std::string(Token::toString(_typeToken)) + " to type."));
return std::shared_ptr<Type>();
}
@ -63,7 +66,7 @@ std::shared_ptr<Type> Type::fromUserDefinedTypeName(UserDefinedTypeName const& _
std::shared_ptr<Type> Type::fromMapping(Mapping const&)
{
assert(false); //@todo not yet implemented
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Mapping types not yet implemented."));
return std::shared_ptr<Type>();
}
@ -94,7 +97,8 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
{
if (isAddress())
_bits = 160;
assert(_bits > 0 && _bits <= 256 && _bits % 8 == 0);
if (asserts(_bits > 0 && _bits <= 256 && _bits % 8 == 0))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid bit number for integer type: " + dev::toString(_bits)));
}
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@ -159,14 +163,12 @@ std::string IntegerType::toString() const
return prefix + dev::toString(m_bits);
}
bytes IntegerType::literalToBigEndian(Literal const& _literal) const
u256 IntegerType::literalValue(Literal const& _literal) const
{
bigint value(_literal.getValue());
if (!isSigned() && value < 0)
return bytes(); // @todo this should already be caught by "smallestTypeforLiteral"
//@todo check that the number of bits is correct
//@todo does "toCompactBigEndian" work for signed numbers?
return toCompactBigEndian(value);
//@todo check that the number is not too large
//@todo does this work for signed numbers?
return u256(value);
}
bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
@ -182,14 +184,14 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
return isImplicitlyConvertibleTo(_convertTo);
}
bytes BoolType::literalToBigEndian(Literal const& _literal) const
u256 BoolType::literalValue(Literal const& _literal) const
{
if (_literal.getToken() == Token::TRUE_LITERAL)
return bytes(1, 1);
return u256(1);
else if (_literal.getToken() == Token::FALSE_LITERAL)
return bytes(1, 0);
return u256(0);
else
return NullBytes;
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Bool type constructed from non-boolean literal."));
}
bool ContractType::operator==(Type const& _other) const

19
libsolidity/Types.h

@ -26,6 +26,7 @@
#include <string>
#include <boost/noncopyable.hpp>
#include <libdevcore/Common.h>
#include <libsolidity/Exceptions.h>
#include <libsolidity/ASTForward.h>
#include <libsolidity/Token.h>
@ -70,8 +71,16 @@ public:
virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); }
virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); }
/// @returns number of bytes used by this type when encoded for CALL, or 0 if the encoding
/// is not a simple big-endian encoding or the type cannot be stored on the stack.
virtual unsigned getCalldataEncodedSize() const { return 0; }
virtual std::string toString() const = 0;
virtual bytes literalToBigEndian(Literal const&) const { return NullBytes; }
virtual u256 literalValue(Literal const&) const
{
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Literal value requested "
"for type without literals."));
}
};
/**
@ -97,8 +106,10 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual unsigned getCalldataEncodedSize() const { return m_bits / 8; }
virtual std::string toString() const override;
virtual bytes literalToBigEndian(Literal const& _literal) const override;
virtual u256 literalValue(Literal const& _literal) const override;
int getNumBits() const { return m_bits; }
bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; }
@ -127,8 +138,10 @@ public:
return _operator == Token::NOT || _operator == Token::DELETE;
}
virtual unsigned getCalldataEncodedSize() const { return 1; }
virtual std::string toString() const override { return "bool"; }
virtual bytes literalToBigEndian(Literal const& _literal) const override;
virtual u256 literalValue(Literal const& _literal) const override;
};
/**

124
solc/main.cpp

@ -34,65 +34,33 @@
#include <libsolidity/Compiler.h>
#include <libsolidity/SourceReferenceFormatter.h>
using namespace std;
using namespace dev;
using namespace solidity;
void help()
{
std::cout
<< "Usage solc [OPTIONS] <file>" << std::endl
<< "Options:" << std::endl
<< " -h,--help Show this help message and exit." << std::endl
<< " -V,--version Show the version and exit." << std::endl;
cout << "Usage solc [OPTIONS] <file>" << endl
<< "Options:" << endl
<< " -h,--help Show this help message and exit." << endl
<< " -V,--version Show the version and exit." << endl;
exit(0);
}
void version()
{
std::cout
<< "solc, the solidity complier commandline interface " << dev::Version << std::endl
<< " by Christian <c@ethdev.com>, (c) 2014." << std::endl
<< "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << std::endl;
cout << "solc, the solidity complier commandline interface " << dev::Version << endl
<< " by Christian <c@ethdev.com>, (c) 2014." << endl
<< "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl;
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)
{
std::string infile;
string infile;
for (int i = 1; i < argc; ++i)
{
std::string arg = argv[i];
string arg = argv[i];
if (arg == "-h" || arg == "--help")
help();
else if (arg == "-V" || arg == "--version")
@ -100,13 +68,13 @@ int main(int argc, char** argv)
else
infile = argv[i];
}
std::string sourceCode;
string sourceCode;
if (infile.empty())
{
std::string s;
while (!std::cin.eof())
string s;
while (!cin.eof())
{
getline(std::cin, s);
getline(cin, s);
sourceCode.append(s);
}
}
@ -114,47 +82,65 @@ int main(int argc, char** argv)
sourceCode = asString(dev::contents(infile));
ASTPointer<ContractDefinition> ast;
std::shared_ptr<Scanner> scanner = std::make_shared<Scanner>(CharStream(sourceCode));
shared_ptr<Scanner> scanner = make_shared<Scanner>(CharStream(sourceCode));
Parser parser;
bytes instructions;
Compiler compiler;
try
{
ast = parser.parse(scanner);
NameAndTypeResolver resolver;
resolver.resolveNamesAndTypes(*ast.get());
cout << "Syntax tree for the contract:" << endl;
dev::solidity::ASTPrinter printer(ast, sourceCode);
printer.print(cout);
compiler.compileContract(*ast);
instructions = compiler.getAssembledBytecode();
}
catch (ParserError const& exception)
{
SourceReferenceFormatter::printExceptionInformation(std::cerr, exception, "Parser error", *scanner);
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Parser error", *scanner);
return -1;
}
dev::solidity::NameAndTypeResolver resolver;
try
{
resolver.resolveNamesAndTypes(*ast.get());
}
catch (DeclarationError const& exception)
{
SourceReferenceFormatter::printExceptionInformation(std::cerr, exception, "Declaration error", *scanner);
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Declaration error", *scanner);
return -1;
}
catch (TypeError const& exception)
{
SourceReferenceFormatter::printExceptionInformation(std::cerr, exception, "Type error", *scanner);
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Type error", *scanner);
return -1;
}
catch (CompilerError const& exception)
{
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Compiler error", *scanner);
return -1;
}
catch (InternalCompilerError const& exception)
{
cerr << "Internal compiler error: " << boost::diagnostic_information(exception) << endl;
return -1;
}
catch (Exception const& exception)
{
cerr << "Exception during compilation: " << boost::diagnostic_information(exception) << endl;
return -1;
}
catch (...)
{
cerr << "Unknown exception during compilation." << endl;
return -1;
}
std::cout << "Syntax tree for the contract:" << std::endl;
dev::solidity::ASTPrinter printer(ast, sourceCode);
printer.print(std::cout);
FirstExpressionExtractor extractor(*ast);
CompilerContext context;
ExpressionCompiler compiler(context);
compiler.compile(*extractor.getExpression());
bytes instructions = compiler.getAssembledBytecode();
// debug
std::cout << "Bytecode for the first expression: " << std::endl;
std::cout << eth::disassemble(instructions) << std::endl;
cout << "EVM assembly:" << endl;
compiler.streamAssembly(cout);
cout << "Opcodes:" << endl;
cout << eth::disassemble(instructions) << endl;
cout << "Binary: " << toHex(instructions) << endl;
return 0;
}

307
test/solidityCompiler.cpp

@ -1,4 +1,3 @@
/*
This file is part of cpp-ethereum.
@ -18,18 +17,21 @@
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Unit tests for the name and type resolution of the solidity parser.
* Unit tests for the solidity compiler.
*/
#include <string>
#include <iostream>
#include <boost/test/unit_test.hpp>
#include <libdevcore/Log.h>
#include <libsolidity/Scanner.h>
#include <libsolidity/Parser.h>
#include <libsolidity/NameAndTypeResolver.h>
#include <libsolidity/Compiler.h>
#include <libsolidity/AST.h>
#include <boost/test/unit_test.hpp>
using namespace std;
using namespace dev::eth;
namespace dev
{
@ -41,186 +43,187 @@ namespace test
namespace
{
/**
* Helper class that extracts the first expression in an AST.
*/
class FirstExpressionExtractor: private ASTVisitor
{
public:
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); }
Expression* getExpression() const { return m_expression; }
private:
virtual bool visit(Expression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); }
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); }
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); }
bool checkExpression(Expression& _expression)
{
if (m_expression == nullptr)
m_expression = &_expression;
return false;
}
private:
Expression* m_expression;
};
bytes compileFirstExpression(std::string const& _sourceCode)
bytes compileContract(const string& _sourceCode)
{
Parser parser;
ASTPointer<ContractDefinition> contract;
BOOST_REQUIRE_NO_THROW(contract = parser.parse(std::make_shared<Scanner>(CharStream(_sourceCode))));
BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared<Scanner>(CharStream(_sourceCode))));
NameAndTypeResolver resolver;
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract));
FirstExpressionExtractor extractor(*contract);
BOOST_REQUIRE(extractor.getExpression() != nullptr);
CompilerContext context;
ExpressionCompiler compiler(context);
compiler.compile(*extractor.getExpression());
bytes instructions = compiler.getAssembledBytecode();
Compiler compiler;
compiler.compileContract(*contract);
// debug
//std::cout << eth::disassemble(instructions) << std::endl;
return instructions;
}
} // end anonymous namespace
BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler)
BOOST_AUTO_TEST_CASE(literal_true)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = true; }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x1});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
//compiler.streamAssembly(cout);
return compiler.getAssembledBytecode();
}
BOOST_AUTO_TEST_CASE(literal_false)
/// Checks that @a _compiledCode is present starting from offset @a _offset in @a _expectation.
/// This is necessary since the compiler will add boilerplate add the beginning that is not
/// tested here.
void checkCodePresentAt(bytes const& _compiledCode, bytes const& _expectation, unsigned _offset)
{
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_REQUIRE(_compiledCode.size() >= _offset + _expectation.size());
auto checkStart = _compiledCode.begin() + _offset;
BOOST_CHECK_EQUAL_COLLECTIONS(checkStart, checkStart + _expectation.size(),
_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);
} // end anonymous namespace
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_SUITE(SolidityCompiler)
BOOST_AUTO_TEST_CASE(comparison)
BOOST_AUTO_TEST_CASE(smoke_test)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = (0x10aa < 0x11aa) != true; }"
" function f() { var x = 2; }\n"
"}\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());
bytes code = compileContract(sourceCode);
unsigned boilerplateSize = 51;
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)});
checkCodePresentAt(code, expectation, boilerplateSize);
}
BOOST_AUTO_TEST_CASE(short_circuiting)
BOOST_AUTO_TEST_CASE(different_argument_numbers)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }"
" function f(uint a, uint b, uint c) returns(uint d) { return b; }\n"
" function g() returns (uint e, uint h) { h = f(1, 2, 3); }\n"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 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());
bytes code = compileContract(sourceCode);
unsigned shift = 75;
unsigned boilerplateSize = 88;
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 + shift, // 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 + shift, // ret address
byte(Instruction::PUSH1), 0x1,
byte(Instruction::PUSH1), 0x2,
byte(Instruction::PUSH1), 0x3,
byte(Instruction::PUSH1), 0x1 + shift,
// 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
});
checkCodePresentAt(code, expectation, boilerplateSize);
}
BOOST_AUTO_TEST_CASE(arithmetics)
BOOST_AUTO_TEST_CASE(ifStatement)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = (1 * (2 / (3 % (4 + (5 - (6 | (7 & (8 ^ 9)))))))); }"
" function f() { bool x; if (x) 77; else if (!x) 78; else 79; }"
"}\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::SWAP1),
byte(eth::Instruction::MOD),
byte(eth::Instruction::SWAP1),
byte(eth::Instruction::DIV),
byte(eth::Instruction::MUL)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
bytes code = compileContract(sourceCode);
unsigned shift = 38;
unsigned boilerplateSize = 51;
bytes expectation({byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x0,
byte(Instruction::DUP1),
byte(Instruction::PUSH1), 0x1b + shift, // "true" target
byte(Instruction::JUMPI),
// new check "else if" condition
byte(Instruction::DUP1),
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x13 + shift,
byte(Instruction::JUMPI),
// "else" body
byte(Instruction::PUSH1), 0x4f,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x17 + shift, // exit path of second part
byte(Instruction::JUMP),
// "else if" body
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x4e,
byte(Instruction::POP),
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x1f + shift,
byte(Instruction::JUMP),
// "if" body
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x4d,
byte(Instruction::POP),
byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
byte(Instruction::POP),
byte(Instruction::JUMP)});
checkCodePresentAt(code, expectation, boilerplateSize);
}
BOOST_AUTO_TEST_CASE(unary_operators)
BOOST_AUTO_TEST_CASE(loops)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = !(~+-(--(++1++)--) == 2); }"
" function f() { while(true){1;break;2;continue;3;return;4;} }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::ADD),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::SWAP1),
byte(eth::Instruction::SUB),
byte(eth::Instruction::PUSH1), 0x0,
byte(eth::Instruction::SUB),
byte(eth::Instruction::NOT),
byte(eth::Instruction::PUSH1), 0x2,
byte(eth::Instruction::EQ),
byte(eth::Instruction::ISZERO)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
bytes code = compileContract(sourceCode);
unsigned shift = 38;
unsigned boilerplateSize = 51;
bytes expectation({byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x1,
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x21 + shift,
byte(Instruction::JUMPI),
byte(Instruction::PUSH1), 0x1,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x21 + shift,
byte(Instruction::JUMP), // break
byte(Instruction::PUSH1), 0x2,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x2 + shift,
byte(Instruction::JUMP), // continue
byte(Instruction::PUSH1), 0x3,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x22 + shift,
byte(Instruction::JUMP), // return
byte(Instruction::PUSH1), 0x4,
byte(Instruction::POP),
byte(Instruction::PUSH1), 0x2 + shift,
byte(Instruction::JUMP),
byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
byte(Instruction::JUMP)});
checkCodePresentAt(code, expectation, boilerplateSize);
}
BOOST_AUTO_TEST_SUITE_END()

229
test/solidityEndToEndTest.cpp

@ -0,0 +1,229 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Unit tests for the solidity expression compiler, testing the behaviour of the code.
*/
#include <string>
#include <boost/test/unit_test.hpp>
#include <libethereum/State.h>
#include <libethereum/Executive.h>
#include <libsolidity/CompilerStack.h>
using namespace std;
namespace dev
{
namespace solidity
{
namespace test
{
class ExecutionFramework
{
public:
ExecutionFramework() { g_logVerbosity = 0; }
bytes compileAndRun(std::string const& _sourceCode)
{
bytes code = dev::solidity::CompilerStack::compile(_sourceCode);
sendMessage(code, true);
return m_output;
}
bytes callFunction(byte _index, bytes const& _data)
{
sendMessage(bytes(1, _index) + _data, false);
return m_output;
}
bytes callFunction(byte _index, u256 const& _argument1)
{
callFunction(_index, toBigEndian(_argument1));
return m_output;
}
private:
void sendMessage(bytes const& _data, bool _isCreation)
{
eth::Executive executive(m_state);
eth::Transaction t = _isCreation ? eth::Transaction(0, m_gasPrice, m_gas, _data)
: eth::Transaction(0, m_gasPrice, m_gas, m_contractAddress, _data);
bytes transactionRLP = t.rlp();
try
{
// this will throw since the transaction is invalid, but it should nevertheless store the transaction
executive.setup(&transactionRLP);
}
catch (...) {}
if (_isCreation)
{
BOOST_REQUIRE(!executive.create(Address(), 0, m_gasPrice, m_gas, &_data, Address()));
m_contractAddress = executive.newAddress();
BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress));
}
else
BOOST_REQUIRE(!executive.call(m_contractAddress, Address(), 0, m_gasPrice, &_data, m_gas, Address()));
BOOST_REQUIRE(executive.go());
executive.finalize();
m_output = executive.out().toBytes();
}
Address m_contractAddress;
eth::State m_state;
u256 const m_gasPrice = 100 * eth::szabo;
u256 const m_gas = 1000000;
bytes m_output;
};
BOOST_AUTO_TEST_SUITE(SolidityCompilerEndToEndTest)
BOOST_AUTO_TEST_CASE(smoke_test)
{
char const* sourceCode = "contract test {\n"
" function f(uint a) returns(uint d) { return a * 7; }\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
u256 a = 0x200030004;
bytes result = framework.callFunction(0, a);
BOOST_CHECK(result == toBigEndian(a * 7));
}
BOOST_AUTO_TEST_CASE(empty_contract)
{
char const* sourceCode = "contract test {\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
BOOST_CHECK(framework.callFunction(0, bytes()).empty());
}
BOOST_AUTO_TEST_CASE(recursive_calls)
{
char const* sourceCode = "contract test {\n"
" function f(uint n) returns(uint nfac) {\n"
" if (n <= 1) return 1;\n"
" else return n * f(n - 1);\n"
" }\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(1)));
BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(1)));
BOOST_CHECK(framework.callFunction(0, u256(2)) == toBigEndian(u256(2)));
BOOST_CHECK(framework.callFunction(0, u256(3)) == toBigEndian(u256(6)));
BOOST_CHECK(framework.callFunction(0, u256(4)) == toBigEndian(u256(24)));
}
BOOST_AUTO_TEST_CASE(while_loop)
{
char const* sourceCode = "contract test {\n"
" function f(uint n) returns(uint nfac) {\n"
" nfac = 1;\n"
" var i = 2;\n"
" while (i <= n) nfac *= i++;\n"
" }\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(1)));
BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(1)));
BOOST_CHECK(framework.callFunction(0, u256(2)) == toBigEndian(u256(2)));
BOOST_CHECK(framework.callFunction(0, u256(3)) == toBigEndian(u256(6)));
BOOST_CHECK(framework.callFunction(0, u256(4)) == toBigEndian(u256(24)));
}
BOOST_AUTO_TEST_CASE(calling_other_functions)
{
// note that the index of a function is its index in the sorted sequence of functions
char const* sourceCode = "contract collatz {\n"
" function run(uint x) returns(uint y) {\n"
" while ((y = x) > 1) {\n"
" if (x % 2 == 0) x = evenStep(x);\n"
" else x = oddStep(x);\n"
" }\n"
" }\n"
" function evenStep(uint x) returns(uint y) {\n"
" return x / 2;\n"
" }\n"
" function oddStep(uint x) returns(uint y) {\n"
" return 3 * x + 1;\n"
" }\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
BOOST_CHECK(framework.callFunction(2, u256(0)) == toBigEndian(u256(0)));
BOOST_CHECK(framework.callFunction(2, u256(1)) == toBigEndian(u256(1)));
BOOST_CHECK(framework.callFunction(2, u256(2)) == toBigEndian(u256(1)));
BOOST_CHECK(framework.callFunction(2, u256(8)) == toBigEndian(u256(1)));
BOOST_CHECK(framework.callFunction(2, u256(127)) == toBigEndian(u256(1)));
}
BOOST_AUTO_TEST_CASE(many_local_variables)
{
char const* sourceCode = "contract test {\n"
" function run(uint x1, uint x2, uint x3) returns(uint y) {\n"
" var a = 0x1; var b = 0x10; var c = 0x100;\n"
" y = a + b + c + x1 + x2 + x3;\n"
" y += b + x2;\n"
" }\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
BOOST_CHECK(framework.callFunction(0, toBigEndian(u256(0x1000)) + toBigEndian(u256(0x10000)) + toBigEndian(u256(0x100000)))
== toBigEndian(u256(0x121121)));
}
BOOST_AUTO_TEST_CASE(multiple_return_values)
{
char const* sourceCode = "contract test {\n"
" function run(bool x1, uint x2) returns(uint y1, bool y2, uint y3) {\n"
" y1 = x2; y2 = x1;\n"
" }\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
BOOST_CHECK(framework.callFunction(0, bytes(1, 1) + toBigEndian(u256(0xcd)))
== toBigEndian(u256(0xcd)) + bytes(1, 1) + toBigEndian(u256(0)));
}
BOOST_AUTO_TEST_CASE(short_circuiting)
{
char const* sourceCode = "contract test {\n"
" function run(uint x) returns(uint y) {\n"
" x == 0 || ((x = 8) > 0);\n"
" return x;"
" }\n"
"}\n";
ExecutionFramework framework;
framework.compileAndRun(sourceCode);
BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(0)));
BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(8)));
}
//@todo test smaller types
BOOST_AUTO_TEST_SUITE_END()
}
}
} // end namespaces

352
test/solidityExpressionCompiler.cpp

@ -0,0 +1,352 @@
/*
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/CompilerContext.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::ISZERO)});
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::ISZERO), // 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::POP),
byte(eth::Instruction::PUSH1), 0x2,
byte(eth::Instruction::PUSH1), 0x9,
byte(eth::Instruction::EQ),
byte(eth::Instruction::ISZERO), // after this we have 2 != 9
byte(eth::Instruction::JUMPDEST),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::EQ),
byte(eth::Instruction::ISZERO)});
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::SWAP1),
byte(eth::Instruction::MOD),
byte(eth::Instruction::SWAP1),
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::NOT),
byte(eth::Instruction::PUSH1), 0x2,
byte(eth::Instruction::EQ),
byte(eth::Instruction::ISZERO)});
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), 0x0a,
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), 0x14,
byte(eth::Instruction::JUMP),
byte(eth::Instruction::JUMPDEST),
// Stack here: a b g(a+1, b)
byte(eth::Instruction::PUSH1), 0x02,
byte(eth::Instruction::MUL),
// Stack here: a b g(a+1, b)*2
byte(eth::Instruction::DUP3),
byte(eth::Instruction::SWAP1),
byte(eth::Instruction::ADD),
// Stack here: a b a+g(a+1, b)*2
byte(eth::Instruction::SWAP2),
byte(eth::Instruction::POP),
byte(eth::Instruction::DUP2),
byte(eth::Instruction::JUMPDEST)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_SUITE_END()
}
}
} // end namespaces
Loading…
Cancel
Save