Merge pull request #514 from chriseth/sourceLoc

Source location for inline assembly.
This commit is contained in:
chriseth 2016-04-22 19:28:28 +02:00
commit dd4300d5b8
9 changed files with 159 additions and 81 deletions

View File

@ -26,6 +26,7 @@
#include <string>
#include <ostream>
#include <tuple>
#include <libdevcore/Common.h> // defines noexcept macro for MSVC
namespace dev
{
@ -36,24 +37,21 @@ namespace dev
*/
struct SourceLocation
{
SourceLocation(): start(-1), end(-1) { }
SourceLocation(int _start, int _end, std::shared_ptr<std::string const> _sourceName):
start(_start), end(_end), sourceName(_sourceName) { }
SourceLocation(): start(-1), end(-1) { }
SourceLocation(SourceLocation const& _other):
SourceLocation(SourceLocation&& _other) noexcept:
start(_other.start),
end(_other.end),
sourceName(_other.sourceName)
sourceName(std::move(_other.sourceName))
{}
SourceLocation& operator=(SourceLocation const& _other)
SourceLocation(SourceLocation const& _other) = default;
SourceLocation& operator=(SourceLocation const&) = default;
SourceLocation& operator=(SourceLocation&& _other) noexcept
{
if (&_other == this)
return *this;
start = _other.start;
end = _other.end;
sourceName = _other.sourceName;
sourceName = std::move(_other.sourceName);
return *this;
}

View File

@ -86,8 +86,12 @@ public:
void operator()(Label const& _item)
{
if (m_state.labels.count(_item.name))
//@TODO location and secondary location
m_state.addError(Error::Type::DeclarationError, "Label " + _item.name + " declared twice.");
//@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)
@ -117,34 +121,43 @@ public:
m_identifierAccess = [](assembly::Identifier const&, eth::Assembly&, CodeGenerator::IdentifierContext) { return false; };
}
void operator()(dev::solidity::assembly::Instruction const& _instruction)
void operator()(assembly::Instruction const& _instruction)
{
m_state.assembly.setSourceLocation(_instruction.location);
m_state.assembly.append(_instruction.instruction);
}
void operator()(assembly::Literal const& _literal)
{
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
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)
//@TODO location
{
m_state.addError(
Error::Type::TypeError,
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")"
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
_identifier.location
);
m_state.assembly.append(u256(0));
}
else
m_state.assembly.append(solidity::dupInstruction(heightDiff));
return;
@ -152,10 +165,14 @@ public:
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))
{
m_state.addError(
Error::Type::DeclarationError,
"Identifier \"" + string(_identifier.name) + "\" not found or not unique"
"Identifier not found or not unique",
_identifier.location
);
m_state.assembly.append(u256(0));
}
}
void operator()(FunctionalInstruction const& _instr)
{
@ -163,30 +180,33 @@ public:
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *it);
expectDeposit(1, height);
expectDeposit(1, height, locationOf(*it));
}
(*this)(_instr.instruction);
}
void operator()(Label const& _label)
{
m_state.assembly.setSourceLocation(_label.location);
m_state.assembly.append(m_state.labels.at(_label.name));
}
void operator()(assembly::Assignment const& _assignment)
{
generateAssignment(_assignment.variableName);
m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
}
void operator()(FunctionalAssignment const& _assignment)
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_assignment.value);
expectDeposit(1, height);
generateAssignment(_assignment.variableName);
expectDeposit(1, height, locationOf(*_assignment.value));
m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
}
void operator()(assembly::VariableDeclaration const& _varDecl)
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(1, height);
expectDeposit(1, height, locationOf(*_varDecl.value));
m_state.variables.push_back(make_pair(_varDecl.name, height));
}
void operator()(assembly::Block const& _block)
@ -194,7 +214,8 @@ public:
size_t numVariables = m_state.variables.size();
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
// pop variables
//@TODO check height before and after
// we deliberately do not check stack height
m_state.assembly.setSourceLocation(_block.location);
while (m_state.variables.size() > numVariables)
{
m_state.assembly.append(solidity::Instruction::POP);
@ -203,22 +224,20 @@ public:
}
private:
void generateAssignment(assembly::Identifier const& _variableName)
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)
//@TODO location
m_state.addError(
Error::Type::TypeError,
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")"
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
_location
);
else
{
m_state.assembly.append(solidity::swapInstruction(heightDiff));
m_state.assembly.append(solidity::Instruction::POP);
}
return;
}
else if (!m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue))
@ -228,16 +247,16 @@ private:
);
}
void expectDeposit(int _deposit, int _oldHeight)
void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
{
if (m_state.assembly.deposit() != _oldHeight + 1)
//@TODO location
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)."
" item(s).",
_location
);
}

View File

@ -48,7 +48,7 @@ public:
/// 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):
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.

View File

