aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AST.cpp83
-rw-r--r--AST.h104
-rw-r--r--ASTForward.h1
-rw-r--r--ASTPrinter.cpp12
-rw-r--r--ASTPrinter.h2
-rw-r--r--ASTVisitor.h2
-rw-r--r--CMakeLists.txt4
-rw-r--r--Compiler.cpp516
-rw-r--r--Compiler.h138
-rw-r--r--CompilerContext.cpp61
-rw-r--r--CompilerContext.h89
-rw-r--r--CompilerStack.cpp49
-rw-r--r--CompilerStack.h43
-rw-r--r--Exceptions.h2
-rw-r--r--ExpressionCompiler.cpp410
-rw-r--r--ExpressionCompiler.h79
-rw-r--r--NameAndTypeResolver.cpp46
-rw-r--r--NameAndTypeResolver.h17
-rw-r--r--Parser.cpp19
-rw-r--r--Parser.h1
-rw-r--r--Scanner.cpp38
-rw-r--r--Scanner.h5
-rw-r--r--Token.h64
-rw-r--r--Types.cpp30
-rw-r--r--Types.h19
25 files changed, 1246 insertions, 588 deletions
diff --git a/AST.cpp b/AST.cpp
index 5bba8dce..026aef97 100644
--- a/AST.cpp
+++ b/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()
diff --git a/AST.h b/AST.h
index 4dc44e29..f42ff47d 100644
--- a/AST.h
+++ b/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;
diff --git a/ASTForward.h b/ASTForward.h
index c9a780f5..2b0bd886 100644
--- a/ASTForward.h
+++ b/ASTForward.h
@@ -53,6 +53,7 @@ class Continue;
class Break;
class Return;
class VariableDefinition;
+class ExpressionStatement;
class Expression;
class Assignment;
class UnaryOperation;
diff --git a/ASTPrinter.cpp b/ASTPrinter.cpp
index 9b545ac9..eb9d92f0 100644
--- a/ASTPrinter.cpp
+++ b/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--;
diff --git a/ASTPrinter.h b/ASTPrinter.h
index d788ba76..e87b2ba3 100644
--- a/ASTPrinter.h
+++ b/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;
diff --git a/ASTVisitor.h b/ASTVisitor.h
index e4818ee2..6e579f35 100644
--- a/ASTVisitor.h
+++ b/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&) { }
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 757d0cc0..f425bba4 100644
--- a/CMakeLists.txt
+++ b/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} )
diff --git a/Compiler.cpp b/Compiler.cpp
index ba1dcfb6..654ecead 100644
--- a/Compiler.cpp
+++ b/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())));
+
+ //@todo remove constructor
+
+ if (publicFunctions.size() > 255)
+ BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("More than 255 public functions for contract."));
+
+ //@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();
+
+ // 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;
+
+ // 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;
+
+ m_context << returnTag << eth::Instruction::STOP;
- // resolve label references
- for (uint32_t pos = 0; pos < m_assemblyItems.size(); ++pos)
+ for (pair<string, pair<FunctionDefinition const*, eth::AssemblyItem>> const& f: publicFunctions)
{
- AssemblyItem const& item = m_assemblyItems[pos];
- if (item.getType() == AssemblyItem::Type::LABEL)
- m_context.setLabelPosition(item.getLabel(), pos + 1);
- }
+ FunctionDefinition const& function = *f.second.first;
+ m_context << f.second.second;
- 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());
+ eth::AssemblyItem returnTag = m_context.pushNewTag();
+ appendCalldataUnpacker(function);
+ m_context.appendJumpTo(m_context.getFunctionEntryLabel(function));
+ m_context << returnTag;
- return assembled;
+ appendReturnValuePacker(function);
+ }
}
-AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context,
- Expression& _expression)
+void Compiler::appendCalldataUnpacker(FunctionDefinition const& _function)
{
- ExpressionCompiler compiler(_context);
- compiler.compile(_expression);
- return compiler.getAssemblyItems();
-}
+ // We do not check the calldata size, everything is zero-padded.
+ unsigned dataOffset = 1;
-void ExpressionCompiler::endVisit(Assignment& _assignment)
-{
- Expression& rightHandSide = _assignment.getRightHandSide();
- Token::Value op = _assignment.getAssignmentOperator();
- if (op != Token::ASSIGN)
+ //@todo this can be done more efficiently, saving some CALLDATALOAD calls
+ for (ASTPointer<VariableDeclaration> const& var: _function.getParameters())
{
- // compound assignment
- // @todo retrieve lvalue value
- rightHandSide.accept(*this);
- Type const& resultType = *_assignment.getType();
- cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType);
- appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType);
+ 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
+ m_context << (u256(1) << ((32 - numBytes) * 8)) << u256(dataOffset)
+ << eth::Instruction::CALLDATALOAD << eth::Instruction::DIV;
+ dataOffset += numBytes;
}
- else
- rightHandSide.accept(*this);
- // @todo store value
}
-void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation)
+void Compiler::appendReturnValuePacker(FunctionDefinition const& _function)
{
- //@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())
+ //@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)
{
- 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())
- {
- 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
+ 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;
}
-bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation)
+bool Compiler::visit(FunctionDefinition& _function)
{
- Expression& leftExpression = _binaryOperation.getLeftExpression();
- Expression& rightExpression = _binaryOperation.getRightExpression();
- Type const& resultType = *_binaryOperation.getType();
- Token::Value const op = _binaryOperation.getOperator();
+ //@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
- if (op == Token::AND || op == Token::OR)
- {
- // special case: short-circuiting
- appendAndOrOperatorCode(_binaryOperation);
- }
- else if (Token::isCompareOp(op))
- {
- leftExpression.accept(*this);
- rightExpression.accept(*this);
+ m_context.startNewFunction();
+ m_returnTag = m_context.newTag();
+ m_breakTags.clear();
+ m_continueTags.clear();
- // the types to compare have to be the same, but the resulting type is always bool
- assert(*leftExpression.getType() == *rightExpression.getType());
- appendCompareOperatorCode(op, *leftExpression.getType());
- }
- else
- {
- leftExpression.accept(*this);
- cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType);
- rightExpression.accept(*this);
- cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType);
- appendOrdinaryBinaryOperatorCode(op, resultType);
- }
+ m_context << m_context.getFunctionEntryLabel(_function);
- // do not visit the child nodes, we already did that explicitly
- return false;
-}
+ // stack upon entry: [return address] [arg0] [arg1] ... [argn]
+ // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
-void ExpressionCompiler::endVisit(FunctionCall& _functionCall)
-{
- if (_functionCall.isTypeConversion())
- {
- //@todo binary representation for all supported types (bool and int) is the same, so no-op
- // here for now.
- }
- else
- {
- //@todo
- }
-}
+ unsigned const numArguments = _function.getParameters().size();
+ unsigned const numReturnValues = _function.getReturnParameters().size();
+ unsigned const numLocalVariables = _function.getLocalVariables().size();
-void ExpressionCompiler::endVisit(MemberAccess&)
-{
+ 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);
-}
+ _function.getBody().accept(*this);
-void ExpressionCompiler::endVisit(IndexAccess&)
-{
+ 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.
-void ExpressionCompiler::endVisit(Identifier&)
-{
+ 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)
+ {
+ m_context << eth::Instruction::POP;
+ stackLayout.pop_back();
+ }
+ else
+ {
+ 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::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
- }
-}
+ m_context << eth::Instruction::JUMP;
-void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType)
-{
- // If the type of one of the operands is extended, we need to remove all
- // higher-order bits that we might have ignored in previous operations.
- // @todo: store in the AST whether the operand might have "dirty" higher
- // order bits
-
- if (_typeOnStack == _targetType)
- return;
- if (_typeOnStack.getCategory() == Type::Category::INTEGER &&
- _targetType.getCategory() == Type::Category::INTEGER)
- {
- //@todo
- }
- else
- {
- // If we get here, there is either an implementation missing to clean higher oder bits
- // for non-integer types that are explicitly convertible or we got here in error.
- assert(!_typeOnStack.isExplicitlyConvertibleTo(_targetType));
- assert(false); // these types should not be convertible.
- }
+ return false;
}
-void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation)
+bool Compiler::visit(IfStatement& _ifStatement)
{
- 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);
+ 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::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
+bool Compiler::visit(WhileStatement& _whileStatement)
{
- if (_operator == Token::EQ || _operator == Token::NE)
- {
- append(eth::Instruction::EQ);
- if (_operator == Token::NE)
- append(eth::Instruction::NOT);
- }
- else
- {
- IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
- assert(type);
- bool const isSigned = type->isSigned();
+ eth::AssemblyItem loopStart = m_context.newTag();
+ eth::AssemblyItem loopEnd = m_context.newTag();
+ m_continueTags.push_back(loopStart);
+ m_breakTags.push_back(loopEnd);
- // note that EVM opcodes compare like "stack[0] < stack[1]",
- // but our left value is at stack[1], so everyhing is reversed.
- switch (_operator)
- {
- case Token::GTE:
- append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT);
- append(eth::Instruction::NOT);
- break;
- case Token::LTE:
- append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT);
- append(eth::Instruction::NOT);
- break;
- case Token::GT:
- append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT);
- break;
- case Token::LT:
- append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT);
- break;
- default:
- assert(false);
- }
- }
+ m_context << 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::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type)
+bool Compiler::visit(Continue&)
{
- 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
+ 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;
}
-void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
+bool Compiler::visit(Break&)
{
- IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
- assert(type);
- bool const isSigned = type->isSigned();
-
- switch (_operator)
- {
- case Token::ADD:
- append(eth::Instruction::ADD);
- break;
- case Token::SUB:
- append(eth::Instruction::SWAP1);
- append(eth::Instruction::SUB);
- break;
- case Token::MUL:
- append(eth::Instruction::MUL);
- break;
- case Token::DIV:
- append(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;
}
+ return false;
}
-uint32_t ExpressionCompiler::appendConditionalJump()
-{
- uint32_t label = m_context.dispenseNewLabel();
- append(eth::Instruction::PUSH1);
- appendLabelref(label);
- append(eth::Instruction::JUMPI);
- return label;
-}
-
-void ExpressionCompiler::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;
}
-
-
}
}
diff --git a/Compiler.h b/Compiler.h
index 5817f12f..4e4d90d4 100644
--- a/Compiler.h
+++ b/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;
+ Compiler(): m_returnTag(m_context.newTag()) {}
-private:
- uint32_t m_nextLabel;
-
- std::map<uint32_t, uint32_t> m_labelPositions;
-};
-
-/**
- * Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream
- * of EVM instructions. It needs a compiler context that is the same for the whole compilation
- * unit.
- */
-class ExpressionCompiler: public ASTVisitor
-{
-public:
- ExpressionCompiler(CompilerContext& _compilerContext): m_context(_compilerContext) {}
+ 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
};
-
}
}
diff --git a/CompilerContext.cpp b/CompilerContext.cpp
new file mode 100644
index 00000000..99cf090e
--- /dev/null
+++ b/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();
+}
+
+}
+}
diff --git a/CompilerContext.h b/CompilerContext.h
new file mode 100644
index 00000000..46c4c72a
--- /dev/null
+++ b/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;
+};
+
+}
+}
diff --git a/CompilerStack.cpp b/CompilerStack.cpp
new file mode 100644
index 00000000..bbd693ae
--- /dev/null
+++ b/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);
+}
+
+}
+}
diff --git a/CompilerStack.h b/CompilerStack.h
new file mode 100644
index 00000000..9f3f81c0
--- /dev/null
+++ b/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>());
+};
+
+}
+}
diff --git a/Exceptions.h b/Exceptions.h
index 5a48c47d..1903c1dc 100644
--- a/Exceptions.h
+++ b/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;
diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp
new file mode 100644
index 00000000..d23579b1
--- /dev/null
+++ b/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);
+}
+
+}
+}
diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h
new file mode 100644
index 00000000..a930723c
--- /dev/null
+++ b/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;
+};
+
+
+}
+}
diff --git a/NameAndTypeResolver.cpp b/NameAndTypeResolver.cpp
index 9626ca84..0578e599 100644
--- a/NameAndTypeResolver.cpp
+++ b/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;
}
diff --git a/NameAndTypeResolver.h b/NameAndTypeResolver.h
index bb7fcb98..90902494 100644
--- a/NameAndTypeResolver.h
+++ b/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;
};
/**
diff --git a/Parser.cpp b/Parser.cpp
index 72770f67..276da072 100644
--- a/Parser.cpp
+++ b/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:
diff --git a/Parser.h b/Parser.h
index eabc2274..307a0d6a 100644
--- a/Parser.h
+++ b/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();
diff --git a/Scanner.cpp b/Scanner.cpp
index 3148de52..c3682031 100644
--- a/Scanner.cpp
+++ b/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();
}
diff --git a/Scanner.h b/Scanner.h
index c08d3219..537c2434 100644
--- a/Scanner.h
+++ b/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();
diff --git a/Token.h b/Token.h
index c54f387c..0fb9b670 100644
--- a/Token.h
+++ b/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];
}
diff --git a/Types.cpp b/Types.cpp
index e6711b3c..a4d70e3a 100644
--- a/Types.cpp
+++ b/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
diff --git a/Types.h b/Types.h
index c9f6da57..4493b803 100644
--- a/Types.h
+++ b/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;
};
/**