mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1699 from ethereum/asmlabels
Assembly labels with stack information
This commit is contained in:
commit
a1e350a4ae
@ -333,11 +333,10 @@ push their entry label (with virtual function resolution applied). The calling s
|
||||
in solidity are:
|
||||
|
||||
- the caller pushes return label, arg1, arg2, ..., argn
|
||||
- the call returns with ret1, ret2, ..., retn
|
||||
- the call returns with ret1, ret2, ..., retm
|
||||
|
||||
This feature is still a bit cumbersome to use, because the stack offset essentially
|
||||
changes during the call, and thus references to local variables will be wrong.
|
||||
It is planned that the stack height changes can be specified in inline assembly.
|
||||
|
||||
.. code::
|
||||
|
||||
@ -361,7 +360,9 @@ Labels
|
||||
|
||||
Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses
|
||||
which can change easily. Solidity inline assembly provides labels to make the use of
|
||||
jumps easier. The following code computes an element in the Fibonacci series.
|
||||
jumps easier. Note that labels are a low-level feature and it is possible to write
|
||||
efficient assembly without labels, just using assembly functions, loops and switch instructions
|
||||
(see below). The following code computes an element in the Fibonacci series.
|
||||
|
||||
.. code::
|
||||
|
||||
@ -391,12 +392,14 @@ will have a wrong impression about the stack height at label ``two``:
|
||||
.. code::
|
||||
|
||||
{
|
||||
let x := 8
|
||||
jump(two)
|
||||
one:
|
||||
// Here the stack height is 1 (because we pushed 7),
|
||||
// but the assembler thinks it is 0 because it reads
|
||||
// Here the stack height is 2 (because we pushed x and 7),
|
||||
// but the assembler thinks it is 1 because it reads
|
||||
// from top to bottom.
|
||||
// Accessing stack variables here will lead to errors.
|
||||
// Accessing the stack variable x here will lead to errors.
|
||||
x := 9
|
||||
jump(three)
|
||||
two:
|
||||
7 // push something onto the stack
|
||||
@ -404,6 +407,31 @@ will have a wrong impression about the stack height at label ``two``:
|
||||
three:
|
||||
}
|
||||
|
||||
This problem can be fixed by manually adjusting the stack height for the
|
||||
assembler - you can provide a stack height delta that is added
|
||||
to the stack height just prior to the label.
|
||||
Note that you will not have to care about these things if you just use
|
||||
loops and assembly-level functions.
|
||||
|
||||
As an example how this can be done in extreme cases, please see the following.
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
let x := 8
|
||||
jump(two)
|
||||
0 // This code is unreachable but will adjust the stack height correctly
|
||||
one:
|
||||
x := 9 // Now x can be accessed properly.
|
||||
jump(three)
|
||||
pop // Similar negative correction.
|
||||
two:
|
||||
7 // push something onto the stack
|
||||
jump(one)
|
||||
three:
|
||||
pop // We have to pop the manually pushed value here again.
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
``invalidJumpLabel`` is a pre-defined label. Jumping to this location will always
|
||||
@ -464,6 +492,9 @@ is performed by replacing the variable's value on the stack by the new value.
|
||||
Switch
|
||||
------
|
||||
|
||||
.. note::
|
||||
Switch is not yet implemented.
|
||||
|
||||
You can use a switch statement as a very basic version of "if/else".
|
||||
It takes the value of an expression and compares it to several constants.
|
||||
The branch corresponding to the matching constant is taken. Contrary to the
|
||||
@ -491,6 +522,9 @@ case does require them.
|
||||
Loops
|
||||
-----
|
||||
|
||||
.. note::
|
||||
Loops are not yet implemented.
|
||||
|
||||
Assembly supports a simple for-style loop. For-style loops have
|
||||
a header containing an initializing part, a condition and a post-iteration
|
||||
part. The condition has to be a functional-style expression, while
|
||||
@ -512,6 +546,9 @@ The following example computes the sum of an area in memory.
|
||||
Functions
|
||||
---------
|
||||
|
||||
.. note::
|
||||
Functions are not yet implemented.
|
||||
|
||||
Assembly allows the definition of low-level functions. These take their
|
||||
arguments (and a return PC) from the stack and also put the results onto the
|
||||
stack. Calling a function looks the same way as executing a functional-style
|
||||
@ -610,25 +647,19 @@ which follow very simple and regular scoping rules and cleanup of local variable
|
||||
Scoping: An identifier that is declared (label, variable, function, assembly)
|
||||
is only visible in the block where it was declared (including nested blocks
|
||||
inside the current block). It is not legal to access local variables across
|
||||
function borders, even if they would be in scope. Shadowing is allowed, but
|
||||
two identifiers with the same name cannot be declared in the same block.
|
||||
function borders, even if they would be in scope. Shadowing is not allowed.
|
||||
Local variables cannot be accessed before they were declared, but labels,
|
||||
functions and assemblies can. Assemblies are special blocks that are used
|
||||
for e.g. returning runtime code or creating contracts. No identifier from an
|
||||
outer assembly is visible in a sub-assembly.
|
||||
|
||||
If control flow passes over the end of a block, pop instructions are inserted
|
||||
that match the number of local variables declared in that block, unless the
|
||||
``}`` is directly preceded by an opcode that does not have a continuing control
|
||||
flow path. Whenever a local variable is referenced, the code generator needs
|
||||
that match the number of local variables declared in that block.
|
||||
Whenever a local variable is referenced, the code generator needs
|
||||
to know its current relative position in the stack and thus it needs to
|
||||
keep track of the current so-called stack height.
|
||||
At the end of a block, this implicit stack height is always reduced by the number
|
||||
of local variables whether ther is a continuing control flow or not.
|
||||
|
||||
This means that the stack height before and after the block should be the same.
|
||||
If this is not the case, a warning is issued,
|
||||
unless the last instruction in the block did not have a continuing control flow path.
|
||||
keep track of the current so-called stack height. Since all local variables
|
||||
are removed at the end of a block, the stack height before and after the block
|
||||
should be the same. If this is not the case, a warning is issued.
|
||||
|
||||
Why do we use higher-level constructs like ``switch``, ``for`` and functions:
|
||||
|
||||
@ -640,10 +671,9 @@ verification and optimization.
|
||||
Furthermore, if manual jumps are allowed, computing the stack height is rather complicated.
|
||||
The position of all local variables on the stack needs to be known, otherwise
|
||||
neither references to local variables nor removing local variables automatically
|
||||
from the stack at the end of a block will work properly. Because of that,
|
||||
every label that is preceded by an instruction that ends or diverts control flow
|
||||
should be annotated with the current stack layout. This annotation is performed
|
||||
automatically during the desugaring phase.
|
||||
from the stack at the end of a block will work properly. The desugaring
|
||||
mechanism correctly inserts operations at unreachable blocks that adjust the
|
||||
stack height properly in case of jumps that do not have a continuing control flow.
|
||||
|
||||
Example:
|
||||
|
||||
@ -696,17 +726,21 @@ After the desugaring phase it looks as follows::
|
||||
$case1:
|
||||
{
|
||||
// the function call - we put return label and arguments on the stack
|
||||
$ret1 calldataload(4) jump($fun_f)
|
||||
$ret1 [r]: // a label with a [...]-annotation resets the stack height
|
||||
// to "current block + number of local variables". It also
|
||||
// introduces a variable, r:
|
||||
// r is at top of stack, $0 is below (from enclosing block)
|
||||
$ret2 0x20 jump($fun_allocate)
|
||||
$ret2 [ret]: // stack here: $0, r, ret (top)
|
||||
$ret1 calldataload(4) jump(f)
|
||||
// This is unreachable code. Opcodes are added that mirror the
|
||||
// effect of the function on the stack height: Arguments are
|
||||
// removed and return values are introduced.
|
||||
pop pop
|
||||
let r := 0
|
||||
$ret1: // the actual return point
|
||||
$ret2 0x20 jump($allocate)
|
||||
pop pop let ret := 0
|
||||
$ret2:
|
||||
mstore(ret, r)
|
||||
return(ret, 0x20)
|
||||
// although it is useless, the jump is automatically inserted,
|
||||
// since the desugaring process does not analyze control-flow
|
||||
// since the desugaring process is a purely syntactic operation that
|
||||
// does not analyze control-flow
|
||||
jump($endswitch)
|
||||
}
|
||||
$caseDefault:
|
||||
@ -717,20 +751,29 @@ After the desugaring phase it looks as follows::
|
||||
$endswitch:
|
||||
}
|
||||
jump($afterFunction)
|
||||
$fun_allocate:
|
||||
allocate:
|
||||
{
|
||||
$start[$retpos, size]:
|
||||
// output variables live in the same scope as the arguments.
|
||||
// we jump over the unreachable code that introduces the function arguments
|
||||
jump($start)
|
||||
let $retpos := 0 let size := 0
|
||||
$start:
|
||||
// output variables live in the same scope as the arguments and is
|
||||
// actually allocated.
|
||||
let pos := 0
|
||||
{
|
||||
pos := mload(0x40)
|
||||
mstore(0x40, add(pos, size))
|
||||
}
|
||||
// This code replaces the arguments by the return values and jumps back.
|
||||
swap1 pop swap1 jump
|
||||
// Again unreachable code that corrects stack height.
|
||||
0 0
|
||||
}
|
||||
$fun_f:
|
||||
f:
|
||||
{
|
||||
start [$retpos, x]:
|
||||
jump($start)
|
||||
let $retpos := 0 let x := 0
|
||||
$start:
|
||||
let y := 0
|
||||
{
|
||||
let i := 0
|
||||
@ -743,8 +786,9 @@ After the desugaring phase it looks as follows::
|
||||
{ i := add(i, 1) }
|
||||
jump($for_begin)
|
||||
$for_end:
|
||||
} // Here, a pop instruction is inserted for i
|
||||
} // Here, a pop instruction will be inserted for i
|
||||
swap1 pop swap1 jump
|
||||
0 0
|
||||
}
|
||||
$afterFunction:
|
||||
stop
|
||||
@ -805,7 +849,7 @@ Grammar::
|
||||
IdentifierOrList = Identifier | '(' IdentifierList ')'
|
||||
IdentifierList = Identifier ( ',' Identifier)*
|
||||
AssemblyAssignment = '=:' Identifier
|
||||
LabelDefinition = Identifier ( '[' ( IdentifierList | NumberLiteral ) ']' )? ':'
|
||||
LabelDefinition = Identifier ':'
|
||||
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
|
||||
( 'default' ':' AssemblyBlock )?
|
||||
AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock
|
||||
@ -838,11 +882,14 @@ Pseudocode::
|
||||
AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) ->
|
||||
<name>:
|
||||
{
|
||||
$<name>_start [$retPC, $argn, ..., arg1]:
|
||||
jump($<name>_start)
|
||||
let $retPC := 0 let argn := 0 ... let arg1 := 0
|
||||
$<name>_start:
|
||||
let ret1 := 0 ... let retm := 0
|
||||
{ desugar(body) }
|
||||
swap and pop items so that only ret1, ... retn, $retPC are left on the stack
|
||||
jump
|
||||
swap and pop items so that only ret1, ... retm, $retPC are left on the stack
|
||||
jump
|
||||
0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC
|
||||
}
|
||||
AssemblyFor('for' { init } condition post body) ->
|
||||
{
|
||||
@ -862,6 +909,7 @@ Pseudocode::
|
||||
pop all local variables that are defined at the current point
|
||||
but not at $forI_end
|
||||
jump($forI_end)
|
||||
0 (as many as variables were removed above)
|
||||
}
|
||||
'continue' ->
|
||||
{
|
||||
@ -869,6 +917,7 @@ Pseudocode::
|
||||
pop all local variables that are defined at the current point
|
||||
but not at $forI_continue
|
||||
jump($forI_continue)
|
||||
0 (as many as variables were removed above)
|
||||
}
|
||||
AssemblySwitch(switch condition cases ( default: defaultBlock )? ) ->
|
||||
{
|
||||
@ -890,10 +939,13 @@ Pseudocode::
|
||||
{
|
||||
// find I such that $funcallI_* does not exist
|
||||
$funcallI_return argn ... arg2 arg1 jump(<name>)
|
||||
pop (n + 1 times)
|
||||
if the current context is `let (id1, ..., idm) := f(...)` ->
|
||||
$funcallI_return [id1, ..., idm]:
|
||||
let id1 := 0 ... let idm := 0
|
||||
$funcallI_return:
|
||||
else ->
|
||||
$funcallI_return[m - n - 1]:
|
||||
0 (m times)
|
||||
$funcallI_return:
|
||||
turn the functional expression that leads to the function call
|
||||
into a statement stream
|
||||
}
|
||||
@ -906,8 +958,16 @@ Pseudocode::
|
||||
Opcode Stream Generation
|
||||
------------------------
|
||||
|
||||
During opcode stream generation, we keep track of the current stack height,
|
||||
so that accessing stack variables by name is possible.
|
||||
During opcode stream generation, we keep track of the current stack height
|
||||
in a counter,
|
||||
so that accessing stack variables by name is possible. The stack height is modified with every opcode
|
||||
that modifies the stack and with every label that is annotated with a stack
|
||||
adjustment. Every time a new
|
||||
local variable is introduced, it is registered together with the current
|
||||
stack height. If a variable is accessed (either for copying its value or for
|
||||
assignment), the appropriate DUP or SWAP instruction is selected depending
|
||||
on the difference bitween the current stack height and the
|
||||
stack height at the point the variable was introduced.
|
||||
|
||||
Pseudocode::
|
||||
|
||||
@ -945,13 +1005,8 @@ Pseudocode::
|
||||
look up id in the syntactic stack of blocks, assert that it is a variable
|
||||
SWAPi where i = 1 + stack_height - stack_height_of_identifier(id)
|
||||
POP
|
||||
LabelDefinition(name [id1, ..., idn] :) ->
|
||||
LabelDefinition(name:) ->
|
||||
JUMPDEST
|
||||
// register new variables id1, ..., idn and set the stack height to
|
||||
// stack_height_at_block_start + number_of_local_variables
|
||||
LabelDefinition(name [number] :) ->
|
||||
JUMPDEST
|
||||
// adjust stack height by +number (can be negative)
|
||||
NumberLiteral(num) ->
|
||||
PUSH<num interpreted as decimal and right-aligned>
|
||||
HexLiteral(lit) ->
|
||||
|
@ -592,8 +592,12 @@ 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.
|
||||
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
|
||||
codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
|
||||
auto identifierAccess = [&](
|
||||
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;
|
||||
@ -641,8 +645,11 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
|
||||
if (!codeGen.typeCheck(identifierAccess))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(IfStatement const& _ifStatement)
|
||||
|
180
libsolidity/inlineasm/AsmAnalysis.cpp
Normal file
180
libsolidity/inlineasm/AsmAnalysis.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Analyzer part of inline assembly.
|
||||
*/
|
||||
|
||||
#include <libsolidity/inlineasm/AsmAnalysis.h>
|
||||
|
||||
#include <libsolidity/inlineasm/AsmData.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;
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)"
|
||||
));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::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 AsmAnalyzer::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 AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
|
||||
{
|
||||
return boost::apply_visitor(*this, *_assignment.value);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::FunctionDefinition const&)
|
||||
{
|
||||
// TODO - we cannot throw an exception here because of some tests.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::FunctionCall const&)
|
||||
{
|
||||
// TODO - we cannot throw an exception here because of some tests.
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
for (auto const& s: _block.statements)
|
||||
if (!boost::apply_visitor(*this, s))
|
||||
success = false;
|
||||
|
||||
m_currentScope = m_currentScope->superScope;
|
||||
return success;
|
||||
}
|
158
libsolidity/inlineasm/AsmAnalysis.h
Normal file
158
libsolidity/inlineasm/AsmAnalysis.h
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/>.
|
||||
*/
|
||||
/**
|
||||
* Analysis part of inline assembly.
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
class AsmAnalyzer: public boost::static_visitor<bool>
|
||||
{
|
||||
public:
|
||||
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
|
||||
AsmAnalyzer(Scopes& _scopes, ErrorList& _errors);
|
||||
|
||||
bool operator()(assembly::Instruction const&) { return true; }
|
||||
bool operator()(assembly::Literal const& _literal);
|
||||
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:
|
||||
Scope* m_currentScope = nullptr;
|
||||
Scopes& m_scopes;
|
||||
ErrorList& m_errors;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -21,14 +21,23 @@
|
||||
*/
|
||||
|
||||
#include <libsolidity/inlineasm/AsmCodeGen.h>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <libdevcore/CommonIO.h>
|
||||
|
||||
#include <libsolidity/inlineasm/AsmParser.h>
|
||||
#include <libsolidity/inlineasm/AsmData.h>
|
||||
#include <libsolidity/inlineasm/AsmAnalysis.h>
|
||||
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include <libevmasm/SourceLocation.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libsolidity/inlineasm/AsmParser.h>
|
||||
#include <libsolidity/inlineasm/AsmData.h>
|
||||
|
||||
#include <libdevcore/CommonIO.h>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include <boost/range/algorithm/count_if.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -42,73 +51,26 @@ struct GeneratorState
|
||||
|
||||
void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation())
|
||||
{
|
||||
auto err = make_shared<Error>(_type);
|
||||
if (!_location.isEmpty())
|
||||
*err << errinfo_sourceLocation(_location);
|
||||
*err << errinfo_comment(_description);
|
||||
errors.push_back(err);
|
||||
errors.push_back(make_shared<Error>(_type, _description, _location));
|
||||
}
|
||||
|
||||
int const* findVariable(string const& _variableName) const
|
||||
size_t newLabelId()
|
||||
{
|
||||
auto localVariable = find_if(
|
||||
variables.rbegin(),
|
||||
variables.rend(),
|
||||
[&](pair<string, int> const& _var) { return _var.first == _variableName; }
|
||||
);
|
||||
return localVariable != variables.rend() ? &localVariable->second : nullptr;
|
||||
}
|
||||
eth::AssemblyItem const* findLabel(string const& _labelName) const
|
||||
{
|
||||
auto label = find_if(
|
||||
labels.begin(),
|
||||
labels.end(),
|
||||
[&](pair<string, eth::AssemblyItem> const& _label) { return _label.first == _labelName; }
|
||||
);
|
||||
return label != labels.end() ? &label->second : nullptr;
|
||||
return assemblyTagToIdentifier(assembly.newTag());
|
||||
}
|
||||
|
||||
map<string, eth::AssemblyItem> labels;
|
||||
vector<pair<string, int>> variables; ///< name plus stack height
|
||||
size_t assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
|
||||
{
|
||||
u256 id = _tag.data();
|
||||
solAssert(id <= std::numeric_limits<size_t>::max(), "Tag id too large.");
|
||||
return size_t(id);
|
||||
}
|
||||
|
||||
std::map<assembly::Block const*, shared_ptr<Scope>> scopes;
|
||||
ErrorList& errors;
|
||||
eth::Assembly& assembly;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scans the inline assembly data for labels, creates tags in the assembly and searches for
|
||||
* duplicate labels.
|
||||
*/
|
||||
class LabelOrganizer: public boost::static_visitor<>
|
||||
{
|
||||
public:
|
||||
LabelOrganizer(GeneratorState& _state): m_state(_state)
|
||||
{
|
||||
// Make the Solidity ErrorTag available to inline assembly
|
||||
m_state.labels.insert(make_pair("invalidJumpLabel", m_state.assembly.errorTag()));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void operator()(T const& /*_item*/) { }
|
||||
void operator()(Label const& _item)
|
||||
{
|
||||
if (m_state.labels.count(_item.name))
|
||||
//@TODO secondary location
|
||||
m_state.addError(
|
||||
Error::Type::DeclarationError,
|
||||
"Label " + _item.name + " declared twice.",
|
||||
_item.location
|
||||
);
|
||||
m_state.labels.insert(make_pair(_item.name, m_state.assembly.newTag()));
|
||||
}
|
||||
void operator()(assembly::Block const& _block)
|
||||
{
|
||||
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
|
||||
}
|
||||
|
||||
private:
|
||||
GeneratorState& m_state;
|
||||
};
|
||||
|
||||
class CodeTransform: public boost::static_visitor<>
|
||||
{
|
||||
public:
|
||||
@ -117,14 +79,42 @@ public:
|
||||
/// @param _identifierAccess used to resolve identifiers external to the inline assembly
|
||||
explicit CodeTransform(
|
||||
GeneratorState& _state,
|
||||
assembly::Block const& _block,
|
||||
assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess()
|
||||
):
|
||||
m_state(_state)
|
||||
m_state(_state),
|
||||
m_scope(*m_state.scopes.at(&_block)),
|
||||
m_initialDeposit(m_state.assembly.deposit()),
|
||||
m_identifierAccess(_identifierAccess)
|
||||
{
|
||||
if (_identifierAccess)
|
||||
m_identifierAccess = _identifierAccess;
|
||||
else
|
||||
m_identifierAccess = [](assembly::Identifier const&, eth::Assembly&, CodeGenerator::IdentifierContext) { return false; };
|
||||
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
|
||||
|
||||
m_state.assembly.setSourceLocation(_block.location);
|
||||
|
||||
// pop variables
|
||||
for (auto const& identifier: m_scope.identifiers)
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(assembly::Instruction const& _instruction)
|
||||
@ -137,40 +127,38 @@ public:
|
||||
m_state.assembly.setSourceLocation(_literal.location);
|
||||
if (_literal.isNumber)
|
||||
m_state.assembly.append(u256(_literal.value));
|
||||
else if (_literal.value.size() > 32)
|
||||
{
|
||||
m_state.addError(
|
||||
Error::Type::TypeError,
|
||||
"String literal too long (" + boost::lexical_cast<string>(_literal.value.size()) + " > 32)"
|
||||
);
|
||||
m_state.assembly.append(u256(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(_literal.value.size() <= 32, "");
|
||||
m_state.assembly.append(_literal.value);
|
||||
}
|
||||
}
|
||||
void operator()(assembly::Identifier const& _identifier)
|
||||
{
|
||||
m_state.assembly.setSourceLocation(_identifier.location);
|
||||
// First search local variables, then labels, then externals.
|
||||
if (int const* stackHeight = m_state.findVariable(_identifier.name))
|
||||
{
|
||||
int heightDiff = m_state.assembly.deposit() - *stackHeight;
|
||||
if (heightDiff <= 0 || heightDiff > 16)
|
||||
// First search internals, then externals.
|
||||
if (m_scope.lookup(_identifier.name, Scope::NonconstVisitor(
|
||||
[=](Scope::Variable& _var)
|
||||
{
|
||||
m_state.addError(
|
||||
Error::Type::TypeError,
|
||||
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
|
||||
_identifier.location
|
||||
);
|
||||
m_state.assembly.append(u256(0));
|
||||
if (int heightDiff = variableHeightDiff(_var, _identifier.location, false))
|
||||
m_state.assembly.append(solidity::dupInstruction(heightDiff));
|
||||
else
|
||||
// Store something to balance the stack
|
||||
m_state.assembly.append(u256(0));
|
||||
},
|
||||
[=](Scope::Label& _label)
|
||||
{
|
||||
assignLabelIdIfUnset(_label);
|
||||
m_state.assembly.append(eth::AssemblyItem(eth::PushTag, _label.id));
|
||||
},
|
||||
[=](Scope::Function&)
|
||||
{
|
||||
solAssert(false, "Not yet implemented");
|
||||
}
|
||||
else
|
||||
m_state.assembly.append(solidity::dupInstruction(heightDiff));
|
||||
return;
|
||||
)))
|
||||
{
|
||||
}
|
||||
else if (eth::AssemblyItem const* label = m_state.findLabel(_identifier.name))
|
||||
m_state.assembly.append(label->pushTag());
|
||||
else if (!m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue))
|
||||
else if (!m_identifierAccess || !m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue))
|
||||
{
|
||||
m_state.addError(
|
||||
Error::Type::DeclarationError,
|
||||
@ -197,7 +185,10 @@ public:
|
||||
void operator()(Label const& _label)
|
||||
{
|
||||
m_state.assembly.setSourceLocation(_label.location);
|
||||
m_state.assembly.append(m_state.labels.at(_label.name));
|
||||
solAssert(m_scope.identifiers.count(_label.name), "");
|
||||
Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers[_label.name]);
|
||||
assignLabelIdIfUnset(label);
|
||||
m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id));
|
||||
}
|
||||
void operator()(assembly::Assignment const& _assignment)
|
||||
{
|
||||
@ -217,42 +208,14 @@ public:
|
||||
int height = m_state.assembly.deposit();
|
||||
boost::apply_visitor(*this, *_varDecl.value);
|
||||
expectDeposit(1, height, locationOf(*_varDecl.value));
|
||||
m_state.variables.push_back(make_pair(_varDecl.name, height));
|
||||
solAssert(m_scope.identifiers.count(_varDecl.name), "");
|
||||
auto& var = boost::get<Scope::Variable>(m_scope.identifiers[_varDecl.name]);
|
||||
var.stackHeight = height;
|
||||
var.active = true;
|
||||
}
|
||||
void operator()(assembly::Block const& _block)
|
||||
{
|
||||
size_t numVariables = m_state.variables.size();
|
||||
int deposit = m_state.assembly.deposit();
|
||||
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
|
||||
|
||||
// pop variables
|
||||
while (m_state.variables.size() > numVariables)
|
||||
{
|
||||
m_state.assembly.append(solidity::Instruction::POP);
|
||||
m_state.variables.pop_back();
|
||||
}
|
||||
|
||||
m_state.assembly.setSourceLocation(_block.location);
|
||||
|
||||
deposit = m_state.assembly.deposit() - deposit;
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
CodeTransform(m_state, _block, m_identifierAccess);
|
||||
}
|
||||
void operator()(assembly::FunctionDefinition const&)
|
||||
{
|
||||
@ -262,27 +225,61 @@ public:
|
||||
private:
|
||||
void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location)
|
||||
{
|
||||
if (int const* stackHeight = m_state.findVariable(_variableName.name))
|
||||
{
|
||||
int heightDiff = m_state.assembly.deposit() - *stackHeight - 1;
|
||||
if (heightDiff <= 0 || heightDiff > 16)
|
||||
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::TypeError,
|
||||
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
|
||||
_location
|
||||
Error::Type::DeclarationError,
|
||||
"Label \"" + string(_variableName.name) + "\" used as variable."
|
||||
);
|
||||
else
|
||||
m_state.assembly.append(solidity::swapInstruction(heightDiff));
|
||||
m_state.assembly.append(solidity::Instruction::POP);
|
||||
return;
|
||||
},
|
||||
[=](Scope::Function const&)
|
||||
{
|
||||
m_state.addError(
|
||||
Error::Type::DeclarationError,
|
||||
"Function \"" + string(_variableName.name) + "\" used as variable."
|
||||
);
|
||||
}
|
||||
)))
|
||||
{
|
||||
}
|
||||
else if (!m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue))
|
||||
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."
|
||||
);
|
||||
}
|
||||
|
||||
/// Determines the stack height difference to the given variables. Automatically generates
|
||||
/// errors if it is not yet in scope or the height difference is too large. Returns 0 on
|
||||
/// 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(
|
||||
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)
|
||||
{
|
||||
if (m_state.assembly.deposit() != _oldHeight + 1)
|
||||
@ -296,7 +293,19 @@ private:
|
||||
);
|
||||
}
|
||||
|
||||
/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
|
||||
void assignLabelIdIfUnset(Scope::Label& _label)
|
||||
{
|
||||
if (_label.id == Scope::Label::unassignedLabelId)
|
||||
_label.id = m_state.newLabelId();
|
||||
else if (_label.id == Scope::Label::errorLabelId)
|
||||
_label.id = size_t(m_state.assembly.errorTag().data());
|
||||
}
|
||||
|
||||
|
||||
GeneratorState& m_state;
|
||||
Scope& m_scope;
|
||||
int const m_initialDeposit;
|
||||
assembly::CodeGenerator::IdentifierAccess m_identifierAccess;
|
||||
};
|
||||
|
||||
@ -305,8 +314,9 @@ bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAcces
|
||||
size_t initialErrorLen = m_errors.size();
|
||||
eth::Assembly assembly;
|
||||
GeneratorState state(m_errors, assembly);
|
||||
(LabelOrganizer(state))(m_parsedData);
|
||||
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
|
||||
return false;
|
||||
CodeTransform(state, m_parsedData, _identifierAccess);
|
||||
return m_errors.size() == initialErrorLen;
|
||||
}
|
||||
|
||||
@ -314,15 +324,16 @@ eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::Identif
|
||||
{
|
||||
eth::Assembly assembly;
|
||||
GeneratorState state(m_errors, assembly);
|
||||
(LabelOrganizer(state))(m_parsedData);
|
||||
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
|
||||
solAssert(false, "Assembly error");
|
||||
CodeTransform(state, m_parsedData, _identifierAccess);
|
||||
return assembly;
|
||||
}
|
||||
|
||||
void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
|
||||
{
|
||||
GeneratorState state(m_errors, _assembly);
|
||||
(LabelOrganizer(state))(m_parsedData);
|
||||
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
||||
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
|
||||
solAssert(false, "Assembly error");
|
||||
CodeTransform(state, m_parsedData, _identifierAccess);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <libsolidity/inlineasm/AsmParser.h>
|
||||
#include <libsolidity/inlineasm/AsmCodeGen.h>
|
||||
#include <libsolidity/inlineasm/AsmPrinter.h>
|
||||
#include <libsolidity/inlineasm/AsmAnalysis.h>
|
||||
|
||||
#include <libsolidity/parsing/Scanner.h>
|
||||
|
||||
@ -45,8 +46,10 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
|
||||
auto result = parser.parse(_scanner);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
*m_parserResult = std::move(*result);
|
||||
return true;
|
||||
AsmAnalyzer::Scopes scopes;
|
||||
return (AsmAnalyzer(scopes, m_errors))(*m_parserResult);
|
||||
}
|
||||
|
||||
string InlineAssemblyStack::toString()
|
||||
|
@ -64,6 +64,14 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
|
||||
*this << errinfo_comment(_description);
|
||||
}
|
||||
|
||||
Error::Error(Error::Type _type, const std::string& _description, const SourceLocation& _location):
|
||||
Error(_type)
|
||||
{
|
||||
if (!_location.isEmpty())
|
||||
*this << errinfo_sourceLocation(_location);
|
||||
*this << errinfo_comment(_description);
|
||||
}
|
||||
|
||||
string Exception::lineInfo() const
|
||||
{
|
||||
char const* const* file = boost::get_error_info<boost::throw_file>(*this);
|
||||
|
@ -59,6 +59,8 @@ public:
|
||||
std::string const& _description = std::string()
|
||||
);
|
||||
|
||||
Error(Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation());
|
||||
|
||||
Type type() const { return m_type; }
|
||||
std::string const& typeName() const { return m_typeName; }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user