Merge pull request #1711 from ethereum/asmfunctions

Assembly: Analysis stage for functions.
This commit is contained in:
chriseth 2017-04-26 17:47:48 +02:00 committed by GitHub
commit 2c1fb46bc3
22 changed files with 1392 additions and 492 deletions

View File

@ -7,6 +7,8 @@ Features:
* Commandline interface: Add the ``--standard-json`` parameter to process a Standard JSON I/O.
* Commandline interface: Support ``--allow-paths`` to define trusted import paths. Note: the
path(s) of the supplied source file(s) is always trusted.
* Inline Assembly: Storage variable access using ``_slot`` and ``_offset`` suffixes.
* Inline Assembly: Disallow blocks with unbalanced stack.
* Static analyzer: Warn about statements without effects.
Bugfixes:

View File

@ -323,9 +323,12 @@ Access to External Variables and Functions
------------------------------------------
Solidity variables and other identifiers can be accessed by simply using their name.
For storage and memory variables, this will push the address and not the value onto the
stack. Also note that non-struct and non-array storage variable addresses occupy two slots
on the stack: One for the address and one for the byte offset inside the storage slot.
For memory variables, this will push the address and not the value onto the
stack. Storage variables are different: Values in storage might not occupy a
full storage slot, so their "address" is composed of a slot and a byte-offset
inside that slot. To retrieve the slot pointed to by the variable ``x``, you
used ``x_slot`` and to retrieve the byte-offset you used ``x_offset``.
In assignments (see below), we can even use local Solidity variables to assign to.
Functions external to inline assembly can also be accessed: The assembly will
@ -340,17 +343,13 @@ changes during the call, and thus references to local variables will be wrong.
.. code::
pragma solidity ^0.4.0;
pragma solidity ^0.4.11;
contract C {
uint b;
function f(uint x) returns (uint r) {
assembly {
b pop // remove the offset, we know it is zero
sload
x
mul
=: r // assign to return variable r
r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero
}
}
}

View File

@ -25,9 +25,12 @@
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -158,21 +161,40 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
{
// We need to perform a full code generation pass here as inline assembly does not distinguish
// reference resolution and code generation.
// Errors created in this stage are completely ignored because we do not yet know
// the type and size of external identifiers, which would result in false errors.
// The only purpose of this step is to fill the inline assembly annotation with
// external references.
ErrorList errorsIgnored;
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored);
codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) {
assembly::ExternalIdentifierAccess::Resolver resolver =
[&](assembly::Identifier const& _identifier, assembly::IdentifierContext) {
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");
if (isSlot || isOffset)
{
// special mode to access storage variables
if (!declarations.empty())
// the special identifier exists itself, we should not allow that.
return size_t(-1);
string realName = _identifier.name.substr(0, _identifier.name.size() - (
isSlot ?
string("_slot").size() :
string("_offset").size()
));
declarations = m_resolver.nameFromCurrentScope(realName);
}
if (declarations.size() != 1)
return false;
_inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front();
// At this stage we neither know the code to generate nor the stack size of the identifier,
// so we do not modify assembly.
return true;
});
return size_t(-1);
_inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
_inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
_inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
return size_t(1);
};
// Will be re-generated later with correct information
assembly::AsmAnalysisInfo analysisInfo;
assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations());
return false;
}

View File

@ -24,8 +24,9 @@
#include <memory>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/ast/AST.h>
#include <libevmasm/Assembly.h> // needed for inline assembly
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
using namespace std;
using namespace dev;
@ -628,65 +629,91 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
// Inline assembly does not have its own type-checking phase, so we just run the
// code-generator and see whether it produces any errors.
// External references have already been resolved in a prior stage and stored in the annotation.
auto identifierAccess = [&](
// We run the resolve step again regardless.
assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&](
assembly::Identifier const& _identifier,
eth::Assembly& _assembly,
assembly::CodeGenerator::IdentifierContext _context
assembly::IdentifierContext _context
)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
return false;
Declaration const* declaration = ref->second;
return size_t(-1);
Declaration const* declaration = ref->second.declaration;
solAssert(!!declaration, "");
if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (ref->second.isSlot || ref->second.isOffset)
{
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
{
typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
return size_t(-1);
}
else if (_context != assembly::IdentifierContext::RValue)
{
typeError(_identifier.location, "Storage variables cannot be assigned to.");
return size_t(-1);
}
}
else if (var->isConstant())
{
typeError(_identifier.location, "Constant variables not supported by inline assembly.");
return size_t(-1);
}
else if (!var->isLocalVariable())
{
typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
return size_t(-1);
}
else if (var->type()->dataStoredIn(DataLocation::Storage))
{
typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables.");
return size_t(-1);
}
else if (var->type()->sizeOnStack() != 1)
{
typeError(_identifier.location, "Only types that use one stack slot are supported.");
return size_t(-1);
}
}
else if (_context == assembly::IdentifierContext::LValue)
{
typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
return size_t(-1);
}
if (_context == assembly::IdentifierContext::RValue)
{
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
unsigned pushes = 0;
if (dynamic_cast<FunctionDefinition const*>(declaration))
pushes = 1;
else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (var->isConstant())
fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly.");
if (var->isLocalVariable())
pushes = var->type()->sizeOnStack();
else if (!var->type()->isValueType())
pushes = 1;
else
pushes = 2; // slot number, intra slot offset
}
else if (dynamic_cast<VariableDeclaration const*>(declaration))
{
}
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
{
if (!contract->isLibrary())
return false;
pushes = 1;
{
typeError(_identifier.location, "Expected a library.");
return size_t(-1);
}
}
else
return false;
for (unsigned i = 0; i < pushes; ++i)
_assembly.append(u256(0)); // just to verify the stack height
return size_t(-1);
}
else
{
// lvalue context
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (!varDecl->isLocalVariable())
return false; // only local variables are inline-assemlby lvalues
for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i)
_assembly.append(Instruction::POP); // remove value just to verify the stack height
}
else
return false;
}
return true;
ref->second.valueSize = 1;
return size_t(1);
};
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
if (!codeGen.typeCheck(identifierAccess))
solAssert(!_inlineAssembly.annotation().analysisInfo, "");
_inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
assembly::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
m_errors,
identifierAccess
);
if (!analyzer.analyze(_inlineAssembly.operations()))
return false;
return true;
}

View File

