Initial EVM1.5 assembly implementation.

This commit is contained in:
chriseth 2017-05-24 18:34:19 +02:00
parent 21e0b69dcb
commit 97cc968a13
20 changed files with 907 additions and 189 deletions

View File

@ -85,6 +85,13 @@ enum class Instruction: uint8_t
DIFFICULTY, ///< get the block's difficulty
GASLIMIT, ///< get the block's gas limit
JUMPTO = 0x4a, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
POP = 0x50, ///< remove item from stack
MLOAD, ///< load word from memory
MSTORE, ///< save word to memory
@ -97,6 +104,8 @@ enum class Instruction: uint8_t
MSIZE, ///< get the size of active memory
GAS, ///< get the amount of available gas
JUMPDEST, ///< set a potential jump destination
BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
BEGINDATA, ///< begine the data section -- not part of Instructions.cpp
PUSH1 = 0x60, ///< place 1 byte item on stack
PUSH2, ///< place 2 byte item on stack

View File

@ -41,6 +41,9 @@ struct Identifier;
namespace julia
{
///
/// Assembly class that abstracts both the libevmasm assembly and the new julia evm assembly.
///
class AbstractAssembly
{
public:
@ -66,6 +69,21 @@ public:
/// Append a reference to a to-be-linked symobl.
/// Currently, we assume that the value is always a 20 byte number.
virtual void appendLinkerSymbol(std::string const& _name) = 0;
/// Append a jump instruction.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendJump(int _stackDiffAfter) = 0;
/// Append a jump-to-immediate operation.
virtual void appendJumpTo(LabelID _label, int _stackDiffAfter = 0) = 0;
/// Append a jump-to-if-immediate operation.
virtual void appendJumpToIf(LabelID _label) = 0;
/// Start a subroutine.
virtual void appendBeginsub(LabelID _label, int _arguments) = 0;
/// Call a subroutine.
virtual void appendJumpsub(LabelID _label, int _arguments, int _returns) = 0;
/// Return from a subroutine.
virtual void appendReturnsub(int _returns) = 0;
};
enum class IdentifierContext { LValue, RValue };
@ -74,7 +92,7 @@ enum class IdentifierContext { LValue, RValue };
/// to inline assembly (not used in standalone assembly mode).
struct ExternalIdentifierAccess
{
using Resolver = std::function<size_t(solidity::assembly::Identifier const&, IdentifierContext)>;
using Resolver = std::function<size_t(solidity::assembly::Identifier const&, IdentifierContext, bool /*_crossesFunctionBoundary*/)>;
/// Resolve a an external reference given by the identifier in the given context.
/// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
Resolver resolve;

View File

@ -0,0 +1,174 @@
/*
This file is part of solidity.
solidity 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.
solidity 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 solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Assembly interface for EVM and EVM1.5.
*/
#include <libjulia/backends/evm/EVMAssembly.h>
#include <libevmasm/Instruction.h>
#include <libsolidity/interface/Utils.h>
using namespace std;
using namespace dev;
using namespace julia;
namespace
{
size_t constexpr labelReferenceSize = 4;
}
void EVMAssembly::setSourceLocation(SourceLocation const&)
{
// Ignored for now;
}
void EVMAssembly::appendInstruction(solidity::Instruction _instr)
{
m_bytecode.push_back(byte(_instr));
m_stackHeight += solidity::instructionInfo(_instr).ret - solidity::instructionInfo(_instr).args;
}
void EVMAssembly::appendConstant(u256 const& _constant)
{
bytes data = toCompactBigEndian(_constant, 1);
appendInstruction(solidity::pushInstruction(data.size()));
m_bytecode += data;
}
void EVMAssembly::appendLabel(LabelID _labelId)
{
setLabelToCurrentPosition(_labelId);
appendInstruction(solidity::Instruction::JUMPDEST);
}
void EVMAssembly::appendLabelReference(LabelID _labelId)
{
solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode.");
// @TODO we now always use labelReferenceSize for all labels, it could be shortened
// for some of them.
appendInstruction(solidity::pushInstruction(labelReferenceSize));
m_labelReferences[m_bytecode.size()] = _labelId;
m_bytecode += bytes(labelReferenceSize);
}
EVMAssembly::LabelID EVMAssembly::newLabelId()
{
m_labelPositions[m_nextLabelID] = size_t(-1);
return m_nextLabelID++;
}
void EVMAssembly::appendLinkerSymbol(string const&)
{
solAssert(false, "Linker symbols not yet implemented.");
}
void EVMAssembly::appendJump(int _stackDiffAfter)
{
solAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
appendInstruction(solidity::Instruction::JUMP);
m_stackHeight += _stackDiffAfter;
}
void EVMAssembly::appendJumpTo(AbstractAssembly::LabelID _labelId, int _stackDiffAfter)
{
if (m_evm15)
{
m_bytecode.push_back(byte(solidity::Instruction::JUMPTO));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _stackDiffAfter;
}
else
{
appendLabelReference(_labelId);
appendJump(_stackDiffAfter);
}
}
void EVMAssembly::appendJumpToIf(AbstractAssembly::LabelID _labelId)
{
if (m_evm15)
{
m_bytecode.push_back(byte(solidity::Instruction::JUMPIF));
appendLabelReferenceInternal(_labelId);
m_stackHeight--;
}
else
{
appendLabelReference(_labelId);
appendInstruction(solidity::Instruction::JUMPI);
}
}
void EVMAssembly::appendBeginsub(AbstractAssembly::LabelID _labelId, int _arguments)
{
solAssert(m_evm15, "BEGINSUB used for EVM 1.0");
solAssert(_arguments >= 0, "");
setLabelToCurrentPosition(_labelId);
m_bytecode.push_back(byte(solidity::Instruction::BEGINSUB));
m_stackHeight += _arguments;
}
void EVMAssembly::appendJumpsub(AbstractAssembly::LabelID _labelId, int _arguments, int _returns)
{
solAssert(m_evm15, "JUMPSUB used for EVM 1.0");
solAssert(_arguments >= 0 && _returns >= 0, "");
m_bytecode.push_back(byte(solidity::Instruction::JUMPSUB));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _returns - _arguments;
}
void EVMAssembly::appendReturnsub(int _returns)
{
solAssert(m_evm15, "RETURNSUB used for EVM 1.0");
solAssert(_returns >= 0, "");
m_bytecode.push_back(byte(solidity::Instruction::RETURNSUB));
m_stackHeight -= _returns;
}
eth::LinkerObject EVMAssembly::finalize()
{
for (auto const& ref: m_labelReferences)
{
size_t referencePos = ref.first;
solAssert(m_labelPositions.count(ref.second), "");
size_t labelPos = m_labelPositions.at(ref.second);
solAssert(labelPos != size_t(-1), "Undefined but allocated label used.");
solAssert(m_bytecode.size() >= 4 && referencePos <= m_bytecode.size() - 4, "");
solAssert(labelPos < (uint64_t(1) << (8 * labelReferenceSize)), "");
for (size_t i = 0; i < labelReferenceSize; i++)
m_bytecode[referencePos + i] = byte((labelPos >> (8 * (labelReferenceSize - i - 1))) & 0xff);
}
eth::LinkerObject obj;
obj.bytecode = m_bytecode;
return obj;
}
void EVMAssembly::setLabelToCurrentPosition(AbstractAssembly::LabelID _labelId)
{
solAssert(m_labelPositions.count(_labelId), "Label not found.");
solAssert(m_labelPositions[_labelId] == size_t(-1), "Label already set.");
m_labelPositions[_labelId] = m_bytecode.size();
}
void EVMAssembly::appendLabelReferenceInternal(AbstractAssembly::LabelID _labelId)
{
m_labelReferences[m_bytecode.size()] = _labelId;
m_bytecode += bytes(labelReferenceSize);
}

View File

@ -0,0 +1,90 @@
/*
This file is part of solidity.
solidity 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.
solidity 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 solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Assembly interface for EVM and EVM1.5.
*/
#pragma once
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <libevmasm/LinkerObject.h>
#include <map>
namespace dev
{
namespace julia
{
class EVMAssembly: public AbstractAssembly
{
public:
explicit EVMAssembly(bool _evm15 = false): m_evm15(_evm15) { }
virtual ~EVMAssembly() {}
/// Set a new source location valid starting from the next instruction.
virtual void setSourceLocation(SourceLocation const& _location) override;
/// Retrieve the current height of the stack. This does not have to be zero
/// at the beginning.
virtual int stackHeight() const override { return m_stackHeight; }
/// Append an EVM instruction.
virtual void appendInstruction(solidity::Instruction _instruction) override;
/// Append a constant.
virtual void appendConstant(u256 const& _constant) override;
/// Append a label.
virtual void appendLabel(LabelID _labelId) override;
/// Append a label reference.
virtual void appendLabelReference(LabelID _labelId) override;
/// Generate a new unique label.
virtual LabelID newLabelId() override;
/// Append a reference to a to-be-linked symobl.
/// Currently, we assume that the value is always a 20 byte number.
virtual void appendLinkerSymbol(std::string const& _name) override;
/// Append a jump instruction.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendJump(int _stackDiffAfter) override;
/// Append a jump-to-immediate operation.
virtual void appendJumpTo(LabelID _label, int _stackDiffAfter) override;
/// Append a jump-to-if-immediate operation.
virtual void appendJumpToIf(LabelID _label) override;
/// Start a subroutine.
virtual void appendBeginsub(LabelID _label, int _arguments) override;
/// Call a subroutine.
virtual void appendJumpsub(LabelID _label, int _arguments, int _returns) override;
/// Return from a subroutine.
virtual void appendReturnsub(int _returns) override;
/// Resolves references inside the bytecode and returns the linker object.
eth::LinkerObject finalize();
private:
void setLabelToCurrentPosition(AbstractAssembly::LabelID _labelId);
void appendLabelReferenceInternal(AbstractAssembly::LabelID _labelId);
bool m_evm15 = false; ///< if true, switch to evm1.5 mode
LabelID m_nextLabelID = 0;
int m_stackHeight = 0;
bytes m_bytecode;
std::map<LabelID, size_t> m_labelPositions;
std::map<size_t, LabelID> m_labelReferences;
};
}
}

View File

@ -25,49 +25,359 @@
#include <libsolidity/interface/Utils.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
using namespace dev::julia;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
CodeTransform::CodeTransform(
ErrorReporter& _errorReporter,
AbstractAssembly& _assembly,
Block const& _block,
AsmAnalysisInfo& _analysisInfo,
ExternalIdentifierAccess const& _identifierAccess,
int _initialStackHeight
):
m_errorReporter(_errorReporter),
m_assembly(_assembly),
m_info(_analysisInfo),
m_scope(*_analysisInfo.scopes.at(&_block)),
m_identifierAccess(_identifierAccess),
m_initialStackHeight(_initialStackHeight)
void CodeTransform::run(Block const& _block)
{
m_scope = m_info.scopes.at(&_block).get();
int blockStartStackHeight = m_assembly.stackHeight();
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
m_assembly.setSourceLocation(_block.location);
// pop variables
for (auto const& identifier: m_scope.identifiers)
for (auto const& identifier: m_scope->identifiers)
if (identifier.second.type() == typeid(Scope::Variable))
m_assembly.appendInstruction(solidity::Instruction::POP);
int deposit = m_assembly.stackHeight() - blockStartStackHeight;
solAssert(deposit == 0, "Invalid stack height at end of block.");
checkStackHeight(&_block);
}
void CodeTransform::operator()(const FunctionDefinition&)
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
{
solAssert(false, "Function definition not removed during desugaring phase.");
solAssert(m_scope, "");
int expectedItems = _varDecl.variables.size();
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(expectedItems, height);
for (auto const& variable: _varDecl.variables)
{
auto& var = boost::get<Scope::Variable>(m_scope->identifiers.at(variable.name));
var.stackHeight = height++;
var.active = true;
}
}
void CodeTransform::operator()(Assignment const& _assignment)
{
visitExpression(*_assignment.value);
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(StackAssignment const& _assignment)
{
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(Label const& _label)
{
m_assembly.setSourceLocation(_label.location);
solAssert(m_scope, "");
solAssert(m_scope->identifiers.count(_label.name), "");
Scope::Label& label = boost::get<Scope::Label>(m_scope->identifiers.at(_label.name));
assignLabelIdIfUnset(label.id);
m_assembly.appendLabel(*label.id);
checkStackHeight(&_label);
}
void CodeTransform::operator()(FunctionCall const& _call)
{
solAssert(m_scope, "");
m_assembly.setSourceLocation(_call.location);
EVMAssembly::LabelID returnLabel(-1); // only used for evm 1.0
if (!m_evm15)
{
returnLabel = m_assembly.newLabelId();
m_assembly.appendLabelReference(returnLabel);
}
Scope::Function* function = nullptr;
solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor(
[=](Scope::Variable&) { solAssert(false, "Expected function name."); },
[=](Scope::Label&) { solAssert(false, "Expected function name."); },
[&](Scope::Function& _function) { function = &_function; }
)), "Function name not found.");
solAssert(function, "");
solAssert(function->arguments.size() == _call.arguments.size(), "");
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
visitExpression(arg);
m_assembly.setSourceLocation(_call.location);
assignLabelIdIfUnset(function->id);
if (m_evm15)
m_assembly.appendJumpsub(*function->id, function->arguments.size(), function->returns.size());
else
{
m_assembly.appendJumpTo(*function->id, function->returns.size() - function->arguments.size() - 1);
m_assembly.appendLabel(returnLabel);
}
checkStackHeight(&_call);
}
void CodeTransform::operator()(FunctionalInstruction const& _instruction)
{
if (m_evm15 && (
_instruction.instruction.instruction == solidity::Instruction::JUMP ||
_instruction.instruction.instruction == solidity::Instruction::JUMPI
))
{
bool const isJumpI = _instruction.instruction.instruction == solidity::Instruction::JUMPI;
if (isJumpI)
{
solAssert(_instruction.arguments.size() == 2, "");
visitExpression(_instruction.arguments.at(1));
}
else
{
solAssert(_instruction.arguments.size() == 1, "");
}
m_assembly.setSourceLocation(_instruction.location);
auto label = labelFromIdentifier(boost::get<assembly::Identifier>(_instruction.arguments.at(0)));
if (isJumpI)
m_assembly.appendJumpToIf(label);
else
m_assembly.appendJumpTo(label);
}
else
{
for (auto const& arg: _instruction.arguments | boost::adaptors::reversed)
visitExpression(arg);
(*this)(_instruction.instruction);
}
checkStackHeight(&_instruction);
}
void CodeTransform::operator()(assembly::Identifier const& _identifier)
{
m_assembly.setSourceLocation(_identifier.location);
// First search internals, then externals.
solAssert(m_scope, "");
if (m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable& _var)
{
if (int heightDiff = variableHeightDiff(_var, _identifier.location, false))
m_assembly.appendInstruction(solidity::dupInstruction(heightDiff));
else
// Store something to balance the stack
m_assembly.appendConstant(u256(0));
},
[=](Scope::Label& _label)
{
assignLabelIdIfUnset(_label.id);
m_assembly.appendLabelReference(*_label.id);
},
[=](Scope::Function&)
{
solAssert(false, "Function not removed during desugaring.");
}
)))
{
return;
}
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_assembly);
checkStackHeight(&_identifier);
}
void CodeTransform::operator()(assembly::Literal const& _literal)
{
m_assembly.setSourceLocation(_literal.location);
if (_literal.kind == assembly::LiteralKind::Number)
m_assembly.appendConstant(u256(_literal.value));
else if (_literal.kind == assembly::LiteralKind::Boolean)
{
if (_literal.value == "true")
m_assembly.appendConstant(u256(1));
else
m_assembly.appendConstant(u256(0));
}
else
{
solAssert(_literal.value.size() <= 32, "");
m_assembly.appendConstant(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
}
checkStackHeight(&_literal);
}
void CodeTransform::operator()(assembly::Instruction const& _instruction)
{
solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5");
solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5");
m_assembly.setSourceLocation(_instruction.location);
m_assembly.appendInstruction(_instruction.instruction);
checkStackHeight(&_instruction);
}
void CodeTransform::operator()(Switch const& _switch)
{
//@TODO use JUMPV in EVM1.5?
visitExpression(*_switch.expression);
int expressionHeight = m_assembly.stackHeight();
map<Case const*, AbstractAssembly::LabelID> caseBodies;
AbstractAssembly::LabelID end = m_assembly.newLabelId();
for (Case const& c: _switch.cases)
{
if (c.value)
{
(*this)(*c.value);
m_assembly.setSourceLocation(c.location);
AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId();
caseBodies[&c] = bodyLabel;
solAssert(m_assembly.stackHeight() == expressionHeight + 1, "");
m_assembly.appendInstruction(solidity::dupInstruction(2));
m_assembly.appendInstruction(solidity::Instruction::EQ);
m_assembly.appendJumpToIf(bodyLabel);
}
else
// default case
(*this)(c.body);
}
m_assembly.setSourceLocation(_switch.location);
m_assembly.appendJumpTo(end);
size_t numCases = caseBodies.size();
for (auto const& c: caseBodies)
{
m_assembly.setSourceLocation(c.first->location);
m_assembly.appendLabel(c.second);
(*this)(c.first->body);
if (--numCases > 0)
{
m_assembly.setSourceLocation(c.first->location);
m_assembly.appendJumpTo(end);
}
}
m_assembly.setSourceLocation(_switch.location);
m_assembly.appendLabel(end);
m_assembly.appendInstruction(solidity::Instruction::POP);
checkStackHeight(&_switch);
}
void CodeTransform::operator()(FunctionDefinition const& _function)
{
solAssert(m_scope, "");
solAssert(m_scope->identifiers.count(_function.name), "");
Scope::Function& function = boost::get<Scope::Function>(m_scope->identifiers.at(_function.name));
assignLabelIdIfUnset(function.id);
int height = m_evm15 ? 0 : 1;
solAssert(m_info.scopes.at(&_function.body), "");
Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
solAssert(varScope, "");
for (auto const& v: _function.arguments | boost::adaptors::reversed)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
var.stackHeight = height++;
var.active = true;
}
m_assembly.setSourceLocation(_function.location);
int stackHeightBefore = m_assembly.stackHeight();
AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId();
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
if (m_evm15)
m_assembly.appendBeginsub(*function.id, _function.arguments.size());
else
m_assembly.appendLabel(*function.id);
for (auto const& v: _function.returns)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
var.stackHeight = height++;
var.active = true;
m_assembly.appendConstant(u256(0));
}
CodeTransform(m_errorReporter, m_assembly, m_info, m_evm15, m_identifierAccess, 0).run(_function.body);
if (_function.arguments.size() > 0)
{
vector<int> stackLayout;
if (!m_evm15)
stackLayout.push_back(_function.returns.size()); // Move return label to the top
stackLayout += vector<int>(_function.arguments.size(), -1); // discard all arguments
for (size_t i = 0; i < _function.returns.size(); ++i)
stackLayout.push_back(i);
solAssert(stackLayout.size() <= 17, "Stack too deep");
while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
m_assembly.appendInstruction(solidity::Instruction::POP);
stackLayout.pop_back();
}
else
{
m_assembly.appendInstruction(swapInstruction(stackLayout.size() - stackLayout.back() - 1));
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
for (int i = 0; size_t(i) < stackLayout.size(); ++i)
solAssert(i == stackLayout[i], "Error reshuffling stack.");
}
if (m_evm15)
m_assembly.appendReturnsub(_function.returns.size());
else
m_assembly.appendJump(stackHeightBefore - _function.returns.size());
m_assembly.appendLabel(afterFunction);
checkStackHeight(&_function);
}
void CodeTransform::operator()(Block const& _block)
{
CodeTransform(m_errorReporter, m_assembly, m_info, m_evm15, m_identifierAccess, m_initialStackHeight).run(_block);
}
AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier)
{
AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1);
if (!m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable&) { solAssert(false, "Expected label"); },
[&](Scope::Label& _label)
{
assignLabelIdIfUnset(_label.id);
label = *_label.id;
},
[=](Scope::Function&) { solAssert(false, "Expected label"); }
)))
{
solAssert(false, "Identifier not found.");
}
return label;
}
void CodeTransform::visitExpression(Statement const& _expression)
{
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, _expression);
expectDeposit(1, height);
}
void CodeTransform::generateAssignment(Identifier const& _variableName, SourceLocation const& _location)
{
auto var = m_scope.lookup(_variableName.name);
solAssert(m_scope, "");
auto var = m_scope->lookup(_variableName.name);
if (var)
{
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
@ -111,142 +421,15 @@ void CodeTransform::checkStackHeight(void const* _astElement)
solAssert(m_info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
solAssert(
m_info.stackHeightInfo.at(_astElement) == m_assembly.stackHeight() - m_initialStackHeight,
"Stack height mismatch between analysis and code generation phase."
"Stack height mismatch between analysis and code generation phase: Analysis: " +
to_string(m_info.stackHeightInfo.at(_astElement)) +
" code gen: " +
to_string(m_assembly.stackHeight() - m_initialStackHeight)
);
}
void CodeTransform::assignLabelIdIfUnset(Scope::Label& _label)
void CodeTransform::assignLabelIdIfUnset(boost::optional<AbstractAssembly::LabelID>& _labelId)
{
if (!_label.id)
_label.id.reset(m_assembly.newLabelId());
}
void CodeTransform::operator()(Block const& _block)
{
CodeTransform(m_errorReporter, m_assembly, _block, m_info, m_identifierAccess, m_initialStackHeight);
checkStackHeight(&_block);
}
void CodeTransform::operator()(Switch const&)
{
solAssert(false, "Switch not removed during desugaring phase.");
}
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
{
int expectedItems = _varDecl.variables.size();
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(expectedItems, height);
for (auto const& variable: _varDecl.variables)
{
auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(variable.name));
var.stackHeight = height++;
var.active = true;
}
}
void CodeTransform::operator()(Assignment const& _assignment)
{
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_assignment.value);
expectDeposit(1, height);
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(StackAssignment const& _assignment)
{
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(Label const& _label)
{
m_assembly.setSourceLocation(_label.location);
solAssert(m_scope.identifiers.count(_label.name), "");
Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name));
assignLabelIdIfUnset(label);
m_assembly.appendLabel(*label.id);
checkStackHeight(&_label);
}
void CodeTransform::operator()(FunctionCall const&)
{
solAssert(false, "Function call not removed during desugaring phase.");
}
void CodeTransform::operator()(FunctionalInstruction const& _instr)
{
for (auto it = _instr.arguments.rbegin(); it != _instr.arguments.rend(); ++it)
{
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *it);
expectDeposit(1, height);
}
(*this)(_instr.instruction);
checkStackHeight(&_instr);
}
void CodeTransform::operator()(assembly::Identifier const& _identifier)
{
m_assembly.setSourceLocation(_identifier.location);
// First search internals, then externals.
if (m_scope.lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable& _var)
{
if (int heightDiff = variableHeightDiff(_var, _identifier.location, false))
m_assembly.appendInstruction(solidity::dupInstruction(heightDiff));
else
// Store something to balance the stack
m_assembly.appendConstant(u256(0));
},
[=](Scope::Label& _label)
{
assignLabelIdIfUnset(_label);
m_assembly.appendLabelReference(*_label.id);
},
[=](Scope::Function&)
{
solAssert(false, "Function not removed during desugaring.");
}
)))
{
return;
}
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_assembly);
checkStackHeight(&_identifier);
}
void CodeTransform::operator()(assembly::Literal const& _literal)
{
m_assembly.setSourceLocation(_literal.location);
if (_literal.kind == assembly::LiteralKind::Number)
m_assembly.appendConstant(u256(_literal.value));
else if (_literal.kind == assembly::LiteralKind::Boolean)
{
if (_literal.value == "true")
m_assembly.appendConstant(u256(1));
else
m_assembly.appendConstant(u256(0));
}
else
{
solAssert(_literal.value.size() <= 32, "");
m_assembly.appendConstant(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
}
checkStackHeight(&_literal);
}
void CodeTransform::operator()(assembly::Instruction const& _instruction)
{
m_assembly.setSourceLocation(_instruction.location);
m_assembly.appendInstruction(_instruction.instruction);
checkStackHeight(&_instruction);
if (!_labelId)
_labelId.reset(m_assembly.newLabelId());
}

View File

@ -18,12 +18,13 @@
* Common code generator for translating Julia / inline assembly to EVM and EVM1.5.
*/
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <libjulia/backends/evm/EVMAssembly.h>
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
namespace dev
{
@ -45,37 +46,49 @@ struct StackAssignment;
struct FunctionDefinition;
struct FunctionCall;
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;
struct AsmAnalysisInfo;
}
}
namespace julia
{
class EVMAssembly;
class CodeTransform: public boost::static_visitor<>
{
public:
/// Create the code transformer which appends assembly to _assembly as a side-effect
/// of its creation.
/// Create the code transformer.
/// @param _identifierAccess used to resolve identifiers external to the inline assembly
CodeTransform(
solidity::ErrorReporter& _errorReporter,
julia::AbstractAssembly& _assembly,
solidity::assembly::Block const& _block,
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
bool _evm15 = false,
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
): CodeTransform(_errorReporter, _assembly, _block, _analysisInfo, _identifierAccess, _assembly.stackHeight())
): CodeTransform(_errorReporter, _assembly, _analysisInfo, _evm15, _identifierAccess, _assembly.stackHeight())
{
}
private:
/// Processes the block and appends the resulting code to the assembly.
void run(solidity::assembly::Block const& _block);
protected:
CodeTransform(
solidity::ErrorReporter& _errorReporter,
julia::AbstractAssembly& _assembly,
solidity::assembly::Block const& _block,
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
bool _evm15,
ExternalIdentifierAccess const& _identifierAccess,
int _initialStackHeight
);
):
m_errorReporter(_errorReporter),
m_assembly(_assembly),
m_info(_analysisInfo),
m_evm15(_evm15),
m_identifierAccess(_identifierAccess),
m_initialStackHeight(_initialStackHeight)
{}
public:
void operator()(solidity::assembly::Instruction const& _instruction);
@ -87,11 +100,14 @@ public:
void operator()(solidity::assembly::StackAssignment const& _assignment);
void operator()(solidity::assembly::Assignment const& _assignment);
void operator()(solidity::assembly::VariableDeclaration const& _varDecl);
void operator()(solidity::assembly::Block const& _block);
void operator()(solidity::assembly::Switch const& _switch);
void operator()(solidity::assembly::FunctionDefinition const&);
void operator()(solidity::assembly::Block const& _block);
AbstractAssembly::LabelID labelFromIdentifier(solidity::assembly::Identifier const& _identifier);
/// Generates code for an expression that is supposed to return a single value.
void visitExpression(solidity::assembly::Statement const& _expression);
private:
void generateAssignment(solidity::assembly::Identifier const& _variableName, SourceLocation const& _location);
/// Determines the stack height difference to the given variables. Automatically generates
@ -103,13 +119,14 @@ private:
void checkStackHeight(void const* _astElement);
/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
void assignLabelIdIfUnset(solidity::assembly::Scope::Label& _label);
/// Assigns the label's or function's id to a value taken from eth::Assembly if it has not yet been set.
void assignLabelIdIfUnset(boost::optional<AbstractAssembly::LabelID>& _labelId);
solidity::ErrorReporter& m_errorReporter;
julia::AbstractAssembly& m_assembly;
solidity::assembly::AsmAnalysisInfo& m_info;
solidity::assembly::Scope& m_scope;
solidity::assembly::Scope* m_scope = nullptr;
bool m_evm15 = false;
ExternalIdentifierAccess m_identifierAccess;
int const m_initialStackHeight;
};

