Browse Source

Expression compiler.

cl-refactor
Christian 10 years ago
parent
commit
d4d5f23e8f
  1. 23
      libsolidity/AST.cpp
  2. 13
      libsolidity/AST.h
  3. 408
      libsolidity/Compiler.cpp
  4. 140
      libsolidity/Compiler.h
  5. 1
      libsolidity/Token.h
  6. 85
      libsolidity/Types.cpp
  7. 31
      libsolidity/Types.h
  8. 41
      solc/main.cpp
  9. 229
      test/solidityCompiler.cpp

23
libsolidity/AST.cpp

@ -328,7 +328,7 @@ void Assignment::checkTypeRequirements()
m_type = m_leftHandSide->getType();
if (m_assigmentOperator != Token::ASSIGN)
{
// complex assignment
// compound assignment
if (!m_type->acceptsBinaryOperator(Token::AssignmentToBinaryOp(m_assigmentOperator)))
BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type."));
}
@ -339,7 +339,7 @@ void UnaryOperation::checkTypeRequirements()
// INC, DEC, NOT, BIT_NOT, DELETE
m_subExpression->checkTypeRequirements();
m_type = m_subExpression->getType();
if (m_type->acceptsUnaryOperator(m_operator))
if (!m_type->acceptsUnaryOperator(m_operator))
BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type."));
}
@ -369,11 +369,11 @@ void FunctionCall::checkTypeRequirements()
m_expression->checkTypeRequirements();
for (ASTPointer<Expression> const& argument: m_arguments)
argument->checkTypeRequirements();
Type const& expressionType = *m_expression->getType();
Type::Category const category = expressionType.getCategory();
if (category == Type::Category::TYPE)
Type const* expressionType = m_expression->getType().get();
if (isTypeConversion())
{
TypeType const* type = dynamic_cast<TypeType const*>(&expressionType);
TypeType const* type = dynamic_cast<TypeType const*>(expressionType);
BOOST_ASSERT(type);
//@todo for structs, we have to check the number of arguments to be equal to the
// number of non-mapping members
@ -384,12 +384,12 @@ void FunctionCall::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed."));
m_type = type->getActualType();
}
else if (category == Type::Category::FUNCTION)
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);
FunctionType const* function = dynamic_cast<FunctionType const*>(expressionType);
BOOST_ASSERT(function);
FunctionDefinition const& fun = function->getFunction();
std::vector<ASTPointer<VariableDeclaration>> const& parameters = fun.getParameters();
@ -405,8 +405,11 @@ void FunctionCall::checkTypeRequirements()
else
m_type = fun.getReturnParameterList()->getParameters().front()->getType();
}
else
BOOST_THROW_EXCEPTION(createTypeError("Type does not support invocation."));
}
bool FunctionCall::isTypeConversion() const
{
return m_expression->getType()->getCategory() == Type::Category::TYPE;
}
void MemberAccess::checkTypeRequirements()

13
libsolidity/AST.h

@ -61,6 +61,12 @@ public:
/// the given description
TypeError createTypeError(std::string const& _description);
///@{
/// Equality relies on the fact that nodes cannot be copied.
bool operator==(ASTNode const& _other) const { return this == &_other; }
bool operator!=(ASTNode const& _other) const { return !operator==(_other); }
///@}
private:
Location m_location;
};
@ -386,7 +392,9 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getLeftHandSide() const { return *m_leftHandSide; }
Token::Value getAssignmentOperator() const { return m_assigmentOperator; }
Expression& getRightHandSide() const { return *m_rightHandSide; }
private:
ASTPointer<Expression> m_leftHandSide;
@ -422,6 +430,8 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
Expression& getLeftExpression() const { return *m_left; }
Expression& getRightExpression() const { return *m_right; }
Token::Value getOperator() const { return m_operator; }
private:
@ -441,6 +451,9 @@ public:
Expression(_location), m_expression(_expression), m_arguments(_arguments) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void checkTypeRequirements() override;
/// Returns true if this is not an actual function call, but an explicit type conversion
/// or constructor call.
bool isTypeConversion() const;
private:
ASTPointer<Expression> m_expression;

408
libsolidity/Compiler.cpp