@ -22,11 +22,12 @@
#pragma once
#include <libsolidity/ast/ASTForward.h>
#include <map>
#include <memory>
#include <vector>
#include <set>
#include <libsolidity/ast/ASTForward.h>
namespace dev
{
@ -112,13 +113,24 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
namespace assembly
{
struct Identifier; // forward
struct AsmAnalysisInfo;
struct Identifier;
}
struct InlineAssemblyAnnotation: StatementAnnotation
{
/// Mapping containing resolved references to external identifiers.
std::map<assembly::Identifier const*, Declaration const*> externalReferences;
struct ExternalIdentifierInfo
{
Declaration const* declaration = nullptr;
bool isSlot = false; ///< Whether the storage slot of a variable is queried.
bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried.
size_t valueSize = size_t(-1);
};
/// Mapping containing resolved references to external identifiers and their value size
std::map<assembly::Identifier const*, ExternalIdentifierInfo> externalReferences;
/// Information generated during analysis phase.
std::shared_ptr<assembly::AsmAnalysisInfo> analysisInfo;
};
struct ReturnAnnotation: StatementAnnotation

View File

@ -265,31 +265,40 @@ void CompilerContext::appendInlineAssembly(
}
unsigned startStackHeight = stackHeight();
auto identifierAccess = [&](
assembly::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](
assembly::Identifier const& _identifier,
eth::Assembly& _assembly,
assembly::CodeGenerator::IdentifierContext _context
) {
assembly::IdentifierContext
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
if (it == _localVariables.end())
return false;
return it == _localVariables.end() ? size_t(-1) : 1;
};
identifierAccess.generateCode = [&](
assembly::Identifier const& _identifier,
assembly::IdentifierContext _context,
eth::Assembly& _assembly
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
solAssert(it != _localVariables.end(), "");
unsigned stackDepth = _localVariables.end() - it;
int stackDiff = _assembly.deposit() - startStackHeight + stackDepth;
if (_context == assembly::CodeGenerator::IdentifierContext::LValue)
if (_context == assembly::IdentifierContext::LValue)
stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_comment("Stack too deep, try removing local variables.")
);
if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
if (_context == assembly::IdentifierContext::RValue)
_assembly.append(dupInstruction(stackDiff));
else
{
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
}
return true;
};
solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block.");

View File

@ -520,93 +520,129 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
ErrorList errors;
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors);
assembly::CodeGenerator codeGen(errors);
unsigned startStackHeight = m_context.stackHeight();
codeGen.assemble(
m_context.nonConstAssembly(),
[&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
return false;
Declaration const* decl = ref->second;
solAssert(!!decl, "");
if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
assembly::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
return size_t(-1);
return ref->second.valueSize;
};
identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), "");
Declaration const* decl = ref->second.declaration;
solAssert(!!decl, "");
if (_context == assembly::IdentifierContext::RValue)
{
int const depositBefore = _assembly.deposit();
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
// If there is a runtime context, we have to merge both labels into the same
// stack slot in case we store it in storage.
if (CompilerContext* rtc = m_context.runtimeContext())
{
functionDef = &m_context.resolveVirtualFunction(*functionDef);
_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
// If there is a runtime context, we have to merge both labels into the same
// stack slot in case we store it in storage.
if (CompilerContext* rtc = m_context.runtimeContext())
{
_assembly.append(u256(1) << 32);
_assembly.append(Instruction::MUL);
_assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
_assembly.append(Instruction::OR);
}
_assembly.append(u256(1) << 32);
_assembly.append(Instruction::MUL);
_assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
_assembly.append(Instruction::OR);
}
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
}
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
{
solAssert(!variable->isConstant(), "");
if (m_context.isStateVariable(decl))
{
solAssert(!variable->isConstant(), "");
if (m_context.isLocalVariable(variable))
{
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i)
_assembly.append(dupInstruction(stackDiff));
}
auto const& location = m_context.storageLocationOfVariable(*decl);
if (ref->second.isSlot)
m_context << location.first;
else if (ref->second.isOffset)
m_context << u256(location.second);
else
solAssert(false, "");
}
else if (m_context.isLocalVariable(decl))
{
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
if (ref->second.isSlot || ref->second.isOffset)
{
solAssert(m_context.isStateVariable(variable), "Invalid variable type.");
auto const& location = m_context.storageLocationOfVariable(*variable);
if (!variable->type()->isValueType())
solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
unsigned size = variable->type()->sizeOnStack();
if (size == 2)
{
solAssert(location.second == 0, "Intra-slot offest assumed to be zero.");
_assembly.append(location.first);
// slot plus offset
if (ref->second.isOffset)
stackDiff--;
}
else
{
_assembly.append(location.first);
_assembly.append(u256(location.second));
solAssert(size == 1, "");
// only slot, offset is zero
if (ref->second.isOffset)
{
_assembly.append(u256(0));
return;
}
}
}
}
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
{
solAssert(contract->isLibrary(), "");
_assembly.appendLibraryAddress(contract->fullyQualifiedName());
else
solAssert(variable->type()->sizeOnStack() == 1, "");
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
solAssert(variable->type()->sizeOnStack() == 1, "");
_assembly.append(dupInstruction(stackDiff));
}
else
solAssert(false, "Invalid declaration type.");
} else {
// lvalue context
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
solAssert(
!!variable && m_context.isLocalVariable(variable),
"Can only assign to stack variables in inline assembly."
);
unsigned size = variable->type()->sizeOnStack();
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size;
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
for (unsigned i = 0; i < size; ++i) {
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
}
solAssert(false, "");
}
return true;
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
solAssert(contract->isLibrary(), "");
_assembly.appendLibraryAddress(contract->fullyQualifiedName());
}
else
solAssert(false, "Invalid declaration type.");
solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), "");
}
else
{
// lvalue context
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
solAssert(
!!variable && m_context.isLocalVariable(variable),
"Can only assign to stack variables in inline assembly."
);
solAssert(variable->type()->sizeOnStack() == 1, "");
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1;
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
}
};
solAssert(_inlineAssembly.annotation().analysisInfo, "");
codeGen.assemble(
_inlineAssembly.operations(),
*_inlineAssembly.annotation().analysisInfo,
m_context.nonConstAssembly(),
identifierAccess
);
solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested.");
m_context.setStackOffset(startStackHeight);

View File

