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