View File

@ -169,7 +169,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
ErrorList errors;
ErrorReporter errorsIgnored(errors);
julia::ExternalIdentifierAccess::Resolver resolver =
[&](assembly::Identifier const& _identifier, julia::IdentifierContext) {
[&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool _crossesFunctionBoundary) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name);
bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset");
@ -188,6 +188,12 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
}
if (declarations.size() != 1)
return size_t(-1);
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && _crossesFunctionBoundary)
{
m_errorReporter.declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function.");
return size_t(-1);
}
_inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
_inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
_inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();

View File

@ -632,7 +632,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
// We run the resolve step again regardless.
julia::ExternalIdentifierAccess::Resolver identifierAccess = [&](
assembly::Identifier const& _identifier,
julia::IdentifierContext _context
julia::IdentifierContext _context,
bool
)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);

View File

@ -269,7 +269,8 @@ void CompilerContext::appendInlineAssembly(
julia::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](
assembly::Identifier const& _identifier,
julia::IdentifierContext
julia::IdentifierContext,
bool
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);

View File

@ -21,15 +21,20 @@
*/
#include <libsolidity/codegen/ContractCompiler.h>
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/GasMeter.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <boost/range/adaptor/reversed.hpp>
#include <algorithm>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -524,7 +529,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
assembly::CodeGenerator codeGen(errorReporter);
unsigned startStackHeight = m_context.stackHeight();
julia::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](assembly::Identifier const& _identifier, julia::IdentifierContext)
identifierAccess.resolve = [&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())