@ -21,6 +21,9 @@
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmScopeFiller.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Utils.h>
@ -35,146 +38,363 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
bool Scope::registerLabel(string const& _name)
AsmAnalyzer::AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo,
ErrorList& _errors,
ExternalIdentifierAccess::Resolver const& _resolver
):
m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors)
{
if (exists(_name))
}
bool AsmAnalyzer::analyze(Block const& _block)
{
if (!(ScopeFiller(m_info.scopes, m_errors))(_block))
return false;
identifiers[_name] = Label();
return (*this)(_block);
}
bool AsmAnalyzer::operator()(Label const& _label)
{
m_info.stackHeightInfo[&_label] = m_stackHeight;
return true;
}
bool Scope::registerVariable(string const& _name)
bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{
if (exists(_name))
return false;
identifiers[_name] = Variable();
auto const& info = instructionInfo(_instruction.instruction);
m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instruction] = m_stackHeight;
return true;
}
bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns)
{
if (exists(_name))
return false;
identifiers[_name] = Function(_arguments, _returns);
return true;
}
Scope::Identifier* Scope::lookup(string const& _name)
{
if (identifiers.count(_name))
return &identifiers[_name];
else if (superScope && !closedScope)
return superScope->lookup(_name);
else
return nullptr;
}
bool Scope::exists(string const& _name)
{
if (identifiers.count(_name))
return true;
else if (superScope)
return superScope->exists(_name);
else
return false;
}
AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::Scopes& _scopes, ErrorList& _errors):
m_scopes(_scopes), m_errors(_errors)
{
// Make the Solidity ErrorTag available to inline assembly
m_scopes[nullptr] = make_shared<Scope>();
Scope::Label errorLabel;
errorLabel.id = Scope::Label::errorLabelId;
m_scopes[nullptr]->identifiers["invalidJumpLabel"] = errorLabel;
m_currentScope = m_scopes[nullptr].get();
}
bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
{
++m_stackHeight;
if (!_literal.isNumber && _literal.value.size() > 32)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)"
"String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)",
_literal.location
));
return false;
}
m_info.stackHeightInfo[&_literal] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
size_t numErrorsBefore = m_errors.size();
bool success = true;
if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
[&](Scope::Variable const& _var)
{
if (!_var.active)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable " + _identifier.name + " used before it was declared.",
_identifier.location
));
success = false;
}
++m_stackHeight;
},
[&](Scope::Label const&)
{
++m_stackHeight;
},
[&](Scope::Function const&)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Function " + _identifier.name + " used without being called.",
_identifier.location
));
success = false;
}
)))
{
}
else
{
size_t stackSize(-1);
if (m_resolver)
stackSize = m_resolver(_identifier, IdentifierContext::RValue);
if (stackSize == size_t(-1))
{
// Only add an error message if the callback did not do it.
if (numErrorsBefore == m_errors.size())
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Identifier not found.",
_identifier.location
));
success = false;
}
m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
}
m_info.stackHeightInfo[&_identifier] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{
bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
{
int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg))
success = false;
if (!expectDeposit(1, stackHeight, locationOf(arg)))
success = false;
}
// Parser already checks that the number of arguments is correct.
solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), "");
if (!(*this)(_instr.instruction))
success = false;
m_info.stackHeightInfo[&_instr] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(Label const& _item)
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{
if (!m_currentScope->registerLabel(_item.name))
{
//@TODO secondary location
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Label name " + _item.name + " already taken in this scope.",
_item.location
));
return false;
}
return true;
bool success = checkAssignment(_assignment.variableName, size_t(-1));
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
{
return boost::apply_visitor(*this, *_assignment.value);
int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_assignment.value);
solAssert(m_stackHeight >= stackHeight, "Negative value size.");
if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight))
success = false;
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
{
int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_varDecl.value);
if (!m_currentScope->registerVariable(_varDecl.name))
{
//@TODO secondary location
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable name " + _varDecl.name + " already taken in this scope.",
_varDecl.location
));
success = false;
}
solAssert(m_stackHeight - stackHeight == 1, "Invalid value size.");
boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true;
m_info.stackHeightInfo[&_varDecl] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(assembly::FunctionDefinition const&)
bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
{
// TODO - we cannot throw an exception here because of some tests.
return true;
Scope& bodyScope = scope(&_funDef.body);
for (auto const& var: _funDef.arguments + _funDef.returns)
boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true;
int const stackHeight = m_stackHeight;
m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
m_virtualVariablesInNextBlock = m_stackHeight;
bool success = (*this)(_funDef.body);
m_stackHeight = stackHeight;
m_info.stackHeightInfo[&_funDef] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(assembly::FunctionCall const&)
bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
{
// TODO - we cannot throw an exception here because of some tests.
return true;
bool success = true;
size_t arguments = 0;
size_t returns = 0;
if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor(
[&](Scope::Variable const&)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Attempt to call variable instead of function.",
_funCall.functionName.location
));
success = false;
},
[&](Scope::Label const&)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Attempt to call label instead of function.",
_funCall.functionName.location
));
success = false;
},
[&](Scope::Function const& _fun)
{
arguments = _fun.arguments;
returns = _fun.returns;
}
)))
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Function not found.",
_funCall.functionName.location
));
success = false;
}
if (success)
{
if (_funCall.arguments.size() != arguments)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Expected " +
boost::lexical_cast<string>(arguments) +
" arguments but got " +
boost::lexical_cast<string>(_funCall.arguments.size()) +
".",
_funCall.functionName.location
));
success = false;
}
}
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
{
int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg))
success = false;
if (!expectDeposit(1, stackHeight, locationOf(arg)))
success = false;
}
m_stackHeight += int(returns) - int(arguments);
m_info.stackHeightInfo[&_funCall] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(Block const& _block)
{
bool success = true;
auto scope = make_shared<Scope>();
scope->superScope = m_currentScope;
m_scopes[&_block] = scope;
m_currentScope = scope.get();
m_currentScope = &scope(&_block);
int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock;
m_virtualVariablesInNextBlock = 0;
for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s))
success = false;
for (auto const& identifier: scope(&_block).identifiers)
if (identifier.second.type() == typeid(Scope::Variable))
--m_stackHeight;
int const stackDiff = m_stackHeight - initialStackHeight;
if (stackDiff != 0)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Unbalanced stack at the end of a block: " +
(
stackDiff > 0 ?
to_string(stackDiff) + string(" surplus item(s).") :
to_string(-stackDiff) + string(" missing item(s).")
),
_block.location
));
success = false;
}
m_currentScope = m_currentScope->superScope;
m_info.stackHeightInfo[&_block] = m_stackHeight;
return success;
}
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
{
bool success = true;
size_t numErrorsBefore = m_errors.size();
size_t variableSize(-1);
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
{
// Check that it is a variable
if (var->type() != typeid(Scope::Variable))
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Assignment requires variable.",
_variable.location
));
success = false;
}
else if (!boost::get<Scope::Variable>(*var).active)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable " + _variable.name + " used before it was declared.",
_variable.location
));
success = false;
}
variableSize = 1;
}
else if (m_resolver)
variableSize = m_resolver(_variable, IdentifierContext::LValue);
if (variableSize == size_t(-1))
{
// Only add message if the callback did not.
if (numErrorsBefore == m_errors.size())
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable not found or variable not lvalue.",
_variable.location
));
success = false;
}
if (_valueSize == size_t(-1))
_valueSize = variableSize == size_t(-1) ? 1 : variableSize;
m_stackHeight -= _valueSize;
if (_valueSize != variableSize && variableSize != size_t(-1))
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Variable size (" +
to_string(variableSize) +
") and value size (" +
to_string(_valueSize) +
") do not match.",
_variable.location
));
success = false;
}
return success;
}
bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location)
{
int stackDiff = m_stackHeight - _oldHeight;
if (stackDiff != _deposit)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Expected instruction(s) to deposit " +
boost::lexical_cast<string>(_deposit) +
" item(s) to the stack, but did deposit " +
boost::lexical_cast<string>(stackDiff) +
" item(s).",
_location
));
return false;
}
else
return true;
}
Scope& AsmAnalyzer::scope(Block const* _block)
{
auto scopePtr = m_info.scopes.at(_block);
solAssert(scopePtr, "Scope requested but not present.");
return *scopePtr;
}

View File

