Gav Wood
10 years ago
33 changed files with 2094 additions and 814 deletions
@ -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(); |
|||
} |
|||
|
|||
} |
|||
} |
@ -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; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
|||
} |
@ -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>()); |
|||
}; |
|||
|
|||
} |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
|||
} |
@ -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; |
|||
}; |
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,229 @@ |
|||
|
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Unit tests for the solidity expression compiler, testing the behaviour of the code. |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <boost/test/unit_test.hpp> |
|||
#include <libethereum/State.h> |
|||
#include <libethereum/Executive.h> |
|||
#include <libsolidity/CompilerStack.h> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
class ExecutionFramework |
|||
{ |
|||
public: |
|||
ExecutionFramework() { g_logVerbosity = 0; } |
|||
|
|||
bytes compileAndRun(std::string const& _sourceCode) |
|||
{ |
|||
bytes code = dev::solidity::CompilerStack::compile(_sourceCode); |
|||
sendMessage(code, true); |
|||
return m_output; |
|||
} |
|||
|
|||
bytes callFunction(byte _index, bytes const& _data) |
|||
{ |
|||
sendMessage(bytes(1, _index) + _data, false); |
|||
return m_output; |
|||
} |
|||
|
|||
bytes callFunction(byte _index, u256 const& _argument1) |
|||
{ |
|||
callFunction(_index, toBigEndian(_argument1)); |
|||
return m_output; |
|||
} |
|||
|
|||
private: |
|||
void sendMessage(bytes const& _data, bool _isCreation) |
|||
{ |
|||
eth::Executive executive(m_state); |
|||
eth::Transaction t = _isCreation ? eth::Transaction(0, m_gasPrice, m_gas, _data) |
|||
: eth::Transaction(0, m_gasPrice, m_gas, m_contractAddress, _data); |
|||
bytes transactionRLP = t.rlp(); |
|||
try |
|||
{ |
|||
// this will throw since the transaction is invalid, but it should nevertheless store the transaction
|
|||
executive.setup(&transactionRLP); |
|||
} |
|||
catch (...) {} |
|||
if (_isCreation) |
|||
{ |
|||
BOOST_REQUIRE(!executive.create(Address(), 0, m_gasPrice, m_gas, &_data, Address())); |
|||
m_contractAddress = executive.newAddress(); |
|||
BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); |
|||
} |
|||
else |
|||
BOOST_REQUIRE(!executive.call(m_contractAddress, Address(), 0, m_gasPrice, &_data, m_gas, Address())); |
|||
BOOST_REQUIRE(executive.go()); |
|||
executive.finalize(); |
|||
m_output = executive.out().toBytes(); |
|||
} |
|||
|
|||
Address m_contractAddress; |
|||
eth::State m_state; |
|||
u256 const m_gasPrice = 100 * eth::szabo; |
|||
u256 const m_gas = 1000000; |
|||
bytes m_output; |
|||
}; |
|||
|
|||
BOOST_AUTO_TEST_SUITE(SolidityCompilerEndToEndTest) |
|||
|
|||
BOOST_AUTO_TEST_CASE(smoke_test) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) returns(uint d) { return a * 7; }\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
u256 a = 0x200030004; |
|||
bytes result = framework.callFunction(0, a); |
|||
BOOST_CHECK(result == toBigEndian(a * 7)); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(empty_contract) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
BOOST_CHECK(framework.callFunction(0, bytes()).empty()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(recursive_calls) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint n) returns(uint nfac) {\n" |
|||
" if (n <= 1) return 1;\n" |
|||
" else return n * f(n - 1);\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(1))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(1))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(2)) == toBigEndian(u256(2))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(3)) == toBigEndian(u256(6))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(4)) == toBigEndian(u256(24))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(while_loop) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint n) returns(uint nfac) {\n" |
|||
" nfac = 1;\n" |
|||
" var i = 2;\n" |
|||
" while (i <= n) nfac *= i++;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(1))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(1))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(2)) == toBigEndian(u256(2))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(3)) == toBigEndian(u256(6))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(4)) == toBigEndian(u256(24))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(calling_other_functions) |
|||
{ |
|||
// note that the index of a function is its index in the sorted sequence of functions
|
|||
char const* sourceCode = "contract collatz {\n" |
|||
" function run(uint x) returns(uint y) {\n" |
|||
" while ((y = x) > 1) {\n" |
|||
" if (x % 2 == 0) x = evenStep(x);\n" |
|||
" else x = oddStep(x);\n" |
|||
" }\n" |
|||
" }\n" |
|||
" function evenStep(uint x) returns(uint y) {\n" |
|||
" return x / 2;\n" |
|||
" }\n" |
|||
" function oddStep(uint x) returns(uint y) {\n" |
|||
" return 3 * x + 1;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
BOOST_CHECK(framework.callFunction(2, u256(0)) == toBigEndian(u256(0))); |
|||
BOOST_CHECK(framework.callFunction(2, u256(1)) == toBigEndian(u256(1))); |
|||
BOOST_CHECK(framework.callFunction(2, u256(2)) == toBigEndian(u256(1))); |
|||
BOOST_CHECK(framework.callFunction(2, u256(8)) == toBigEndian(u256(1))); |
|||
BOOST_CHECK(framework.callFunction(2, u256(127)) == toBigEndian(u256(1))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(many_local_variables) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function run(uint x1, uint x2, uint x3) returns(uint y) {\n" |
|||
" var a = 0x1; var b = 0x10; var c = 0x100;\n" |
|||
" y = a + b + c + x1 + x2 + x3;\n" |
|||
" y += b + x2;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
BOOST_CHECK(framework.callFunction(0, toBigEndian(u256(0x1000)) + toBigEndian(u256(0x10000)) + toBigEndian(u256(0x100000))) |
|||
== toBigEndian(u256(0x121121))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(multiple_return_values) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function run(bool x1, uint x2) returns(uint y1, bool y2, uint y3) {\n" |
|||
" y1 = x2; y2 = x1;\n" |
|||
" }\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
BOOST_CHECK(framework.callFunction(0, bytes(1, 1) + toBigEndian(u256(0xcd))) |
|||
== toBigEndian(u256(0xcd)) + bytes(1, 1) + toBigEndian(u256(0))); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(short_circuiting) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function run(uint x) returns(uint y) {\n" |
|||
" x == 0 || ((x = 8) > 0);\n" |
|||
" return x;" |
|||
" }\n" |
|||
"}\n"; |
|||
ExecutionFramework framework; |
|||
framework.compileAndRun(sourceCode); |
|||
BOOST_CHECK(framework.callFunction(0, u256(0)) == toBigEndian(u256(0))); |
|||
BOOST_CHECK(framework.callFunction(0, u256(1)) == toBigEndian(u256(8))); |
|||
} |
|||
|
|||
//@todo test smaller types
|
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
|||
|
@ -0,0 +1,352 @@ |
|||
|
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2014 |
|||
* Unit tests for the solidity expression compiler. |
|||
*/ |
|||
|
|||
#include <string> |
|||
|
|||
#include <libdevcore/Log.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/Parser.h> |
|||
#include <libsolidity/NameAndTypeResolver.h> |
|||
#include <libsolidity/CompilerContext.h> |
|||
#include <libsolidity/ExpressionCompiler.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <boost/test/unit_test.hpp> |
|||
|
|||
using namespace std; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
namespace |
|||
{ |
|||
|
|||
/// Helper class that extracts the first expression in an AST.
|
|||
class FirstExpressionExtractor: private ASTVisitor |
|||
{ |
|||
public: |
|||
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); } |
|||
Expression* getExpression() const { return m_expression; } |
|||
private: |
|||
virtual bool visit(Expression& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(PrimaryExpression& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); } |
|||
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); } |
|||
bool checkExpression(Expression& _expression) |
|||
{ |
|||
if (m_expression == nullptr) |
|||
m_expression = &_expression; |
|||
return false; |
|||
} |
|||
private: |
|||
Expression* m_expression; |
|||
}; |
|||
|
|||
Declaration const& resolveDeclaration(vector<string> const& _namespacedName, |
|||
NameAndTypeResolver const& _resolver) |
|||
{ |
|||
Declaration const* declaration = nullptr; |
|||
for (string const& namePart: _namespacedName) |
|||
BOOST_REQUIRE(declaration = _resolver.resolveName(namePart, declaration)); |
|||
BOOST_REQUIRE(declaration); |
|||
return *declaration; |
|||
} |
|||
|
|||
bytes compileFirstExpression(const string& _sourceCode, vector<vector<string>> _functions = {}, |
|||
vector<vector<string>> _localVariables = {}) |
|||
{ |
|||
Parser parser; |
|||
ASTPointer<ContractDefinition> contract; |
|||
BOOST_REQUIRE_NO_THROW(contract = parser.parse(make_shared<Scanner>(CharStream(_sourceCode)))); |
|||
NameAndTypeResolver resolver; |
|||
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); |
|||
FirstExpressionExtractor extractor(*contract); |
|||
BOOST_REQUIRE(extractor.getExpression() != nullptr); |
|||
|
|||
CompilerContext context; |
|||
for (vector<string> const& function: _functions) |
|||
context.addFunction(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver))); |
|||
for (vector<string> const& variable: _localVariables) |
|||
context.addVariable(dynamic_cast<VariableDeclaration const&>(resolveDeclaration(variable, resolver))); |
|||
|
|||
ExpressionCompiler::compileExpression(context, *extractor.getExpression()); |
|||
|
|||
for (vector<string> const& function: _functions) |
|||
context << context.getFunctionEntryLabel(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver))); |
|||
bytes instructions = context.getAssembledBytecode(); |
|||
// debug
|
|||
// cout << eth::disassemble(instructions) << endl;
|
|||
return instructions; |
|||
} |
|||
|
|||
} // end anonymous namespace
|
|||
|
|||
BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler) |
|||
|
|||
BOOST_AUTO_TEST_CASE(literal_true) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = true; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(literal_false) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = false; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x0}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(int_literal) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = 0x12345678901234567890; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90, |
|||
0x12, 0x34, 0x56, 0x78, 0x90}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(comparison) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = (0x10aa < 0x11aa) != true; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH2), 0x10, 0xaa, |
|||
byte(eth::Instruction::PUSH2), 0x11, 0xaa, |
|||
byte(eth::Instruction::GT), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(short_circuiting) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = (10 + 8 >= 4 || 2 != 9) != true; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0xa, |
|||
byte(eth::Instruction::PUSH1), 0x8, |
|||
byte(eth::Instruction::ADD), |
|||
byte(eth::Instruction::PUSH1), 0x4, |
|||
byte(eth::Instruction::GT), |
|||
byte(eth::Instruction::ISZERO), // after this we have 10 + 8 >= 4
|
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x14, |
|||
byte(eth::Instruction::JUMPI), // short-circuit if it is true
|
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::PUSH1), 0x9, |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO), // after this we have 2 != 9
|
|||
byte(eth::Instruction::JUMPDEST), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(arithmetics) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = (1 * (2 / (3 % (4 + (5 - (6 | (7 & (8 ^ 9)))))))); }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::PUSH1), 0x3, |
|||
byte(eth::Instruction::PUSH1), 0x4, |
|||
byte(eth::Instruction::PUSH1), 0x5, |
|||
byte(eth::Instruction::PUSH1), 0x6, |
|||
byte(eth::Instruction::PUSH1), 0x7, |
|||
byte(eth::Instruction::PUSH1), 0x8, |
|||
byte(eth::Instruction::PUSH1), 0x9, |
|||
byte(eth::Instruction::XOR), |
|||
byte(eth::Instruction::AND), |
|||
byte(eth::Instruction::OR), |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::SUB), |
|||
byte(eth::Instruction::ADD), |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::MOD), |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::DIV), |
|||
byte(eth::Instruction::MUL)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(unary_operators) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f() { var x = !(~+-1 == 2); }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode); |
|||
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::PUSH1), 0x0, |
|||
byte(eth::Instruction::SUB), |
|||
byte(eth::Instruction::NOT), |
|||
byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::EQ), |
|||
byte(eth::Instruction::ISZERO)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(unary_inc_dec) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a) { var x = ((a++ ^ ++a) ^ a--) ^ --a; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}}); |
|||
|
|||
// Stack: a, x
|
|||
bytes expectation({byte(eth::Instruction::DUP2), |
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a x a (a+1)
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), // first ++
|
|||
// Stack here: (a+1) x a
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: (a+1) x a (a+2)
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), |
|||
// Stack here: (a+2) x a
|
|||
byte(eth::Instruction::DUP3), // second ++
|
|||
byte(eth::Instruction::XOR), |
|||
// Stack here: (a+2) x a^(a+2)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::SUB), |
|||
// Stack here: (a+2) x a^(a+2) (a+2) (a+1)
|
|||
byte(eth::Instruction::SWAP4), |
|||
byte(eth::Instruction::POP), // first --
|
|||
byte(eth::Instruction::XOR), |
|||
// Stack here: (a+1) x a^(a+2)^(a+2)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x1, |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::SUB), |
|||
// Stack here: (a+1) x a^(a+2)^(a+2) a
|
|||
byte(eth::Instruction::SWAP3), |
|||
byte(eth::Instruction::POP), // second ++
|
|||
// Stack here: a x a^(a+2)^(a+2)
|
|||
byte(eth::Instruction::DUP3), // will change
|
|||
byte(eth::Instruction::XOR)}); |
|||
// Stack here: a x a^(a+2)^(a+2)^a
|
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(assignment) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a, uint b) { (a += b) * 2; }" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); |
|||
|
|||
// Stack: a, b
|
|||
bytes expectation({byte(eth::Instruction::DUP1), |
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b a+b
|
|||
byte(eth::Instruction::SWAP2), |
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::DUP2), |
|||
// Stack here: a+b b a+b
|
|||
byte(eth::Instruction::PUSH1), 0x2, |
|||
byte(eth::Instruction::MUL)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(function_call) |
|||
{ |
|||
char const* sourceCode = "contract test {\n" |
|||
" function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n" |
|||
" function g(uint a, uint b) returns (uint c) {}\n" |
|||
"}\n"; |
|||
bytes code = compileFirstExpression(sourceCode, {{"test", "g"}}, |
|||
{{"test", "f", "a"}, {"test", "f", "b"}}); |
|||
|
|||
// Stack: a, b
|
|||
bytes expectation({byte(eth::Instruction::PUSH1), 0x0a, |
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x01, |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b <ret label> (a+1)
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::PUSH1), 0x14, |
|||
byte(eth::Instruction::JUMP), |
|||
byte(eth::Instruction::JUMPDEST), |
|||
// Stack here: a b g(a+1, b)
|
|||
byte(eth::Instruction::PUSH1), 0x02, |
|||
byte(eth::Instruction::MUL), |
|||
// Stack here: a b g(a+1, b)*2
|
|||
byte(eth::Instruction::DUP3), |
|||
byte(eth::Instruction::SWAP1), |
|||
byte(eth::Instruction::ADD), |
|||
// Stack here: a b a+g(a+1, b)*2
|
|||
byte(eth::Instruction::SWAP2), |
|||
byte(eth::Instruction::POP), |
|||
byte(eth::Instruction::DUP2), |
|||
byte(eth::Instruction::JUMPDEST)}); |
|||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} // end namespaces
|
|||
|
Loading…
Reference in new issue