@ -0,0 +1,408 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Solidity AST to EVM bytecode compiler.
*/
#include <boost/assert.hpp>
#include <utility>
#include <libsolidity/AST.h>
#include <libsolidity/Compiler.h>
namespace dev {
namespace solidity {
void CompilerContext::setLabelPosition(uint32_t _label, uint32_t _position)
{
BOOST_ASSERT(m_labelPositions.find(_label) == m_labelPositions.end());
m_labelPositions[_label] = _position;
}
uint32_t CompilerContext::getLabelPosition(uint32_t _label) const
{
auto iter = m_labelPositions.find(_label);
BOOST_ASSERT(iter != m_labelPositions.end());
return iter->second;
}
void ExpressionCompiler::compile(Expression& _expression)
{
m_assemblyItems.clear();
_expression.accept(*this);
}
bytes ExpressionCompiler::getAssembledBytecode() const
{
bytes assembled;
assembled.reserve(m_assemblyItems.size());
// resolve label references
for (uint32_t pos = 0; pos < m_assemblyItems.size(); ++pos)
{
AssemblyItem const& item = m_assemblyItems[pos];
if (item.getType() == AssemblyItem::Type::LABEL)
m_context.setLabelPosition(item.getLabel(), pos + 1);
}
for (AssemblyItem const& item: m_assemblyItems)
{
if (item.getType() == AssemblyItem::Type::LABELREF)
assembled.push_back(m_context.getLabelPosition(item.getLabel()));
else
assembled.push_back(item.getData());
}
return assembled;
}
AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context,
Expression& _expression)
{
ExpressionCompiler compiler(_context);
compiler.compile(_expression);
return compiler.getAssemblyItems();
}
void ExpressionCompiler::endVisit(Assignment& _assignment)
{
Expression& rightHandSide = _assignment.getRightHandSide();
Token::Value op = _assignment.getAssignmentOperator();
if (op != Token::ASSIGN)
{
// compound assignment
// @todo retrieve lvalue value
rightHandSide.accept(*this);
Type const& resultType = *_assignment.getType();
cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType);
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType);
}
else
rightHandSide.accept(*this);
// @todo store value
}
void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation)
{
//@todo type checking and creating code for an operator should be in the same place:
// the operator should know how to convert itself and to which types it applies, so
// put this code together with "Type::acceptsBinary/UnaryOperator" into a class that
// represents the operator
switch (_unaryOperation.getOperator())
{
case Token::NOT: // !
append(eth::Instruction::NOT);
break;
case Token::BIT_NOT: // ~
// ~a modeled as "a xor (0 - 1)" for now
append(eth::Instruction::PUSH1);
append(1);
append(eth::Instruction::PUSH1);
append(0);
append(eth::Instruction::SUB);
append(eth::Instruction::XOR);
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: // -
append(eth::Instruction::NEG);
break;
default:
BOOST_ASSERT(false); // invalid operation
}
}
bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation)
{
Expression& leftExpression = _binaryOperation.getLeftExpression();
Expression& rightExpression = _binaryOperation.getRightExpression();
Type const& resultType = *_binaryOperation.getType();
Token::Value const op = _binaryOperation.getOperator();
if (op == Token::AND || op == Token::OR)
{
// special case: short-circuiting
appendAndOrOperatorCode(_binaryOperation);
}
else if (Token::isCompareOp(op))
{
leftExpression.accept(*this);
rightExpression.accept(*this);
// the types to compare have to be the same, but the resulting type is always bool
BOOST_ASSERT(*leftExpression.getType() == *rightExpression.getType());
appendCompareOperatorCode(op, *leftExpression.getType());
}
else
{
leftExpression.accept(*this);
cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType);
rightExpression.accept(*this);
cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType);
appendOrdinaryBinaryOperatorCode(op, resultType);
}
// do not visit the child nodes, we already did that explicitly
return false;
}
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
}
}
void ExpressionCompiler::endVisit(MemberAccess& _memberAccess)
{
}
void ExpressionCompiler::endVisit(IndexAccess& _indexAccess)
{
}
void ExpressionCompiler::endVisit(Identifier& _identifier)
{
}
void ExpressionCompiler::endVisit(Literal& _literal)
{
switch (_literal.getType()->getCategory())
{
case Type::Category::INTEGER:
case Type::Category::BOOL:
{
bytes value = _literal.getType()->literalToBigEndian(_literal);
BOOST_ASSERT(value.size() <= 32);
BOOST_ASSERT(!value.empty());
append(static_cast<byte>(eth::Instruction::PUSH1) + static_cast<byte>(value.size() - 1));
append(value);
break;
}
default:
BOOST_ASSERT(false); // @todo
}
}
void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(const Type& _typeOnStack, const Type& _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_ASSERT(!_typeOnStack.isExplicitlyConvertibleTo(_targetType));
BOOST_ASSERT(false); // these types should not be convertible.
}
}
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation)
{
Token::Value const op = _binaryOperation.getOperator();
BOOST_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);
}
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
{
if (_operator == Token::EQ || _operator == Token::NE)
{
append(eth::Instruction::EQ);
if (_operator == Token::NE)
append(eth::Instruction::NOT);
}
else
{
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
BOOST_ASSERT(type != nullptr);
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:
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:
BOOST_ASSERT(false);
}
}
}
void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type)
{
if (Token::isArithmeticOp(_operator))
appendArithmeticOperatorCode(_operator, _type);
else if (Token::isBitOp(_operator))
appendBitOperatorCode(_operator);
else if (Token::isShiftOp(_operator))
appendShiftOperatorCode(_operator);
else
BOOST_ASSERT(false); // unknown binary operator
}
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
{
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
BOOST_ASSERT(type != nullptr);
bool const isSigned = type->isSigned();
switch (_operator)
{
case Token::ADD:
append(eth::Instruction::ADD);
break;
case Token::SUB:
append(eth::Instruction::SWAP1);
append(eth::Instruction::SUB);
break;
case Token::MUL:
append(eth::Instruction::MUL);
break;
case Token::DIV:
append(isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV);
break;
case Token::MOD:
append(isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD);
break;
default:
BOOST_ASSERT(false);
}
}
void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
{
switch (_operator)
{
case Token::BIT_OR:
append(eth::Instruction::OR);
break;
case Token::BIT_AND:
append(eth::Instruction::AND);
break;
case Token::BIT_XOR:
append(eth::Instruction::XOR);
break;
default:
BOOST_ASSERT(false);
}
}
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
{
switch (_operator)
{
case Token::SHL:
BOOST_ASSERT(false); //@todo
break;
case Token::SAR:
BOOST_ASSERT(false); //@todo
break;
default:
BOOST_ASSERT(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)
{
m_assemblyItems.reserve(m_assemblyItems.size() + _data.size());
for (byte b: _data)
append(b);
}
}
}