@ -20,6 +20,8 @@
#pragma once
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
@ -46,101 +48,32 @@ struct Assignment;
struct FunctionDefinition;
struct FunctionCall;
template <class...>
struct GenericVisitor{};
template <class Visitable, class... Others>
struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
{
using GenericVisitor<Others...>::operator ();
explicit GenericVisitor(
std::function<void(Visitable&)> _visitor,
std::function<void(Others&)>... _otherVisitors
):
GenericVisitor<Others...>(_otherVisitors...),
m_visitor(_visitor)
{}
void operator()(Visitable& _v) const { m_visitor(_v); }
std::function<void(Visitable&)> m_visitor;
};
template <>
struct GenericVisitor<>: public boost::static_visitor<> {
void operator()() const {}
};
struct Scope
{
struct Variable
{
int stackHeight = 0;
bool active = false;
};
struct Label
{
size_t id = unassignedLabelId;
static const size_t errorLabelId = -1;
static const size_t unassignedLabelId = 0;
};
struct Function
{
Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {}
size_t arguments = 0;
size_t returns = 0;
};
using Identifier = boost::variant<Variable, Label, Function>;
using Visitor = GenericVisitor<Variable const, Label const, Function const>;
using NonconstVisitor = GenericVisitor<Variable, Label, Function>;
bool registerVariable(std::string const& _name);
bool registerLabel(std::string const& _name);
bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns);
/// Looks up the identifier in this or super scopes (stops and function and assembly boundaries)
/// and returns a valid pointer if found or a nullptr if not found.
/// The pointer will be invalidated if the scope is modified.
Identifier* lookup(std::string const& _name);
/// Looks up the identifier in this and super scopes (stops and function and assembly boundaries)
/// and calls the visitor, returns false if not found.
template <class V>
bool lookup(std::string const& _name, V const& _visitor)
{
if (Identifier* id = lookup(_name))
{
boost::apply_visitor(_visitor, *id);
return true;
}
else
return false;
}
/// @returns true if the name exists in this scope or in super scopes (also searches
/// across function and assembly boundaries).
bool exists(std::string const& _name);
Scope* superScope = nullptr;
/// If true, identifiers from the super scope are not visible here, but they are still
/// taken into account to prevent shadowing.
bool closedScope = false;
std::map<std::string, Identifier> identifiers;
};
struct Scope;
struct AsmAnalysisInfo;
/**
* Performs the full analysis stage, calls the ScopeFiller internally, then resolves
* references and performs other checks.
* If all these checks pass, code generation should not throw errors.
*/
class AsmAnalyzer: public boost::static_visitor<bool>
{
public:
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
AsmAnalyzer(Scopes& _scopes, ErrorList& _errors);
AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo,
ErrorList& _errors,
ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver()
);
bool operator()(assembly::Instruction const&) { return true; }
bool analyze(assembly::Block const& _block);
bool operator()(assembly::Instruction const&);
bool operator()(assembly::Literal const& _literal);
bool operator()(assembly::Identifier const&) { return true; }
bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
bool operator()(assembly::Label const& _label);
bool operator()(assembly::Assignment const&) { return true; }
bool operator()(assembly::Assignment const&);
bool operator()(assembly::FunctionalAssignment const& _functionalAssignment);
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
@ -148,8 +81,20 @@ public:
bool operator()(assembly::Block const& _block);
private:
/// Verifies that a variable to be assigned to exists and has the same size
/// as the value, @a _valueSize, unless that is equal to -1.
bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1));
bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
Scope& scope(assembly::Block const* _block);
/// This is used when we enter the body of a function definition. There, the parameters
/// and return parameters appear as variables which are already on the stack before
/// we enter the block.
int m_virtualVariablesInNextBlock = 0;
int m_stackHeight = 0;
ExternalIdentifierAccess::Resolver const& m_resolver;
Scope* m_currentScope = nullptr;
Scopes& m_scopes;
AsmAnalysisInfo& m_info;
ErrorList& m_errors;
};

View File

@ -0,0 +1,26 @@
/*
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/>.
*/
/**
* Information generated during analyzer part of inline assembly.
*/
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <ostream>

View File

@ -0,0 +1,61 @@
/*
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/>.
*/
/**
* Information generated during analyzer part of inline assembly.
*/
#pragma once
#include <boost/variant.hpp>
#include <map>
#include <memory>
namespace dev
{
namespace solidity
{
namespace assembly
{
struct Literal;
struct Block;
struct Label;
struct FunctionalInstruction;
struct FunctionalAssignment;
struct VariableDeclaration;
struct Instruction;
struct Identifier;
struct Assignment;
struct FunctionDefinition;
struct FunctionCall;
struct Scope;
using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>;
struct AsmAnalysisInfo
{
using StackHeightInfo = std::map<void const*, int>;
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
Scopes scopes;
StackHeightInfo stackHeightInfo;
};
}
}
}

View File