@ -24,6 +24,7 @@
#include <boost/variant.hpp>
#include <libevmasm/Instruction.h>
#include <libevmasm/SourceLocation.h>
namespace dev
{
@ -35,29 +36,43 @@ namespace assembly
/// What follows are the AST nodes for assembly.
/// Direct EVM instruction (except PUSHi and JUMPDEST)
struct Instruction { solidity::Instruction instruction; };
struct Instruction { SourceLocation location; solidity::Instruction instruction; };
/// Literal number or string (up to 32 bytes)
struct Literal { bool isNumber; std::string value; };
struct Literal { SourceLocation location; bool isNumber; std::string value; };
/// External / internal identifier or label reference
struct Identifier { std::string name; };
struct Identifier { SourceLocation location; std::string name; };
struct FunctionalInstruction;
/// Jump label ("name:")
struct Label { std::string name; };
struct Label { SourceLocation location; std::string name; };
/// Assignemnt (":= x", moves stack top into x, potentially multiple slots)
struct Assignment { Identifier variableName; };
struct Assignment { SourceLocation location; Identifier variableName; };
struct FunctionalAssignment;
struct VariableDeclaration;
struct Block;
using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionalInstruction, VariableDeclaration, Block>;
/// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand
/// side and requires x to occupy exactly one stack slot.
struct FunctionalAssignment { Identifier variableName; std::shared_ptr<Statement> value; };
struct FunctionalAssignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; };
/// Functional instruction, e.g. "mul(mload(20), add(2, x))"
struct FunctionalInstruction { Instruction instruction; std::vector<Statement> arguments; };
struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
/// Block-scope variable declaration ("let x := mload(20)"), non-hoisted
struct VariableDeclaration { std::string name; std::shared_ptr<Statement> value; };
struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr<Statement> value; };
/// Block that creates a scope (frees declared stack variables)
struct Block { std::vector<Statement> statements; };
struct Block { SourceLocation location; std::vector<Statement> statements; };
struct LocationExtractor: boost::static_visitor<SourceLocation>
{
template <class T> SourceLocation operator()(T const& _node) const
{
return _node.location;
}
};
/// Extracts the source location from an inline assembly node.
template <class T> inline SourceLocation locationOf(T const& _node)
{
return boost::apply_visitor(LocationExtractor(), _node);
}
}
}

View File