View File

@ -25,7 +25,7 @@
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Utils.h>
#include <boost/range/adaptor/reversed.hpp>
@ -120,7 +120,10 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
size_t stackSize(-1);
if (m_resolver)
stackSize = m_resolver(_identifier, julia::IdentifierContext::RValue);
{
bool insideFunction = m_currentScope->insideFunction();
stackSize = m_resolver(_identifier, julia::IdentifierContext::RValue, insideFunction);
}
if (stackSize == size_t(-1))
{
// Only add an error message if the callback did not do it.
@ -200,7 +203,8 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
}
int const stackHeight = m_stackHeight;
m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
// 1 for return label, depends on VM version
m_stackHeight = 1 + _funDef.arguments.size() + _funDef.returns.size();
bool success = (*this)(_funDef.body);
@ -254,10 +258,11 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
success = false;
}
}
m_stackHeight += 1; // Return label, but depends on backend
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
if (!expectExpression(arg))
success = false;
m_stackHeight += int(returns) - int(arguments);
m_stackHeight += int(returns) - int(arguments) - 1; // Return label, but depends on backend
m_info.stackHeightInfo[&_funCall] = m_stackHeight;
return success;
}
@ -295,6 +300,7 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
}
m_stackHeight--;
m_info.stackHeightInfo[&_switch] = m_stackHeight;
return success;
}
@ -378,7 +384,10 @@ bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t
variableSize = 1;
}
else if (m_resolver)
variableSize = m_resolver(_variable, julia::IdentifierContext::LValue);
{
bool insideFunction = m_currentScope->insideFunction();
variableSize = m_resolver(_variable, julia::IdentifierContext::LValue, insideFunction);
}
if (variableSize == size_t(-1))
{
// Only add message if the callback did not.

View File

@ -20,7 +20,11 @@
#pragma once
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Exceptions.h>
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <boost/variant.hpp>

View File

@ -87,13 +87,46 @@ public:
{
m_assembly.appendLibraryAddress(_linkerSymbol);
}
virtual void appendJump(int _stackDiffAfter) override
{
appendInstruction(solidity::Instruction::JUMP);
m_assembly.adjustDeposit(_stackDiffAfter);
}
virtual void appendJumpTo(LabelID _label, int _stackDiffAfter) override
{
appendLabelReference(_label);
appendJump(_stackDiffAfter);
}
virtual void appendJumpToIf(LabelID _label) override
{
appendLabelReference(_label);
appendInstruction(solidity::Instruction::JUMPI);
}
virtual void appendBeginsub(LabelID, int) override
{
// TODO we could emulate that, though
solAssert(false, "BEGINSUB not implemented for EVM 1.0");
}
/// Call a subroutine.
virtual void appendJumpsub(LabelID, int, int) override
{
// TODO we could emulate that, though
solAssert(false, "JUMPSUB not implemented for EVM 1.0");
}
/// Return from a subroutine.
virtual void appendReturnsub(int) override
{
// TODO we could emulate that, though
solAssert(false, "RETURNSUB not implemented for EVM 1.0");
}
private:
size_t assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
{
u256 id = _tag.data();
solAssert(id <= std::numeric_limits<size_t>::max(), "Tag id too large.");
return size_t(id);
solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large.");
return LabelID(id);
}
eth::Assembly& m_assembly;
@ -107,7 +140,7 @@ eth::Assembly assembly::CodeGenerator::assemble(
{
eth::Assembly assembly;
EthAssemblyAdapter assemblyAdapter(assembly);
julia::CodeTransform(m_errorReporter, assemblyAdapter, _parsedData, _analysisInfo, _identifierAccess);
julia::CodeTransform(m_errorReporter, assemblyAdapter, _analysisInfo, false, _identifierAccess).run(_parsedData);
return assembly;
}
@ -119,5 +152,5 @@ void assembly::CodeGenerator::assemble(
)
{
EthAssemblyAdapter assemblyAdapter(_assembly);
julia::CodeTransform(m_errorReporter, assemblyAdapter, _parsedData, _analysisInfo, _identifierAccess);
julia::CodeTransform(m_errorReporter, assemblyAdapter, _analysisInfo, false, _identifierAccess).run(_parsedData);
}

View File

@ -79,3 +79,11 @@ bool Scope::exists(string const& _name)
else
return false;
}
bool Scope::insideFunction() const
{
for (Scope const* s = this; s; s = s->superScope)
if (s->functionScope)
return true;
return false;
}

View File

@ -85,6 +85,7 @@ struct Scope
Function(std::vector<JuliaType> const& _arguments, std::vector<JuliaType> const& _returns): arguments(_arguments), returns(_returns) {}
std::vector<JuliaType> arguments;
std::vector<JuliaType> returns;
boost::optional<LabelID> id;
};
using Identifier = boost::variant<Variable, Label, Function>;
@ -123,6 +124,9 @@ struct Scope
/// across function and assembly boundaries).
bool exists(std::string const& _name);
/// @returns true if this scope is inside a function.
bool insideFunction() const;
Scope* superScope = nullptr;
/// If true, variables from the super scope are not visible here (other identifiers are),
/// but they are still taken into account to prevent shadowing.