@ -24,7 +24,9 @@
#include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/SourceLocation.h>
@ -46,13 +48,8 @@ using namespace dev::solidity::assembly;
struct GeneratorState
{
GeneratorState(ErrorList& _errors, eth::Assembly& _assembly):
errors(_errors), assembly(_assembly) {}
void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation())
{
errors.push_back(make_shared<Error>(_type, _description, _location));
}
GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly):
errors(_errors), info(_analysisInfo), assembly(_assembly) {}
size_t newLabelId()
{
@ -66,8 +63,8 @@ struct GeneratorState
return size_t(id);
}
std::map<assembly::Block const*, shared_ptr<Scope>> scopes;
ErrorList& errors;
AsmAnalysisInfo info;
eth::Assembly& assembly;
};
@ -80,13 +77,24 @@ public:
explicit CodeTransform(
GeneratorState& _state,
assembly::Block const& _block,
assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess()
assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess()
): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit())
{
}
private:
CodeTransform(
GeneratorState& _state,
assembly::Block const& _block,
assembly::ExternalIdentifierAccess const& _identifierAccess,
int _initialDeposit
):
m_state(_state),
m_scope(*m_state.scopes.at(&_block)),
m_initialDeposit(m_state.assembly.deposit()),
m_identifierAccess(_identifierAccess)
m_scope(*m_state.info.scopes.at(&_block)),
m_identifierAccess(_identifierAccess),
m_initialDeposit(_initialDeposit)
{
int blockStartDeposit = m_state.assembly.deposit();
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
m_state.assembly.setSourceLocation(_block.location);
@ -96,31 +104,16 @@ public:
if (identifier.second.type() == typeid(Scope::Variable))
m_state.assembly.append(solidity::Instruction::POP);
int deposit = m_state.assembly.deposit() - m_initialDeposit;
// issue warnings for stack height discrepancies
if (deposit < 0)
{
m_state.addError(
Error::Type::Warning,
"Inline assembly block is not balanced. It takes " + toString(-deposit) + " item(s) from the stack.",
_block.location
);
}
else if (deposit > 0)
{
m_state.addError(
Error::Type::Warning,
"Inline assembly block is not balanced. It leaves " + toString(deposit) + " item(s) on the stack.",
_block.location
);
}
int deposit = m_state.assembly.deposit() - blockStartDeposit;
solAssert(deposit == 0, "Invalid stack height at end of block.");
}
public:
void operator()(assembly::Instruction const& _instruction)
{
m_state.assembly.setSourceLocation(_instruction.location);
m_state.assembly.append(_instruction.instruction);
checkStackHeight(&_instruction);
}
void operator()(assembly::Literal const& _literal)
{
@ -132,6 +125,7 @@ public:
solAssert(_literal.value.size() <= 32, "");
m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
}
checkStackHeight(&_literal);
}
void operator()(assembly::Identifier const& _identifier)
{
@ -153,20 +147,18 @@ public:
},
[=](Scope::Function&)
{
solAssert(false, "Not yet implemented");
solAssert(false, "Function not removed during desugaring.");
}
)))
{
return;
}
else if (!m_identifierAccess || !m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue))
{
m_state.addError(
Error::Type::DeclarationError,
"Identifier not found or not unique",
_identifier.location
);
m_state.assembly.append(u256(0));
}
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly);
checkStackHeight(&_identifier);
}
void operator()(FunctionalInstruction const& _instr)
{
@ -174,9 +166,10 @@ public:
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *it);
expectDeposit(1, height, locationOf(*it));
expectDeposit(1, height);
}
(*this)(_instr.instruction);
checkStackHeight(&_instr);
}
void operator()(assembly::FunctionCall const&)
{
@ -186,36 +179,39 @@ public:
{
m_state.assembly.setSourceLocation(_label.location);
solAssert(m_scope.identifiers.count(_label.name), "");
Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers[_label.name]);
Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name));
assignLabelIdIfUnset(label);
m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id));
checkStackHeight(&_label);
}
void operator()(assembly::Assignment const& _assignment)
{
m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void operator()(FunctionalAssignment const& _assignment)
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_assignment.value);
expectDeposit(1, height, locationOf(*_assignment.value));
expectDeposit(1, height);
m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void operator()(assembly::VariableDeclaration const& _varDecl)
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(1, height, locationOf(*_varDecl.value));
solAssert(m_scope.identifiers.count(_varDecl.name), "");
auto& var = boost::get<Scope::Variable>(m_scope.identifiers[_varDecl.name]);
expectDeposit(1, height);
auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name));
var.stackHeight = height;
var.active = true;
}
void operator()(assembly::Block const& _block)
{
CodeTransform(m_state, _block, m_identifierAccess);
CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit);
checkStackHeight(&_block);
}
void operator()(assembly::FunctionDefinition const&)
{
@ -225,35 +221,22 @@ public:
private:
void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location)
{
if (m_scope.lookup(_variableName.name, Scope::Visitor(
[=](Scope::Variable const& _var)
{
if (int heightDiff = variableHeightDiff(_var, _location, true))
m_state.assembly.append(solidity::swapInstruction(heightDiff - 1));
m_state.assembly.append(solidity::Instruction::POP);
},
[=](Scope::Label const&)
{
m_state.addError(
Error::Type::DeclarationError,
"Label \"" + string(_variableName.name) + "\" used as variable."
);
},
[=](Scope::Function const&)
{
m_state.addError(
Error::Type::DeclarationError,
"Function \"" + string(_variableName.name) + "\" used as variable."
);
}
)))
auto var = m_scope.lookup(_variableName.name);
if (var)
{
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
if (int heightDiff = variableHeightDiff(_var, _location, true))
m_state.assembly.append(solidity::swapInstruction(heightDiff - 1));
m_state.assembly.append(solidity::Instruction::POP);
}
else if (!m_identifierAccess || !m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue))
m_state.addError(
Error::Type::DeclarationError,
"Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue."
else
{
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly);
}
}
/// Determines the stack height difference to the given variables. Automatically generates
@ -261,36 +244,33 @@ private:
/// errors and the (positive) stack height difference otherwise.
int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap)
{
if (!_var.active)
{
m_state.addError( Error::Type::TypeError, "Variable used before it was declared", _location);
return 0;
}
int heightDiff = m_state.assembly.deposit() - _var.stackHeight;
if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
{
m_state.addError(
//@TODO move this to analysis phase.
m_state.errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
_location
);
));
return 0;
}
else
return heightDiff;
}
void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
void expectDeposit(int _deposit, int _oldHeight)
{
if (m_state.assembly.deposit() != _oldHeight + 1)
m_state.addError(Error::Type::TypeError,
"Expected instruction(s) to deposit " +
boost::lexical_cast<string>(_deposit) +
" item(s) to the stack, but did deposit " +
boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) +
" item(s).",
_location
);
solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit.");
}
void checkStackHeight(void const* _astElement)
{
solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
solAssert(
m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit,
"Stack height mismatch between analysis and code generation phase."
);
}
/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
@ -305,35 +285,29 @@ private:
GeneratorState& m_state;
Scope& m_scope;
ExternalIdentifierAccess m_identifierAccess;
int const m_initialDeposit;
assembly::CodeGenerator::IdentifierAccess m_identifierAccess;
};
bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
{
size_t initialErrorLen = m_errors.size();
eth::Assembly assembly;
GeneratorState state(m_errors, assembly);
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
return false;
CodeTransform(state, m_parsedData, _identifierAccess);
return m_errors.size() == initialErrorLen;
}
eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
eth::Assembly assembly::CodeGenerator::assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
ExternalIdentifierAccess const& _identifierAccess
)
{
eth::Assembly assembly;
GeneratorState state(m_errors, assembly);
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
solAssert(false, "Assembly error");
CodeTransform(state, m_parsedData, _identifierAccess);
GeneratorState state(m_errors, _analysisInfo, assembly);
CodeTransform(state, _parsedData, _identifierAccess);
return assembly;
}
void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
void assembly::CodeGenerator::assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
eth::Assembly& _assembly,
ExternalIdentifierAccess const& _identifierAccess
)
{
GeneratorState state(m_errors, _assembly);
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
solAssert(false, "Assembly error");
CodeTransform(state, m_parsedData, _identifierAccess);
GeneratorState state(m_errors, _analysisInfo, _assembly);
CodeTransform(state, _parsedData, _identifierAccess);
}

View File

@ -22,9 +22,11 @@
#pragma once
#include <functional>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/interface/Exceptions.h>
#include <functional>
namespace dev
{
namespace eth
@ -36,30 +38,27 @@ namespace solidity
namespace assembly
{
struct Block;
struct Identifier;
class CodeGenerator
{
public:
enum class IdentifierContext { LValue, RValue };
/// Function type that is called for external identifiers. Such a function should search for
/// the identifier and append appropriate assembly items to the assembly. If in lvalue context,
/// the value to assign is assumed to be on the stack and an assignment is to be performed.
/// If in rvalue context, the function is assumed to append instructions to
/// push the value of the identifier onto the stack. On error, the function should return false.
using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>;
CodeGenerator(Block const& _parsedData, ErrorList& _errors):
m_parsedData(_parsedData), m_errors(_errors) {}
/// Performs type checks and @returns false on error.
/// Actually runs the full code generation but discards the result.
bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess());
CodeGenerator(ErrorList& _errors):
m_errors(_errors) {}
/// Performs code generation and @returns the result.
eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess());
eth::Assembly assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
);
/// Performs code generation and appends generated to to _assembly.
void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess());
void assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
eth::Assembly& _assembly,
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
);
private:
Block const& m_parsedData;
ErrorList& m_errors;
};

