Perform stack height checks in assembly analysis phase.

This commit is contained in:
chriseth 2017-03-22 13:21:58 +01:00
parent f13c8af3f9
commit 580921b329
3 changed files with 148 additions and 37 deletions

View File

@ -51,11 +51,20 @@ bool AsmAnalyzer::analyze(Block const& _block)
{ {
if (!(ScopeFiller(m_scopes, m_errors))(_block)) if (!(ScopeFiller(m_scopes, m_errors))(_block))
return false; return false;
return (*this)(_block); return (*this)(_block);
} }
bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{
auto const& info = instructionInfo(_instruction.instruction);
m_stackHeight += info.ret - info.args;
return true;
}
bool AsmAnalyzer::operator()(assembly::Literal const& _literal) bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
{ {
++m_stackHeight;
if (!_literal.isNumber && _literal.value.size() > 32) if (!_literal.isNumber && _literal.value.size() > 32)
{ {
m_errors.push_back(make_shared<Error>( m_errors.push_back(make_shared<Error>(
@ -83,8 +92,12 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
)); ));
success = false; success = false;
} }
++m_stackHeight;
},
[&](Scope::Label const&)
{
++m_stackHeight;
}, },
[&](Scope::Label const&) {},
[&](Scope::Function const&) [&](Scope::Function const&)
{ {
m_errors.push_back(make_shared<Error>( m_errors.push_back(make_shared<Error>(
@ -97,14 +110,21 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
))) )))
{ {
} }
else if (!m_resolver || m_resolver(_identifier, IdentifierContext::RValue) == size_t(-1)) else
{ {
m_errors.push_back(make_shared<Error>( size_t stackSize(-1);
Error::Type::DeclarationError, if (m_resolver)
"Identifier not found.", stackSize = m_resolver(_identifier, IdentifierContext::RValue);
_identifier.location if (stackSize == size_t(-1))
)); {
success = false; m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Identifier not found.",
_identifier.location
));
success = false;
}
m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
} }
return success; return success;
} }
@ -113,8 +133,14 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{ {
bool success = true; bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed) for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
{
int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg)) if (!boost::apply_visitor(*this, arg))
success = false; success = false;
if (!expectDeposit(1, stackHeight, locationOf(arg)))
success = false;
}
// Parser already checks that the number of arguments is correct.
if (!(*this)(_instr.instruction)) if (!(*this)(_instr.instruction))
success = false; success = false;
return success; return success;
@ -122,13 +148,15 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{ {
return checkAssignment(_assignment.variableName); return checkAssignment(_assignment.variableName, size_t(-1));
} }
bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment) bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
{ {
int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_assignment.value); bool success = boost::apply_visitor(*this, *_assignment.value);
if (!checkAssignment(_assignment.variableName)) solAssert(m_stackHeight >= stackHeight, "Negative value size.");
if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight))
success = false; success = false;
return success; return success;
} }
@ -146,7 +174,14 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
for (auto const& var: _funDef.arguments + _funDef.returns) for (auto const& var: _funDef.arguments + _funDef.returns)
boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true; boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true;
return (*this)(_funDef.body); int const stackHeight = m_stackHeight;
m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
m_virtualVariablesInNextBlock = m_stackHeight;
bool success = (*this)(_funDef.body);
m_stackHeight = stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
@ -202,12 +237,16 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
)); ));
success = false; 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) for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
{
int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg)) if (!boost::apply_visitor(*this, arg))
success = false; success = false;
if (!expectDeposit(1, stackHeight, locationOf(arg)))
success = false;
}
m_stackHeight += returns - arguments;
return success; return success;
} }
@ -216,19 +255,42 @@ bool AsmAnalyzer::operator()(Block const& _block)
bool success = true; bool success = true;
m_currentScope = &scope(&_block); m_currentScope = &scope(&_block);
int const virtualVariablesInNextBlock = m_virtualVariablesInNextBlock;
m_virtualVariablesInNextBlock = 0;
int const initialStackHeight = m_stackHeight;
for (auto const& s: _block.statements) for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s)) if (!boost::apply_visitor(*this, s))
success = false; success = false;
for (auto const& identifier: scope(&_block).identifiers)
if (identifier.second.type() == typeid(Scope::Variable))
--m_stackHeight;
int const stackDiff = m_stackHeight - initialStackHeight + virtualVariablesInNextBlock;
if (stackDiff != 0)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Unbalanced stack at the end of a block: " +
(
stackDiff > 0 ?
to_string(stackDiff) + string(" surplus item(s).") :
to_string(-stackDiff) + string(" missing item(s).")
),
_block.location
));
success = false;
}
m_currentScope = m_currentScope->superScope; m_currentScope = m_currentScope->superScope;
return success; return success;
} }
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable) bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
{ {
if (!(*this)(_variable)) bool success = true;
return false; size_t variableSize(-1);
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
{ {
// Check that it is a variable // Check that it is a variable
@ -239,19 +301,69 @@ bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable)
"Assignment requires variable.", "Assignment requires variable.",
_variable.location _variable.location
)); ));
return false; success = false;
} }
else if (!boost::get<Scope::Variable>(*var).active)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable " + _variable.name + " used before it was declared.",
_variable.location
));
success = false;
}
variableSize = 1;
} }
else if (!m_resolver || m_resolver(_variable, IdentifierContext::LValue) == size_t(-1)) else if (m_resolver)
variableSize = m_resolver(_variable, IdentifierContext::LValue);
if (variableSize == size_t(-1))
{ {
m_errors.push_back(make_shared<Error>( m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError, Error::Type::DeclarationError,
"Variable not found.", "Variable not found or variable not lvalue.",
_variable.location _variable.location
)); ));
success = false;
}
if (_valueSize == size_t(-1))
_valueSize = variableSize == size_t(-1) ? 1 : variableSize;
m_stackHeight -= _valueSize;
if (_valueSize != variableSize && variableSize != size_t(-1))
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Variable size (" +
to_string(variableSize) +
") and value size (" +
to_string(_valueSize) +
") do not match.",
_variable.location
));
success = false;
}
return success;
}
bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location)
{
int stackDiff = m_stackHeight - _oldHeight;
if (stackDiff != _deposit)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Expected instruction(s) to deposit " +
boost::lexical_cast<string>(_deposit) +
" item(s) to the stack, but did deposit " +
boost::lexical_cast<string>(stackDiff) +
" item(s).",
_location
));
return false; return false;
} }
return true; else
return true;
} }
Scope& AsmAnalyzer::scope(Block const* _block) Scope& AsmAnalyzer::scope(Block const* _block)