View File

@ -30,6 +30,9 @@
#include <libevmasm/Assembly.h>
#include <libjulia/backends/evm/EVMCodeTransform.h>
#include <libjulia/backends/evm/EVMAssembly.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -87,7 +90,11 @@ eth::LinkerObject AssemblyStack::assemble(Machine _machine)
return assembly.assemble();
}
case Machine::EVM15:
solUnimplemented("EVM 1.5 backend is not yet implemented.");
{
julia::EVMAssembly assembly(true);
julia::CodeTransform(m_errorReporter, assembly, *m_analysisInfo, true).run(*m_parserResult);
return assembly.finalize();
}
case Machine::eWasm:
solUnimplemented("eWasm backend is not yet implemented.");
}

View File

@ -21,11 +21,13 @@
#include "../TestHelper.h"
#include <test/libsolidity/ErrorCheck.h>
#include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/parsing/Scanner.h>
#include <test/libsolidity/ErrorCheck.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <boost/optional.hpp>
#include <boost/algorithm/string/replace.hpp>

View File

@ -481,6 +481,42 @@ BOOST_AUTO_TEST_CASE(revert)
BOOST_CHECK(successAssemble("{ revert(0, 0) }"));
}
BOOST_AUTO_TEST_CASE(function_calls)
{
BOOST_CHECK(successAssemble("{ function f() {} }"));
BOOST_CHECK(successAssemble("{ function f() { let y := 2 } }"));
BOOST_CHECK(successAssemble("{ function f() -> z { let y := 2 } }"));
BOOST_CHECK(successAssemble("{ function f(a) { let y := 2 } }"));
BOOST_CHECK(successAssemble("{ function f(a) { let y := a } }"));
BOOST_CHECK(successAssemble("{ function f() -> x, y, z {} }"));
BOOST_CHECK(successAssemble("{ function f(x, y, z) {} }"));
BOOST_CHECK(successAssemble("{ function f(a, b) -> x, y, z { y := a } }"));
BOOST_CHECK(successAssemble("{ function f() {} f() }"));
BOOST_CHECK(successAssemble("{ function f() -> x, y { x := 1 y := 2} let a, b := f() }"));
BOOST_CHECK(successAssemble("{ function f(a, b) -> x, y { x := b y := a } let a, b := f(2, 3) }"));
BOOST_CHECK(successAssemble("{ function rec(a) { rec(sub(a, 1)) } rec(2) }"));
BOOST_CHECK(successAssemble("{ let r := 2 function f() -> x, y { x := 1 y := 2} let a, b := f() b := r }"));
}
BOOST_AUTO_TEST_CASE(switch_statement)
{
BOOST_CHECK(successAssemble("{ switch 1 default {} }"));
BOOST_CHECK(successAssemble("{ switch 1 case 1 {} default {} }"));
BOOST_CHECK(successAssemble("{ switch 1 case 1 {} }"));
BOOST_CHECK(successAssemble("{ let a := 3 switch a case 1 { a := 1 } case 2 { a := 5 } a := 9}"));
BOOST_CHECK(successAssemble("{ let a := 2 switch calldataload(0) case 1 { a := 1 } case 2 { a := 5 } }"));
}
BOOST_AUTO_TEST_CASE(large_constant)
{
auto source = R"({
switch mul(1, 2)
case 0x0000000000000000000000000000000000000000000000000000000026121ff0 {
}
})";
BOOST_CHECK(successAssemble(source));
}
BOOST_AUTO_TEST_CASE(keccak256)
{
BOOST_CHECK(successAssemble("{ 0 0 keccak256 pop }"));

View File

@ -7535,6 +7535,102 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access)
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(10)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_function_call)
{
char const* sourceCode = R"(
contract C {
function f() {
assembly {
function asmfun(a, b, c) -> x, y, z {
x := a
y := b
z := 7
}
let a1, b1, c1 := asmfun(1, 2, 3)
mstore(0x00, a1)
mstore(0x20, b1)
mstore(0x40, c1)
return(0, 0x60)
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_function_call2)
{
char const* sourceCode = R"(
contract C {
function f() {
assembly {
let d := 0x10
function asmfun(a, b, c) -> x, y, z {
x := a
y := b
z := 7
}
let a1, b1, c1 := asmfun(1, 2, 3)
mstore(0x00, a1)
mstore(0x20, b1)
mstore(0x40, c1)
mstore(0x60, d)
return(0, 0x80)
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7), u256(0x10)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_switch)
{
char const* sourceCode = R"(
contract C {
function f(uint a) returns (uint b) {
assembly {
switch a
case 1 { b := 8 }
case 2 { b := 9 }
default { b := 2 }
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(2)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(8)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(9)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(2)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_recursion)
{
char const* sourceCode = R"(
contract C {
function f(uint a) returns (uint b) {
assembly {
function fac(n) -> nf {
switch n
case 0 { nf := 1 }
case 1 { nf := 1 }
default { nf := mul(n, fac(sub(n, 1))) }
}
b := fac(a)
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(1)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(2)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(6)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(4)) == encodeArgs(u256(24)));
}
BOOST_AUTO_TEST_CASE(index_access_with_type_conversion)
{
// Test for a bug where higher order bits cleanup was not done for array index access.

View File

@ -5159,6 +5159,21 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_access)
CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
}
BOOST_AUTO_TEST_CASE(inline_assembly_variable_access_out_of_functions)
{
char const* text = R"(
contract test {
function f() {
uint a;
assembly {
function g() -> x { x := a }
}
}
}
)";
CHECK_ERROR(text, DeclarationError, "Inline assembly functions cannot access their outer scope.");
}
BOOST_AUTO_TEST_CASE(invalid_mobile_type)
{
char const* text = R"(