140
libsolidity/Compiler.h

@ -0,0 +1,140 @@
/*
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.
*/
#include <libevmface/Instruction.h>
#include <libsolidity/ASTVisitor.h>
#include <libsolidity/Types.h>
#include <libsolidity/Token.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
{
public:
CompilerContext(): m_nextLabel(0) {}
uint32_t dispenseNewLabel() { return m_nextLabel++; }
void setLabelPosition(uint32_t _label, uint32_t _position);
uint32_t getLabelPosition(uint32_t _label) const;
private:
uint32_t m_nextLabel;
std::map<uint32_t, uint32_t> m_labelPositions;
};
/// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream
/// of EVM instructions. It needs a compiler context that is the same for the whole compilation
/// unit.
class ExpressionCompiler: public ASTVisitor
{
public:
ExpressionCompiler(CompilerContext& _compilerContext): m_context(_compilerContext) {}
/// Compile the given expression and (re-)populate the assembly item list.
void compile(Expression& _expression);
AssemblyItems const& getAssemblyItems() const { return m_assemblyItems; }
bytes getAssembledBytecode() const;
/// Compile the given expression and return the assembly items right away.
static AssemblyItems compileExpression(CompilerContext& _context, Expression& _expression);
private:
virtual 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);
/// 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;
};
}
}

1
libsolidity/Token.h