View File

@ -0,0 +1,79 @@
/*
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/>.
*/
/**
* Scopes for identifiers.
*/
#include <libsolidity/inlineasm/AsmScope.h>
using namespace std;
using namespace dev::solidity::assembly;
bool Scope::registerLabel(string const& _name)
{
if (exists(_name))
return false;
identifiers[_name] = Label();
return true;
}
bool Scope::registerVariable(string const& _name)
{
if (exists(_name))
return false;
identifiers[_name] = Variable();
return true;
}
bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns)
{
if (exists(_name))
return false;
identifiers[_name] = Function(_arguments, _returns);
return true;
}
Scope::Identifier* Scope::lookup(string const& _name)
{
bool crossedFunctionBoundary = false;
for (Scope* s = this; s; s = s->superScope)
{
auto id = s->identifiers.find(_name);
if (id != s->identifiers.end())
{
if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable))
return nullptr;
else
return &id->second;
}
if (s->functionScope)
crossedFunctionBoundary = true;
}
return nullptr;
}
bool Scope::exists(string const& _name)
{
if (identifiers.count(_name))
return true;
else if (superScope)
return superScope->exists(_name);
else
return false;
}

View File

@ -0,0 +1,128 @@
/*
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/>.
*/
/**
* Scopes for identifiers.
*/
#pragma once
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
#include <functional>
#include <memory>
namespace dev
{
namespace solidity
{
namespace assembly
{
template <class...>
struct GenericVisitor{};
template <class Visitable, class... Others>
struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
{
using GenericVisitor<Others...>::operator ();
explicit GenericVisitor(
std::function<void(Visitable&)> _visitor,
std::function<void(Others&)>... _otherVisitors
):
GenericVisitor<Others...>(_otherVisitors...),
m_visitor(_visitor)
{}
void operator()(Visitable& _v) const { m_visitor(_v); }
std::function<void(Visitable&)> m_visitor;
};
template <>
struct GenericVisitor<>: public boost::static_visitor<> {
void operator()() const {}
};
struct Scope
{
struct Variable
{
/// Used during code generation to store the stack height. @todo move there.
int stackHeight = 0;
/// Used during analysis to check whether we already passed the declaration inside the block.
/// @todo move there.
bool active = false;
};
struct Label
{
size_t id = unassignedLabelId;
static const size_t errorLabelId = -1;
static const size_t unassignedLabelId = 0;
};
struct Function
{
Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {}
size_t arguments = 0;
size_t returns = 0;
};
using Identifier = boost::variant<Variable, Label, Function>;
using Visitor = GenericVisitor<Variable const, Label const, Function const>;
using NonconstVisitor = GenericVisitor<Variable, Label, Function>;
bool registerVariable(std::string const& _name);
bool registerLabel(std::string const& _name);
bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns);
/// Looks up the identifier in this or super scopes and returns a valid pointer if found
/// or a nullptr if not found. Variable lookups up across function boundaries will fail, as
/// will any lookups across assembly boundaries.
/// The pointer will be invalidated if the scope is modified.
/// @param _crossedFunction if true, we already crossed a function boundary during recursive lookup
Identifier* lookup(std::string const& _name);
/// Looks up the identifier in this and super scopes (will not find variables across function
/// boundaries and generally stops at assembly boundaries) and calls the visitor, returns
/// false if not found.
template <class V>
bool lookup(std::string const& _name, V const& _visitor)
{
if (Identifier* id = lookup(_name))
{
boost::apply_visitor(_visitor, *id);
return true;
}
else
return false;
}
/// @returns true if the name exists in this scope or in super scopes (also searches
/// across function and assembly boundaries).
bool exists(std::string const& _name);
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.
bool functionScope = false;
std::map<std::string, Identifier> identifiers;
};
}
}
}

View File

@ -0,0 +1,130 @@
/*
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/>.
*/
/**
* Module responsible for registering identifiers inside their scopes.
*/
#include <libsolidity/inlineasm/AsmScopeFiller.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Utils.h>
#include <boost/range/adaptor/reversed.hpp>
#include <memory>
#include <functional>
using namespace std;
using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
ScopeFiller::ScopeFiller(ScopeFiller::Scopes& _scopes, ErrorList& _errors):
m_scopes(_scopes), m_errors(_errors)
{
// Make the Solidity ErrorTag available to inline assembly
Scope::Label errorLabel;
errorLabel.id = Scope::Label::errorLabelId;
scope(nullptr).identifiers["invalidJumpLabel"] = errorLabel;
m_currentScope = &scope(nullptr);
}
bool ScopeFiller::operator()(Label const& _item)
{
if (!m_currentScope->registerLabel(_item.name))
{
//@TODO secondary location
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Label name " + _item.name + " already taken in this scope.",
_item.location
));
return false;
}
return true;
}
bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
{
return registerVariable(_varDecl.name, _varDecl.location, *m_currentScope);
}
bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
{
bool success = true;
if (!m_currentScope->registerFunction(_funDef.name, _funDef.arguments.size(), _funDef.returns.size()))
{
//@TODO secondary location
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Function name " + _funDef.name + " already taken in this scope.",
_funDef.location
));
success = false;
}
Scope& body = scope(&_funDef.body);
body.superScope = m_currentScope;
body.functionScope = true;
for (auto const& var: _funDef.arguments + _funDef.returns)
if (!registerVariable(var, _funDef.location, body))
success = false;
if (!(*this)(_funDef.body))
success = false;
return success;
}
bool ScopeFiller::operator()(Block const& _block)
{
bool success = true;
scope(&_block).superScope = m_currentScope;
m_currentScope = &scope(&_block);
for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s))
success = false;
m_currentScope = m_currentScope->superScope;
return success;
}
bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope)
{
if (!_scope.registerVariable(_name))
{
//@TODO secondary location
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable name " + _name + " already taken in this scope.",
_location
));
return false;
}
return true;
}
Scope& ScopeFiller::scope(Block const* _block)
{
auto& scope = m_scopes[_block];
if (!scope)
scope = make_shared<Scope>();
return *scope;
}

View File

@ -0,0 +1,89 @@
/*
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/>.
*/
/**
* Module responsible for registering identifiers inside their scopes.
*/
#pragma once
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
#include <functional>
#include <memory>
namespace dev
{
namespace solidity
{
namespace assembly
{
struct Literal;
struct Block;
struct Label;
struct FunctionalInstruction;
struct FunctionalAssignment;
struct VariableDeclaration;
struct Instruction;
struct Identifier;
struct Assignment;
struct FunctionDefinition;
struct FunctionCall;
struct Scope;
/**
* Fills scopes with identifiers and checks for name clashes.
* Does not resolve references.
*/
class ScopeFiller: public boost::static_visitor<bool>
{
public:
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
ScopeFiller(Scopes& _scopes, ErrorList& _errors);
bool operator()(assembly::Instruction const&) { return true; }
bool operator()(assembly::Literal const&) { return true; }
bool operator()(assembly::Identifier const&) { return true; }
bool operator()(assembly::FunctionalInstruction const&) { return true; }
bool operator()(assembly::Label const& _label);
bool operator()(assembly::Assignment const&) { return true; }
bool operator()(assembly::FunctionalAssignment const&) { return true; }
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const&) { return true; }
bool operator()(assembly::Block const& _block);
private:
bool registerVariable(
std::string const& _name,
SourceLocation const& _location,
Scope& _scope
);
Scope& scope(assembly::Block const* _block);
Scope* m_currentScope = nullptr;
Scopes& m_scopes;
ErrorList& m_errors;
};
}
}
}