@ -35,7 +35,7 @@ shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scann
try
{
m_scanner = _scanner;
return make_shared<assembly::Block>(parseBlock());
return make_shared<Block>(parseBlock());
}
catch (FatalError const&)
{
@ -47,10 +47,11 @@ shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scann
assembly::Block Parser::parseBlock()
{
assembly::Block block = createWithLocation<Block>();
expectToken(Token::LBrace);
Block block;
while (m_scanner->currentToken() != Token::RBrace)
block.statements.emplace_back(parseStatement());
block.location.end = endPosition();
m_scanner->next();
return block;
}
@ -65,11 +66,14 @@ assembly::Statement Parser::parseStatement()
return parseBlock();
case Token::Assign:
{
assembly::Assignment assignment = createWithLocation<assembly::Assignment>();
m_scanner->next();
expectToken(Token::Colon);
string name = m_scanner->currentLiteral();
assignment.variableName.location = location();
assignment.variableName.name = m_scanner->currentLiteral();
assignment.location.end = endPosition();
expectToken(Token::Identifier);
return assembly::Assignment{assembly::Identifier{name}};
return assignment;
}
case Token::Return: // opcode
case Token::Byte: // opcode
@ -84,24 +88,30 @@ assembly::Statement Parser::parseStatement()
switch (m_scanner->currentToken())
{
case Token::LParen:
return parseFunctionalInstruction(statement);
return parseFunctionalInstruction(std::move(statement));
case Token::Colon:
{
if (statement.type() != typeid(assembly::Identifier))
fatalParserError("Label name / variable name must precede \":\".");
string const& name = boost::get<assembly::Identifier>(statement).name;
assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
m_scanner->next();
if (m_scanner->currentToken() == Token::Assign)
{
// functional assignment
FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location);
m_scanner->next();
unique_ptr<Statement> value;
value.reset(new Statement(parseExpression()));
return FunctionalAssignment{{std::move(name)}, std::move(value)};
funAss.variableName = identifier;
funAss.value.reset(new Statement(parseExpression()));
funAss.location.end = locationOf(*funAss.value).end;
return funAss;
}
else
{
// label
return Label{name};
Label label = createWithLocation<Label>(identifier.location);
label.name = identifier.name;
return label;
}
}
default:
break;
@ -113,7 +123,7 @@ assembly::Statement Parser::parseExpression()
{
Statement operation = parseElementaryOperation(true);
if (m_scanner->currentToken() == Token::LParen)
return parseFunctionalInstruction(operation);
return parseFunctionalInstruction(std::move(operation));
else
return operation;
}
@ -137,8 +147,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
s_instructions[name] = instruction.second;
}
//@TODO track location
Statement ret;
switch (m_scanner->currentToken())
{
case Token::Identifier:
@ -162,48 +171,50 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
if (info.ret != 1)
fatalParserError("Instruction " + info.name + " not allowed in this context.");
}
m_scanner->next();
return Instruction{instr};
ret = Instruction{location(), instr};
}
else
m_scanner->next();
return Identifier{literal};
ret = Identifier{location(), literal};
break;
}
case Token::StringLiteral:
case Token::Number:
{
Literal literal{
ret = Literal{
location(),
m_scanner->currentToken() == Token::Number,
m_scanner->currentLiteral()
};
m_scanner->next();
return literal;
}
default:
break;
}
default:
fatalParserError("Expected elementary inline assembly operation.");
return {};
}
m_scanner->next();
return ret;
}
assembly::VariableDeclaration Parser::parseVariableDeclaration()
{
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let);
string name = m_scanner->currentLiteral();
varDecl.name = m_scanner->currentLiteral();
expectToken(Token::Identifier);
expectToken(Token::Colon);
expectToken(Token::Assign);
unique_ptr<Statement> value;
value.reset(new Statement(parseExpression()));
return VariableDeclaration{name, std::move(value)};
varDecl.value.reset(new Statement(parseExpression()));
varDecl.location.end = locationOf(*varDecl.value).end;
return varDecl;
}
FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement const& _instruction)
FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction)
{
if (_instruction.type() != typeid(Instruction))
fatalParserError("Assembly instruction required in front of \"(\")");
solidity::Instruction instr = boost::get<solidity::assembly::Instruction>(_instruction).instruction;
FunctionalInstruction ret;
ret.instruction = std::move(boost::get<Instruction>(_instruction));
ret.location = ret.instruction.location;
solidity::Instruction instr = ret.instruction.instruction;
InstructionInfo instrInfo = instructionInfo(instr);
if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16)
fatalParserError("DUPi instructions not allowed for functional notation");
@ -211,14 +222,29 @@ FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement con
fatalParserError("SWAPi instructions not allowed for functional notation");
expectToken(Token::LParen);
vector<Statement> arguments;
unsigned args = unsigned(instrInfo.args);
for (unsigned i = 0; i < args; ++i)
{
arguments.push_back(parseExpression());
ret.arguments.emplace_back(parseExpression());
if (i != args - 1)
expectToken(Token::Comma);
{
if (m_scanner->currentToken() != Token::Comma)
fatalParserError(string(
"Expected comma (" +
instrInfo.name +
" expects " +
boost::lexical_cast<string>(args) +
" arguments)"
));
else
m_scanner->next();
}
}
ret.location.end = endPosition();
if (m_scanner->currentToken() == Token::Comma)
fatalParserError(
string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)")
);
expectToken(Token::RParen);
return FunctionalInstruction{{instr}, std::move(arguments)};
return ret;
}

View File

@ -44,13 +44,29 @@ public:
std::shared_ptr<Block> parse(std::shared_ptr<Scanner> const& _scanner);
protected:
/// Creates an inline assembly node with the given source location.
template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation())
{
T r;
r.location = _loc;
if (r.location.isEmpty())
{
r.location.start = position();
r.location.end = endPosition();
}
if (!r.location.sourceName)
r.location.sourceName = sourceName();
return r;
}
SourceLocation location() const { return SourceLocation(position(), endPosition(), sourceName()); }
Block parseBlock();
Statement parseStatement();
/// Parses a functional expression that has to push exactly one stack element
Statement parseExpression();
Statement parseElementaryOperation(bool _onlySinglePusher = false);
VariableDeclaration parseVariableDeclaration();
FunctionalInstruction parseFunctionalInstruction(Statement const& _instruction);
FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction);
};
}

View File

@ -34,14 +34,18 @@ using namespace dev::solidity::assembly;
bool InlineAssemblyStack::parse(const std::shared_ptr<Scanner>& _scanner)
{
m_parserResult = make_shared<Block>();
Parser parser(m_errors);
m_asmBlock = parser.parse(_scanner);
return !!m_asmBlock;
auto result = parser.parse(_scanner);
if (!result)
return false;
*m_parserResult = std::move(*result);
return true;
}
eth::Assembly InlineAssemblyStack::assemble()
{
CodeGenerator codeGen(*m_asmBlock, m_errors);
CodeGenerator codeGen(*m_parserResult, m_errors);
return codeGen.assemble();
}

View File

@ -50,7 +50,7 @@ public:
ErrorList const& errors() const { return m_errors; }
private:
std::shared_ptr<Block> m_asmBlock;
std::shared_ptr<Block> m_parserResult;
ErrorList m_errors;
};

View File

@ -861,7 +861,7 @@ void CommandLineInterface::outputAssembly()
cout << endl << "======= " << src.first << " =======" << endl;
eth::Assembly assembly = m_assemblyStacks[src.first].assemble();
cout << assembly.assemble().toHex() << endl;
cout << assembly.out();
assembly.stream(cout, "", m_sourceCodes);
}
}