@ -236,6 +236,7 @@ public:
static bool isAssignmentOp(Value tok) { return ASSIGN <= tok && tok <= ASSIGN_MOD; }
static bool isBinaryOp(Value op) { return COMMA <= op && op <= MOD; }
static bool isTruncatingBinaryOp(Value op) { return BIT_OR <= op && op <= SHR; }
static bool isArithmeticOp(Value op) { return ADD <= op && op <= MOD; }
static bool isCompareOp(Value op) { return EQ <= op && op <= IN; }
static bool isOrderedRelationalCompareOp(Value op)
{

85
libsolidity/Types.cpp

@ -21,6 +21,7 @@
*/
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <libsolidity/Types.h>
#include <libsolidity/AST.h>
@ -96,7 +97,7 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
if (_convertTo.getCategory() != Category::INTEGER)
if (_convertTo.getCategory() != getCategory())
return false;
IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
if (convertTo.m_bits < m_bits)
@ -113,7 +114,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
return _convertTo.getCategory() == Category::INTEGER;
return _convertTo.getCategory() == getCategory();
}
bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const
@ -128,7 +129,24 @@ bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const
bool IntegerType::acceptsUnaryOperator(Token::Value _operator) const
{
return _operator == Token::DELETE || (!isAddress() && _operator == Token::BIT_NOT);
if (_operator == Token::DELETE)
return true;
if (isAddress())
return false;
if (_operator == Token::BIT_NOT)
return true;
if (isHash())
return false;
return _operator == Token::ADD || _operator == Token::SUB ||
_operator == Token::INC || _operator == Token::DEC;
}
bool IntegerType::operator==(const Type& _other) const
{
if (_other.getCategory() != getCategory())
return false;
IntegerType const& other = dynamic_cast<IntegerType const&>(_other);
return other.m_bits == m_bits && other.m_modifier == m_modifier;
}
std::string IntegerType::toString() const
@ -139,11 +157,21 @@ std::string IntegerType::toString() const
return prefix + dev::toString(m_bits);
}
bytes IntegerType::literalToBigEndian(const Literal& _literal) const
{
bigint value(_literal.getValue());
if (!isSigned() && value < 0)
return bytes(); // @todo this should already be caught by "smallestTypeforLiteral"
//@todo check that the number of bits is correct
//@todo does "toCompactBigEndian" work for signed numbers?
return toCompactBigEndian(value);
}
bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
// conversion to integer is fine, but not to address
// this is an example of explicit conversions being not transitive (though implicit should be)
if (_convertTo.getCategory() == Category::INTEGER)
if (_convertTo.getCategory() == getCategory())
{
IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
if (!convertTo.isAddress())
@ -152,22 +180,55 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
return isImplicitlyConvertibleTo(_convertTo);
}
bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bytes BoolType::literalToBigEndian(const Literal& _literal) const
{
if (_convertTo.getCategory() != Category::CONTRACT)
if (_literal.getToken() == Token::TRUE_LITERAL)
return bytes(1, 1);
else if (_literal.getToken() == Token::FALSE_LITERAL)
return bytes(1, 0);
else
return NullBytes;
}
bool ContractType::operator==(const Type& _other) const
{
if (_other.getCategory() != getCategory())
return false;
ContractType const& convertTo = dynamic_cast<ContractType const&>(_convertTo);
return &m_contract == &convertTo.m_contract;
ContractType const& other = dynamic_cast<ContractType const&>(_other);
return other.m_contract == m_contract;
}
bool StructType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool StructType::operator==(const Type& _other) const
{
if (_convertTo.getCategory() != Category::STRUCT)
if (_other.getCategory() != getCategory())
return false;
StructType const& convertTo = dynamic_cast<StructType const&>(_convertTo);
return &m_struct == &convertTo.m_struct;
StructType const& other = dynamic_cast<StructType const&>(_other);
return other.m_struct == m_struct;
}
bool FunctionType::operator==(const Type& _other) const
{
if (_other.getCategory() != getCategory())
return false;
FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
return other.m_function == m_function;
}
bool MappingType::operator==(const Type& _other) const
{
if (_other.getCategory() != getCategory())
return false;
MappingType const& other = dynamic_cast<MappingType const&>(_other);
return *other.m_keyType == *m_keyType && *other.m_valueType == *m_valueType;
}
bool TypeType::operator==(const Type& _other) const
{
if (_other.getCategory() != getCategory())
return false;
TypeType const& other = dynamic_cast<TypeType const&>(_other);
return *getActualType() == *other.getActualType();
}
}
}

31
libsolidity/Types.h