View File

@ -53,7 +53,7 @@ struct Scope;
/** /**
* Performs the full analysis stage, calls the ScopeFiller internally, then resolves * Performs the full analysis stage, calls the ScopeFiller internally, then resolves
* references and performs other checks. * references and performs other checks.
* @todo Does not yet check for stack height issues. * If all these checks pass, code generation should not throw errors.
*/ */
class AsmAnalyzer: public boost::static_visitor<bool> class AsmAnalyzer: public boost::static_visitor<bool>
{ {
@ -63,7 +63,7 @@ public:
bool analyze(assembly::Block const& _block); bool analyze(assembly::Block const& _block);
bool operator()(assembly::Instruction const&) { return true; } bool operator()(assembly::Instruction const&);
bool operator()(assembly::Literal const& _literal); bool operator()(assembly::Literal const& _literal);
bool operator()(assembly::Identifier const&); bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
@ -76,9 +76,16 @@ public:
bool operator()(assembly::Block const& _block); bool operator()(assembly::Block const& _block);
private: private:
bool checkAssignment(assembly::Identifier const& _assignment); /// Verifies that a variable to be assigned to exists and has the same size
/// as the value, @a _valueSize, unless that is equal to -1.
bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1));
bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
Scope& scope(assembly::Block const* _block); Scope& scope(assembly::Block const* _block);
/// Number of excess stack slots generated by function arguments to take into account for
/// next block.
int m_virtualVariablesInNextBlock = 0;
int m_stackHeight = 0;
ExternalIdentifierAccess::Resolver const& m_resolver; ExternalIdentifierAccess::Resolver const& m_resolver;
Scope* m_currentScope = nullptr; Scope* m_currentScope = nullptr;
Scopes& m_scopes; Scopes& m_scopes;

View File

@ -185,7 +185,7 @@ public:
{ {
int height = m_state.assembly.deposit(); int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *it); boost::apply_visitor(*this, *it);
expectDeposit(1, height, locationOf(*it)); expectDeposit(1, height);
} }
(*this)(_instr.instruction); (*this)(_instr.instruction);
} }
@ -210,7 +210,7 @@ public:
{ {
int height = m_state.assembly.deposit(); int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_assignment.value); boost::apply_visitor(*this, *_assignment.value);
expectDeposit(1, height, locationOf(*_assignment.value)); expectDeposit(1, height);
m_state.assembly.setSourceLocation(_assignment.location); m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location); generateAssignment(_assignment.variableName, _assignment.location);
} }
@ -218,7 +218,7 @@ public:
{ {
int height = m_state.assembly.deposit(); int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_varDecl.value); boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(1, height, locationOf(*_varDecl.value)); expectDeposit(1, height);
auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name)); auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name));
var.stackHeight = height; var.stackHeight = height;
var.active = true; var.active = true;
@ -279,17 +279,9 @@ private:
return heightDiff; return heightDiff;
} }
void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location) void expectDeposit(int _deposit, int _oldHeight)
{ {
if (m_state.assembly.deposit() != _oldHeight + 1) solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit.");
m_state.addError(Error::Type::TypeError,
"Expected instruction(s) to deposit " +
boost::lexical_cast<string>(_deposit) +
" item(s) to the stack, but did deposit " +
boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) +
" item(s).",
_location
);
} }
/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set. /// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.