View File

@ -26,6 +26,7 @@
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/inlineasm/AsmPrinter.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/parsing/Scanner.h>
@ -39,7 +40,10 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
bool InlineAssemblyStack::parse(
shared_ptr<Scanner> const& _scanner,
ExternalIdentifierAccess::Resolver const& _resolver
)
{
m_parserResult = make_shared<Block>();
Parser parser(m_errors);
@ -48,8 +52,8 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
return false;
*m_parserResult = std::move(*result);
AsmAnalyzer::Scopes scopes;
return (AsmAnalyzer(scopes, m_errors))(*m_parserResult);
AsmAnalysisInfo analysisInfo;
return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult);
}
string InlineAssemblyStack::toString()
@ -59,14 +63,17 @@ string InlineAssemblyStack::toString()
eth::Assembly InlineAssemblyStack::assemble()
{
CodeGenerator codeGen(*m_parserResult, m_errors);
return codeGen.assemble();
AsmAnalysisInfo analysisInfo;
AsmAnalyzer analyzer(analysisInfo, m_errors);
solAssert(analyzer.analyze(*m_parserResult), "");
CodeGenerator codeGen(m_errors);
return codeGen.assemble(*m_parserResult, analysisInfo);
}
bool InlineAssemblyStack::parseAndAssemble(
string const& _input,
eth::Assembly& _assembly,
CodeGenerator::IdentifierAccess const& _identifierAccess
ExternalIdentifierAccess const& _identifierAccess
)
{
ErrorList errors;
@ -74,8 +81,12 @@ bool InlineAssemblyStack::parseAndAssemble(
auto parserResult = Parser(errors).parse(scanner);
if (!errors.empty())
return false;
solAssert(parserResult, "");
CodeGenerator(*parserResult, errors).assemble(_assembly, _identifierAccess);
AsmAnalysisInfo analysisInfo;
AsmAnalyzer analyzer(analysisInfo, errors, _identifierAccess.resolve);
solAssert(analyzer.analyze(*parserResult), "");
CodeGenerator(errors).assemble(*parserResult, analysisInfo, _assembly, _identifierAccess);
// At this point, the assembly might be messed up, but we should throw an
// internal compiler error anyway.

View File

@ -22,10 +22,10 @@
#pragma once
#include <libsolidity/interface/Exceptions.h>
#include <string>
#include <functional>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
namespace dev
{
@ -39,13 +39,34 @@ class Scanner;
namespace assembly
{
struct Block;
struct Identifier;
enum class IdentifierContext { LValue, RValue };
/// Object that is used to resolve references and generate code for access to identifiers external
/// to inline assembly (not used in standalone assembly mode).
struct ExternalIdentifierAccess
{
using Resolver = std::function<size_t(assembly::Identifier const&, IdentifierContext)>;
/// 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;
using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>;
/// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context)
/// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed
/// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack.
CodeGenerator generateCode;
};
class InlineAssemblyStack
{
public:
/// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`.
/// @return false or error.
bool parse(std::shared_ptr<Scanner> const& _scanner);
bool parse(
std::shared_ptr<Scanner> const& _scanner,
ExternalIdentifierAccess::Resolver const& _externalIdentifierResolver = ExternalIdentifierAccess::Resolver()
);
/// Converts the parser result back into a string form (not necessarily the same form
/// as the source form, but it should parse into the same parsed form again).
std::string toString();
@ -56,7 +77,7 @@ public:
bool parseAndAssemble(
std::string const& _input,
eth::Assembly& _assembly,
CodeGenerator::IdentifierAccess const& _identifierAccess = CodeGenerator::IdentifierAccess()
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
);
ErrorList const& errors() const { return m_errors; }

View File

@ -30,6 +30,7 @@
#include <libevmasm/Assembly.h>
#include <boost/optional.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <string>
#include <memory>
@ -63,7 +64,7 @@ boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _ass
}
if (!success)
{
BOOST_CHECK_EQUAL(stack.errors().size(), 1);
BOOST_REQUIRE_EQUAL(stack.errors().size(), 1);
return *stack.errors().front();
}
else
@ -137,22 +138,22 @@ BOOST_AUTO_TEST_CASE(smoke_test)
BOOST_AUTO_TEST_CASE(simple_instructions)
{
BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub }"));
BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub pop }"));
}
BOOST_AUTO_TEST_CASE(suicide_selfdestruct)
{
BOOST_CHECK(successParse("{ suicide selfdestruct }"));
BOOST_CHECK(successParse("{ 0x01 suicide 0x02 selfdestruct }"));
}
BOOST_AUTO_TEST_CASE(keywords)
{
BOOST_CHECK(successParse("{ byte return address }"));
BOOST_CHECK(successParse("{ 1 2 byte 2 return address pop }"));
}
BOOST_AUTO_TEST_CASE(constants)
{
BOOST_CHECK(successParse("{ 7 8 mul }"));
BOOST_CHECK(successParse("{ 7 8 mul pop }"));
}
BOOST_AUTO_TEST_CASE(vardecl)
@ -162,37 +163,43 @@ BOOST_AUTO_TEST_CASE(vardecl)
BOOST_AUTO_TEST_CASE(assignment)
{
BOOST_CHECK(successParse("{ 7 8 add =: x }"));
BOOST_CHECK(successParse("{ let x := 2 7 8 add =: x }"));
}
BOOST_AUTO_TEST_CASE(label)
{
BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump }"));
BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump pop }"));
}
BOOST_AUTO_TEST_CASE(label_complex)
{
BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) }"));
BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop }"));
}
BOOST_AUTO_TEST_CASE(functional)
{
BOOST_CHECK(successParse("{ add(7, mul(6, x)) add mul(7, 8) }"));
BOOST_CHECK(successParse("{ let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }"));
}
BOOST_AUTO_TEST_CASE(functional_assignment)
{
BOOST_CHECK(successParse("{ x := 7 }"));
BOOST_CHECK(successParse("{ let x := 2 x := 7 }"));
}
BOOST_AUTO_TEST_CASE(functional_assignment_complex)
{
BOOST_CHECK(successParse("{ x := add(7, mul(6, x)) add mul(7, 8) }"));
BOOST_CHECK(successParse("{ let x := 2 x := add(7, mul(6, x)) mul(7, 8) add }"));
}
BOOST_AUTO_TEST_CASE(vardecl_complex)
{
BOOST_CHECK(successParse("{ let x := add(7, mul(6, x)) add mul(7, 8) }"));
BOOST_CHECK(successParse("{ let y := 2 let x := add(7, mul(6, y)) add mul(7, 8) }"));
}
BOOST_AUTO_TEST_CASE(variable_use_before_decl)
{
CHECK_PARSE_ERROR("{ x := 2 let x := 3 }", DeclarationError, "Variable x used before it was declared.");
CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared.");
}
BOOST_AUTO_TEST_CASE(blocks)
@ -212,7 +219,28 @@ BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
BOOST_AUTO_TEST_CASE(function_calls)
{
BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }"));
BOOST_CHECK(successParse("{ function f(a) -> (b) {} function g(a, b, c) {} function x() { g(1, 2, f(mul(2, 3))) x() } }"));
}
BOOST_AUTO_TEST_CASE(opcode_for_functions)
{
CHECK_PARSE_ERROR("{ function gas() { } }", ParserError, "Cannot use instruction names for identifier names.");
}
BOOST_AUTO_TEST_CASE(opcode_for_function_args)
{
CHECK_PARSE_ERROR("{ function f(gas) { } }", ParserError, "Cannot use instruction names for identifier names.");
CHECK_PARSE_ERROR("{ function f() -> (gas) { } }", ParserError, "Cannot use instruction names for identifier names.");
}
BOOST_AUTO_TEST_CASE(name_clashes)
{
CHECK_PARSE_ERROR("{ let g := 2 function g() { } }", DeclarationError, "Function name g already taken in this scope");
}
BOOST_AUTO_TEST_CASE(variable_access_cross_functions)
{
CHECK_PARSE_ERROR("{ let x := 2 function g() { x pop } }", DeclarationError, "Identifier not found.");
}
BOOST_AUTO_TEST_SUITE_END()
@ -226,7 +254,7 @@ BOOST_AUTO_TEST_CASE(print_smoke)
BOOST_AUTO_TEST_CASE(print_instructions)
{
parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n}");
parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n pop\n}");
}
BOOST_AUTO_TEST_CASE(print_subblock)
@ -236,7 +264,7 @@ BOOST_AUTO_TEST_CASE(print_subblock)
BOOST_AUTO_TEST_CASE(print_functional)
{
parsePrintCompare("{\n mul(sload(0x12), 7)\n}");
parsePrintCompare("{\n let x := mul(sload(0x12), 7)\n}");
}
BOOST_AUTO_TEST_CASE(print_label)
@ -251,13 +279,13 @@ BOOST_AUTO_TEST_CASE(print_assignments)
BOOST_AUTO_TEST_CASE(print_string_literals)
{
parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n}");
parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n pop\n}");
}
BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
{
string source = "{ \"\\u1bac\" }";
string parsed = "{\n \"\\xe1\\xae\\xac\"\n}";
string source = "{ let x := \"\\u1bac\" }";
string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}";
assembly::InlineAssemblyStack stack;
BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(source))));
BOOST_REQUIRE(stack.errors().empty());
@ -272,7 +300,21 @@ BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
BOOST_AUTO_TEST_CASE(function_calls)
{
parsePrintCompare("{\n g(1, mul(2, x), f(mul(2, 3)))\n x()\n}");
string source = R"({
function y()
{
}
function f(a) -> (b)
{
}
function g(a, b, c)
{
}
g(1, mul(2, address), f(mul(2, caller)))
y()
})";
boost::replace_all(source, "\t", " ");
parsePrintCompare(source);
}
BOOST_AUTO_TEST_SUITE_END()
@ -291,27 +333,32 @@ BOOST_AUTO_TEST_CASE(oversize_string_literals)
BOOST_AUTO_TEST_CASE(assignment_after_tag)
{
BOOST_CHECK(successParse("{ let x := 1 { tag: =: x } }"));
BOOST_CHECK(successParse("{ let x := 1 { 7 tag: =: x } }"));
}
BOOST_AUTO_TEST_CASE(magic_variables)
{
CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique");
CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique");
BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }"));
CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found");
CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found");
BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover pop }"));
}
BOOST_AUTO_TEST_CASE(stack_variables)
{
BOOST_CHECK(successAssemble("{ let y := 3 { 2 { let x := y } pop} }"));
}
BOOST_AUTO_TEST_CASE(imbalanced_stack)
{
BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false));
CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves");
CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes");
CHECK_ASSEMBLE_ERROR("{ 1 }", DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s).");
CHECK_ASSEMBLE_ERROR("{ pop }", DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s).");
BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false));
}
BOOST_AUTO_TEST_CASE(error_tag)
{
BOOST_CHECK(successAssemble("{ invalidJumpLabel }"));
BOOST_CHECK(successAssemble("{ jump(invalidJumpLabel) }"));
}
BOOST_AUTO_TEST_CASE(designated_invalid_instruction)

