mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1711 from ethereum/asmfunctions
Assembly: Analysis stage for functions.
This commit is contained in:
commit
2c1fb46bc3
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.");
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
26
libsolidity/inlineasm/AsmAnalysisInfo.cpp
Normal file
26
libsolidity/inlineasm/AsmAnalysisInfo.cpp
Normal 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>
|
||||
|
61
libsolidity/inlineasm/AsmAnalysisInfo.h
Normal file
61
libsolidity/inlineasm/AsmAnalysisInfo.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
79
libsolidity/inlineasm/AsmScope.cpp
Normal file
79
libsolidity/inlineasm/AsmScope.cpp
Normal 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;
|
||||
}
|
128
libsolidity/inlineasm/AsmScope.h
Normal file
128
libsolidity/inlineasm/AsmScope.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
130
libsolidity/inlineasm/AsmScopeFiller.cpp
Normal file
130
libsolidity/inlineasm/AsmScopeFiller.cpp
Normal 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;
|
||||
}
|
89
libsolidity/inlineasm/AsmScopeFiller.h
Normal file
89
libsolidity/inlineasm/AsmScopeFiller.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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; }
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user