@ -26,6 +26,7 @@
#include <string>
#include <boost/noncopyable.hpp>
#include <boost/assert.hpp>
#include <libdevcore/Common.h>
#include <libsolidity/ASTForward.h>
#include <libsolidity/Token.h>
@ -52,7 +53,7 @@ public:
static std::shared_ptr<Type> forLiteral(Literal const& _literal);
virtual Category getCategory() const = 0;
virtual bool isImplicitlyConvertibleTo(Type const&) const { return false; }
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const
{
return isImplicitlyConvertibleTo(_convertTo);
@ -60,7 +61,11 @@ public:
virtual bool acceptsBinaryOperator(Token::Value) const { return false; }
virtual bool acceptsUnaryOperator(Token::Value) const { return false; }
virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); }
virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); }
virtual std::string toString() const = 0;
virtual bytes literalToBigEndian(Literal const&) const { return NullBytes; }
};
class IntegerType: public Type
@ -81,7 +86,10 @@ public:
virtual bool acceptsBinaryOperator(Token::Value _operator) const override;
virtual bool acceptsUnaryOperator(Token::Value _operator) const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override;
virtual bytes literalToBigEndian(Literal const& _literal) const override;
int getNumBits() const { return m_bits; }
bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; }
@ -97,10 +105,6 @@ class BoolType: public Type
{
public:
virtual Category getCategory() const { return Category::BOOL; }
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override
{
return _convertTo.getCategory() == Category::BOOL;
}
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool acceptsBinaryOperator(Token::Value _operator) const override
{
@ -110,7 +114,9 @@ public:
{
return _operator == Token::NOT || _operator == Token::DELETE;
}
virtual std::string toString() const override { return "bool"; }
virtual bytes literalToBigEndian(Literal const& _literal) const override;
};
class ContractType: public Type
@ -118,7 +124,8 @@ class ContractType: public Type
public:
virtual Category getCategory() const override { return Category::CONTRACT; }
ContractType(ContractDefinition const& _contract): m_contract(_contract) {}
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override { return "contract{...}"; }
@ -131,12 +138,12 @@ class StructType: public Type
public:
virtual Category getCategory() const override { return Category::STRUCT; }
StructType(StructDefinition const& _struct): m_struct(_struct) {}
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const;
virtual bool acceptsUnaryOperator(Token::Value _operator) const override
{
return _operator == Token::DELETE;
}
virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override { return "struct{...}"; }
@ -154,6 +161,8 @@ public:
virtual std::string toString() const override { return "function(...)returns(...)"; }
virtual bool operator==(Type const& _other) const override;
private:
FunctionDefinition const& m_function;
};
@ -165,8 +174,11 @@ public:
MappingType() {}
virtual std::string toString() const override { return "mapping(...=>...)"; }
virtual bool operator==(Type const& _other) const override;
private:
//@todo
std::shared_ptr<Type const> m_keyType;
std::shared_ptr<Type const> m_valueType;
};
//@todo should be changed into "empty anonymous struct"
@ -175,6 +187,7 @@ class VoidType: public Type
public:
virtual Category getCategory() const override { return Category::VOID; }
VoidType() {}
virtual std::string toString() const override { return "void"; }
};
@ -186,6 +199,8 @@ public:
std::shared_ptr<Type const> const& getActualType() const { return m_actualType; }
virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; }
private:

41
solc/main.cpp

@ -10,6 +10,7 @@
#include <libsolidity/ASTPrinter.h>
#include <libsolidity/NameAndTypeResolver.h>
#include <libsolidity/Exceptions.h>
#include <libsolidity/Compiler.h>
#include <libsolidity/SourceReferenceFormatter.h>
using namespace dev;
@ -34,6 +35,35 @@ void version()
exit(0);
}
/// Helper class that extracts the first expression in an AST.
class FirstExpressionExtractor: private ASTVisitor
{
public:
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); }
Expression* getExpression() const { return m_expression; }
private:
virtual bool visit(Expression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); }
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); }
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); }
bool checkExpression(Expression& _expression)
{
if (m_expression == nullptr)
m_expression = &_expression;
return false;
}
private:
Expression* m_expression;
};
int main(int argc, char** argv)
{
std::string infile;
@ -92,5 +122,16 @@ int main(int argc, char** argv)
std::cout << "Syntax tree for the contract:" << std::endl;
dev::solidity::ASTPrinter printer(ast, sourceCode);
printer.print(std::cout);
FirstExpressionExtractor extractor(*ast);
CompilerContext context;
ExpressionCompiler compiler(context);
compiler.compile(*extractor.getExpression());
bytes instructions = compiler.getAssembledBytecode();
// debug
std::cout << "Bytecode for the first expression: " << std::endl;
std::cout << eth::disassemble(instructions) << std::endl;
return 0;
}

229
test/solidityCompiler.cpp