View File

@ -7426,18 +7426,52 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access)
uint16 x;
uint16 public y;
uint public z;
function f() {
// we know that z is aligned because it is too large, so we just discard its
// intra-slot offset value
assembly { 7 z pop sstore }
function f() returns (bool) {
uint off1;
uint off2;
assembly {
sstore(z_slot, 7)
off1 := z_offset
off2 := y_offset
}
assert(off1 == 0);
assert(off2 == 2);
return true;
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs());
BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer)
{
char const* sourceCode = R"(
contract C {
struct Data { uint contents; }
uint public separator;
Data public a;
uint public separator2;
function f() returns (bool) {
Data x = a;
uint off;
assembly {
sstore(x_slot, 7)
off := x_offset
}
assert(off == 0);
return true;
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(7)));
BOOST_CHECK(callContractFunction("separator()") == encodeArgs(u256(0)));
BOOST_CHECK(callContractFunction("separator2()") == encodeArgs(u256(0)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_jumps)
{
char const* sourceCode = R"(
@ -7474,6 +7508,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access)
assembly {
_x
jump(g)
pop
}
}
}

View File

@ -4997,7 +4997,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_positive_stack)
}
}
)";
CHECK_WARNING(text, "Inline assembly block is not balanced");
CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s).");
}
BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack)
@ -5011,7 +5011,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack)
}
}
)";
CHECK_WARNING(text, "Inline assembly block is not balanced");
CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s).");
}
BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load)
@ -5024,7 +5024,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load)
}
}
)";
CHECK_WARNING(text, "Inline assembly block is not balanced");
CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
}
BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier)
@ -5053,12 +5053,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage)
function f() {
assembly {
x := 2
pop
}
}
}
)";
CHECK_ERROR(text, DeclarationError, "not found, not unique or not lvalue.");
CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
@ -5069,7 +5068,6 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
modifier m {
assembly {
x := 2
pop
}
_;
}
@ -5077,7 +5075,37 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
}
}
)";
CHECK_ERROR(text, DeclarationError, "");
CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
}
BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign)
{
char const* text = R"(
contract test {
uint constant x = 1;
function f() {
assembly {
x := 2
}
}
}
)";
CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
}
BOOST_AUTO_TEST_CASE(inline_assembly_constant_access)
{
char const* text = R"(
contract test {
uint constant x = 1;
function f() {
assembly {
let y := x
}
}
}
)";
CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
}
BOOST_AUTO_TEST_CASE(invalid_mobile_type)