mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Refactor assembly analysis into scope filling and checking.
This commit is contained in:
parent
72fdf755c9
commit
5d6747eb32
@ -21,6 +21,8 @@
|
||||
#include <libsolidity/inlineasm/AsmAnalysis.h>
|
||||
|
||||
#include <libsolidity/inlineasm/AsmData.h>
|
||||
#include <libsolidity/inlineasm/AsmScopeFiller.h>
|
||||
#include <libsolidity/inlineasm/AsmScope.h>
|
||||
|
||||
#include <libsolidity/interface/Exceptions.h>
|
||||
#include <libsolidity/interface/Utils.h>
|
||||
@ -36,68 +38,16 @@ using namespace dev::solidity;
|
||||
using namespace dev::solidity::assembly;
|
||||
|
||||
|
||||
bool Scope::registerLabel(string const& _name)
|
||||
AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::Scopes& _scopes, ErrorList& _errors, bool _allowFailedLookups):
|
||||
m_allowFailedLookups(_allowFailedLookups), m_scopes(_scopes), m_errors(_errors)
|
||||
{
|
||||
if (exists(_name))
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::analyze(Block const& _block)
|
||||
{
|
||||
if (!(ScopeFiller(m_scopes, m_errors))(_block))
|
||||
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 = identifiers.find(_name);
|
||||
if (id != 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;
|
||||
}
|
||||
|
||||
AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::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);
|
||||
return (*this)(_block);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
|
||||
@ -113,6 +63,47 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
|
||||
{
|
||||
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;
|
||||
}
|
||||
},
|
||||
[&](Scope::Label const&) {},
|
||||
[&](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 if (!m_allowFailedLookups)
|
||||
{
|
||||
m_errors.push_back(make_shared<Error>(
|
||||
Error::Type::DeclarationError,
|
||||
"Identifier not found.",
|
||||
_identifier.location
|
||||
));
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
|
||||
{
|
||||
bool success = true;
|
||||
@ -124,74 +115,100 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
|
||||
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;
|
||||
return checkAssignment(_assignment.variableName);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
|
||||
{
|
||||
return boost::apply_visitor(*this, *_assignment.value);
|
||||
bool success = boost::apply_visitor(*this, *_assignment.value);
|
||||
if (!checkAssignment(_assignment.variableName))
|
||||
success = false;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
|
||||
{
|
||||
bool success = boost::apply_visitor(*this, *_varDecl.value);
|
||||
if (!registerVariable(_varDecl.name, _varDecl.location, *m_currentScope))
|
||||
success = false;
|
||||
boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::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;
|
||||
Scope& bodyScope = scope(&_funDef.body);
|
||||
for (auto const& var: _funDef.arguments + _funDef.returns)
|
||||
if (!registerVariable(var, _funDef.location, body))
|
||||
success = false;
|
||||
boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true;
|
||||
|
||||
(*this)(_funDef.body);
|
||||
|
||||
return success;
|
||||
return (*this)(_funDef.body);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
|
||||
{
|
||||
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;
|
||||
}
|
||||
//@todo check the number of returns - depends on context and should probably
|
||||
// be only done once we have stack height checks
|
||||
}
|
||||
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
|
||||
if (!boost::apply_visitor(*this, arg))
|
||||
success = false;
|
||||
// TODO actually look up the function (can only be done in a second pass)
|
||||
// and check that the number of arguments and of returns matches the context
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Block const& _block)
|
||||
{
|
||||
bool success = true;
|
||||
scope(&_block).superScope = m_currentScope;
|
||||
m_currentScope = &scope(&_block);
|
||||
|
||||
for (auto const& s: _block.statements)
|
||||
@ -202,25 +219,29 @@ bool AsmAnalyzer::operator()(Block const& _block)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope)
|
||||
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable)
|
||||
{
|
||||
if (!_scope.registerVariable(_name))
|
||||
if (!(*this)(_variable))
|
||||
return false;
|
||||
else if (!m_allowFailedLookups)
|
||||
{
|
||||
// Check that it is a variable
|
||||
if (m_currentScope->lookup(_variable.name)->type() != typeid(Scope::Variable))
|
||||
{
|
||||
//@TODO secondary location
|
||||
m_errors.push_back(make_shared<Error>(
|
||||
Error::Type::DeclarationError,
|
||||
"Variable name " + _name + " already taken in this scope.",
|
||||
_location
|
||||
Error::Type::TypeError,
|
||||
"Assignment requires variable.",
|
||||
_variable.location
|
||||
));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Scope& AsmAnalyzer::scope(Block const* _block)
|
||||
{
|
||||
auto& scope = m_scopes[_block];
|
||||
if (!scope)
|
||||
scope = make_shared<Scope>();
|
||||
return *scope;
|
||||
auto scopePtr = m_scopes.at(_block);
|
||||
solAssert(scopePtr, "Scope requested but not present.");
|
||||
return *scopePtr;
|
||||
}
|
||||
|
@ -46,104 +46,29 @@ 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 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;
|
||||
};
|
||||
|
||||
struct Scope;
|
||||
|
||||
/**
|
||||
* Performs the full analysis stage, calls the ScopeFiller internally, then resolves
|
||||
* references and performs other checks.
|
||||
* @todo Does not yet check for stack height issues.
|
||||
*/
|
||||
class AsmAnalyzer: public boost::static_visitor<bool>
|
||||
{
|
||||
public:
|
||||
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
|
||||
AsmAnalyzer(Scopes& _scopes, ErrorList& _errors);
|
||||
/// @param _allowFailedLookups if true, allow failed lookups for variables (they
|
||||
/// will be provided from the environment later on)
|
||||
AsmAnalyzer(Scopes& _scopes, ErrorList& _errors, bool _allowFailedLookups);
|
||||
|
||||
bool analyze(assembly::Block const& _block);
|
||||
|
||||
bool operator()(assembly::Instruction const&) { return true; }
|
||||
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::Label 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);
|
||||
@ -151,14 +76,10 @@ public:
|
||||
bool operator()(assembly::Block const& _block);
|
||||
|
||||
private:
|
||||
bool registerVariable(
|
||||
std::string const& _name,
|
||||
SourceLocation const& _location,
|
||||
Scope& _scope
|
||||
);
|
||||
|
||||
bool checkAssignment(assembly::Identifier const& _assignment);
|
||||
Scope& scope(assembly::Block const* _block);
|
||||
|
||||
bool m_allowFailedLookups = false;
|
||||
Scope* m_currentScope = nullptr;
|
||||
Scopes& m_scopes;
|
||||
ErrorList& m_errors;
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <libsolidity/inlineasm/AsmParser.h>
|
||||
#include <libsolidity/inlineasm/AsmData.h>
|
||||
#include <libsolidity/inlineasm/AsmScope.h>
|
||||
#include <libsolidity/inlineasm/AsmAnalysis.h>
|
||||
|
||||
#include <libevmasm/Assembly.h>
|
||||
@ -153,12 +154,14 @@ 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))
|
||||
solAssert(m_identifierAccess, "Identifier not found and no external access available.");
|
||||
if (!m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue))
|
||||
{
|
||||
m_state.addError(
|
||||
Error::Type::DeclarationError,
|
||||
@ -186,7 +189,7 @@ 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));
|
||||
}
|
||||
@ -208,8 +211,7 @@ public:
|
||||
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]);
|
||||
auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name));
|
||||
var.stackHeight = height;
|
||||
var.active = true;
|
||||
}
|
||||
@ -225,31 +227,17 @@ public:
|
||||
private:
|
||||
void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location)
|
||||
{
|
||||
if (m_scope.lookup(_variableName.name, Scope::Visitor(
|
||||
[=](Scope::Variable const& _var)
|
||||
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);
|
||||
},
|
||||
[=](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."
|
||||
);
|
||||
return;
|
||||
}
|
||||
)))
|
||||
{
|
||||
}
|
||||
else if (!m_identifierAccess || !m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue))
|
||||
solAssert(m_identifierAccess, "Identifier not found and no external access available.");
|
||||
if (!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."
|
||||
@ -261,11 +249,6 @@ 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))
|
||||
{
|
||||
@ -314,7 +297,7 @@ bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAcces
|
||||
size_t initialErrorLen = m_errors.size();
|
||||
eth::Assembly assembly;
|
||||
GeneratorState state(m_errors, assembly);
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors, !!_identifierAccess)).analyze(m_parsedData))
|
||||
return false;
|
||||
CodeTransform(state, m_parsedData, _identifierAccess);
|
||||
return m_errors.size() == initialErrorLen;
|
||||
@ -324,7 +307,7 @@ eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::Identif
|
||||
{
|
||||
eth::Assembly assembly;
|
||||
GeneratorState state(m_errors, assembly);
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors, !!_identifierAccess)).analyze(m_parsedData))
|
||||
solAssert(false, "Assembly error");
|
||||
CodeTransform(state, m_parsedData, _identifierAccess);
|
||||
return assembly;
|
||||
@ -333,7 +316,7 @@ eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::Identif
|
||||
void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
|
||||
{
|
||||
GeneratorState state(m_errors, _assembly);
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors, !!_identifierAccess)).analyze(m_parsedData))
|
||||
solAssert(false, "Assembly error");
|
||||
CodeTransform(state, m_parsedData, _identifierAccess);
|
||||
}
|
||||
|
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
158
libsolidity/inlineasm/AsmScopeFiller.cpp
Normal file
158
libsolidity/inlineasm/AsmScopeFiller.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
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()(FunctionalInstruction const& _instr)
|
||||
{
|
||||
bool success = true;
|
||||
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
|
||||
if (!boost::apply_visitor(*this, arg))
|
||||
success = false;
|
||||
if (!(*this)(_instr.instruction))
|
||||
success = false;
|
||||
return success;
|
||||
}
|
||||
|
||||
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()(FunctionalAssignment const& _assignment)
|
||||
{
|
||||
return boost::apply_visitor(*this, *_assignment.value);
|
||||
}
|
||||
|
||||
bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
|
||||
{
|
||||
bool success = boost::apply_visitor(*this, *_varDecl.value);
|
||||
if (!registerVariable(_varDecl.name, _varDecl.location, *m_currentScope))
|
||||
success = false;
|
||||
return success;
|
||||
}
|
||||
|
||||
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()(assembly::FunctionCall const& _funCall)
|
||||
{
|
||||
bool success = true;
|
||||
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
|
||||
if (!boost::apply_visitor(*this, arg))
|
||||
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& _functionalInstruction);
|
||||
bool operator()(assembly::Label const& _label);
|
||||
bool operator()(assembly::Assignment const&) { return true; }
|
||||
bool operator()(assembly::FunctionalAssignment const& _functionalAssignment);
|
||||
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
|
||||
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
|
||||
bool operator()(assembly::FunctionCall const& _functionCall);
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
|
||||
|
||||
*m_parserResult = std::move(*result);
|
||||
AsmAnalyzer::Scopes scopes;
|
||||
return (AsmAnalyzer(scopes, m_errors))(*m_parserResult);
|
||||
return (AsmAnalyzer(scopes, m_errors, false)).analyze(*m_parserResult);
|
||||
}
|
||||
|
||||
string InlineAssemblyStack::toString()
|
||||
|
@ -63,7 +63,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
|
||||
@ -162,7 +162,7 @@ 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)
|
||||
@ -177,22 +177,28 @@ BOOST_AUTO_TEST_CASE(label_complex)
|
||||
|
||||
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 }"));
|
||||
}
|
||||
|
||||
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 +218,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) {} 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 } }", DeclarationError, "Identifier not found.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
@ -272,7 +299,9 @@ 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}");
|
||||
parsePrintCompare(
|
||||
"{\n function y()\n {\n }\n function f(a)\n {\n }\n function g(a, b, c)\n {\n }\n g(1, mul(2, address), f(mul(2, caller)))\n y()\n}"
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
@ -296,8 +325,8 @@ BOOST_AUTO_TEST_CASE(assignment_after_tag)
|
||||
|
||||
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");
|
||||
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 }"));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user