@ -0,0 +1,229 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Unit tests for the name and type resolution of the solidity parser.
*/
#include <string>
#include <libdevcore/Log.h>
#include <libsolidity/Scanner.h>
#include <libsolidity/Parser.h>
#include <libsolidity/NameAndTypeResolver.h>
#include <libsolidity/Compiler.h>
#include <libsolidity/AST.h>
#include <boost/test/unit_test.hpp>
namespace dev
{
namespace solidity
{
namespace test
{
namespace
{
/// Helper class that extracts the first expression in an AST.
class FirstExpressionExtractor: private ASTVisitor
{
public:
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); }
Expression* getExpression() const { return m_expression; }
private:
virtual bool visit(Expression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); }
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); }
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); }
bool checkExpression(Expression& _expression)
{
if (m_expression == nullptr)
m_expression = &_expression;
return false;
}
private:
Expression* m_expression;
};
bytes compileFirstExpression(const std::string& _sourceCode)
{
Parser parser;
ASTPointer<ContractDefinition> contract;
BOOST_REQUIRE_NO_THROW(contract = parser.parse(std::make_shared<Scanner>(CharStream(_sourceCode))));
NameAndTypeResolver resolver;
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract));
FirstExpressionExtractor extractor(*contract);
BOOST_REQUIRE(extractor.getExpression() != nullptr);
CompilerContext context;
ExpressionCompiler compiler(context);
compiler.compile(*extractor.getExpression());
bytes instructions = compiler.getAssembledBytecode();
// debug
//std::cout << eth::disassemble(instructions) << std::endl;
return instructions;
}
} // end anonymous namespace
BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler)
BOOST_AUTO_TEST_CASE(literal_true)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = true; }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x1});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(literal_false)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = false; }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x0});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(int_literal)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = 0x12345678901234567890; }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90,
0x12, 0x34, 0x56, 0x78, 0x90});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(comparison)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = (0x10aa < 0x11aa) != true; }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH2), 0x10, 0xaa,
byte(eth::Instruction::PUSH2), 0x11, 0xaa,
byte(eth::Instruction::GT),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::EQ),
byte(eth::Instruction::NOT)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(short_circuiting)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0xa,
byte(eth::Instruction::PUSH1), 0x8,
byte(eth::Instruction::ADD),
byte(eth::Instruction::PUSH1), 0x4,
byte(eth::Instruction::GT),
byte(eth::Instruction::NOT), // after this we have 10 + 8 >= 4
byte(eth::Instruction::DUP1),
byte(eth::Instruction::PUSH1), 0x14,
byte(eth::Instruction::JUMPI), // short-circuit if it is true
byte(eth::Instruction::PUSH1), 0x2,
byte(eth::Instruction::PUSH1), 0x9,
byte(eth::Instruction::EQ),
byte(eth::Instruction::NOT), // after this we have 2 != 9
byte(eth::Instruction::JUMPDEST),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::EQ),
byte(eth::Instruction::NOT)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(arithmetics)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = (1 * (2 / (3 % (4 + (5 - (6 | (7 & (8 ^ 9)))))))); }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::PUSH1), 0x2,
byte(eth::Instruction::PUSH1), 0x3,
byte(eth::Instruction::PUSH1), 0x4,
byte(eth::Instruction::PUSH1), 0x5,
byte(eth::Instruction::PUSH1), 0x6,
byte(eth::Instruction::PUSH1), 0x7,
byte(eth::Instruction::PUSH1), 0x8,
byte(eth::Instruction::PUSH1), 0x9,
byte(eth::Instruction::XOR),
byte(eth::Instruction::AND),
byte(eth::Instruction::OR),
byte(eth::Instruction::SWAP1),
byte(eth::Instruction::SUB),
byte(eth::Instruction::ADD),
byte(eth::Instruction::MOD),
byte(eth::Instruction::DIV),
byte(eth::Instruction::MUL)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_CASE(unary_operators)
{
char const* sourceCode = "contract test {\n"
" function f() { var x = !(~+-(--(++1++)--) == 2); }"
"}\n";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::ADD),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::SWAP1),
byte(eth::Instruction::SUB),
byte(eth::Instruction::NEG),
byte(eth::Instruction::PUSH1), 0x1,
byte(eth::Instruction::PUSH1), 0x0,
byte(eth::Instruction::SUB),
byte(eth::Instruction::XOR), // bitwise not
byte(eth::Instruction::PUSH1), 0x2,
byte(eth::Instruction::EQ),
byte(eth::Instruction::NOT)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}
BOOST_AUTO_TEST_SUITE_END()
}
}
} // end namespaces
Loading…
Cancel
Save