Merge pull request #10721 from blishko/smt-try-catch

[SMTChecker] Support try-catch statements
This commit is contained in:
Leonardo
2021-01-12 12:04:38 +01:00
committed by GitHub
27 changed files with 538 additions and 24 deletions
+38
View File
@@ -345,6 +345,44 @@ bool BMC::visit(ForStatement const& _node)
return false;
}
bool BMC::visit(TryStatement const& _tryStatement)
{
FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall());
solAssert(externalCall && externalCall->annotation().tryCall, "");
externalCall->accept(*this);
auto callExpr = expr(*externalCall);
if (_tryStatement.successClause()->parameters())
tryCatchAssignment(
_tryStatement.successClause()->parameters()->parameters(), *m_context.expression(*externalCall)
);
smtutil::Expression clauseId = m_context.newVariable("clause_choice_" + to_string(m_context.newUniqueId()), smtutil::SortProvider::uintSort);
auto const& clauses = _tryStatement.clauses();
m_context.addAssertion(clauseId >= 0 && clauseId < clauses.size());
solAssert(clauses[0].get() == _tryStatement.successClause(), "First clause of TryStatement should be the success clause");
vector<set<VariableDeclaration const*>> touchedVars;
vector<pair<VariableIndices, smtutil::Expression>> clausesVisitResults;
for (size_t i = 0; i < clauses.size(); ++i)
{
clausesVisitResults.push_back(visitBranch(clauses[i].get()));
touchedVars.push_back(touchedVariables(*clauses[i]));
}
// merge the information from all clauses
smtutil::Expression pathCondition = clausesVisitResults.front().second;
auto currentIndices = clausesVisitResults[0].first;
for (size_t i = 1; i < clauses.size(); ++i)
{
mergeVariables(touchedVars[i - 1] + touchedVars[i], clauseId == i, clausesVisitResults[i].first, currentIndices);
currentIndices = copyVariableIndices();
pathCondition = pathCondition || clausesVisitResults[i].second;
}
setPathCondition(pathCondition);
return false;
}
void BMC::endVisit(UnaryOperation const& _op)
{
SMTEncoder::endVisit(_op);
+1
View File
@@ -91,6 +91,7 @@ private:
void endVisit(UnaryOperation const& _node) override;
void endVisit(FunctionCall const& _node) override;
void endVisit(Return const& _node) override;
bool visit(TryStatement const& _node) override;
//@}
/// Visitor helpers.
+42
View File
@@ -541,6 +541,48 @@ void CHC::endVisit(Return const& _return)
m_currentBlock = predicate(*returnGhost);
}
bool CHC::visit(TryStatement const& _tryStatement)
{
FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall());
solAssert(externalCall && externalCall->annotation().tryCall, "");
solAssert(m_currentFunction, "");
auto tryHeaderBlock = createBlock(&_tryStatement, PredicateType::FunctionBlock, "try_header_");
auto afterTryBlock = createBlock(&m_currentFunction->body(), PredicateType::FunctionBlock);
auto const& clauses = _tryStatement.clauses();
solAssert(clauses[0].get() == _tryStatement.successClause(), "First clause of TryStatement should be the success clause");
auto clauseBlocks = applyMap(clauses, [this](ASTPointer<TryCatchClause> clause) {
return createBlock(clause.get(), PredicateType::FunctionBlock, "try_clause_" + std::to_string(clause->id()));
});
connectBlocks(m_currentBlock, predicate(*tryHeaderBlock));
setCurrentBlock(*tryHeaderBlock);
// Visit everything, except the actual external call.
externalCall->expression().accept(*this);
ASTNode::listAccept(externalCall->arguments(), *this);
// Branch directly to all catch clauses, since in these cases, any effects of the external call are reverted.
for (size_t i = 1; i < clauseBlocks.size(); ++i)
connectBlocks(m_currentBlock, predicate(*clauseBlocks[i]));
// Only now visit the actual call to record its effects and connect to the success clause.
endVisit(*externalCall);
if (_tryStatement.successClause()->parameters())
tryCatchAssignment(
_tryStatement.successClause()->parameters()->parameters(), *m_context.expression(*externalCall)
);
connectBlocks(m_currentBlock, predicate(*clauseBlocks[0]));
for (size_t i = 0; i < clauses.size(); ++i)
{
setCurrentBlock(*clauseBlocks[i]);
clauses[i]->accept(*this);
connectBlocks(m_currentBlock, predicate(*afterTryBlock));
}
setCurrentBlock(*afterTryBlock);
return false;
}
void CHC::pushInlineFrame(CallableDeclaration const& _callable)
{
m_returnDests.push_back(createBlock(&_callable, PredicateType::FunctionBlock, "return_"));
+1
View File
@@ -84,6 +84,7 @@ private:
void endVisit(Continue const& _node) override;
void endVisit(IndexRangeAccess const& _node) override;
void endVisit(Return const& _node) override;
bool visit(TryStatement const& _node) override;
void pushInlineFrame(CallableDeclaration const& _callable) override;
void popInlineFrame(CallableDeclaration const& _callable) override;
+43 -15
View File
@@ -312,20 +312,6 @@ bool SMTEncoder::visit(InlineAssembly const& _inlineAsm)
return false;
}
bool SMTEncoder::visit(TryCatchClause const& _clause)
{
if (auto params = _clause.parameters())
for (auto const& var: params->parameters())
createVariable(*var);
m_errorReporter.warning(
7645_error,
_clause.location(),
"Assertion checker does not support try/catch clauses."
);
return false;
}
void SMTEncoder::pushInlineFrame(CallableDeclaration const&)
{
pushPathCondition(currentPathConditions());
@@ -2121,6 +2107,26 @@ smtutil::Expression SMTEncoder::compoundAssignment(Assignment const& _assignment
return values.first;
}
void SMTEncoder::tryCatchAssignment(vector<shared_ptr<VariableDeclaration>> const& _variables, smt::SymbolicVariable const& _rhs)
{
if (_variables.size() > 1)
{
auto const* symbTuple = dynamic_cast<smt::SymbolicTupleVariable const*>(&_rhs);
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
solAssert(symbComponents.size() == _variables.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto param = _variables.at(i);
solAssert(param, "");
solAssert(m_context.knownVariable(*param), "");
assignment(*param, symbTuple->component(i));
}
}
else if (_variables.size() == 1)
assignment(*_variables.front(), _rhs.currentValue());
}
void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression const& _value)
{
// In general, at this point, the SMT sorts of _variable and _value are the same,
@@ -2688,7 +2694,29 @@ vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedA
vector<VariableDeclaration const*> SMTEncoder::localVariablesIncludingModifiers(FunctionDefinition const& _function, ContractDefinition const* _contract)
{
return _function.localVariables() + modifiersVariables(_function, _contract);
return _function.localVariables() + tryCatchVariables(_function) + modifiersVariables(_function, _contract);
}
vector<VariableDeclaration const*> SMTEncoder::tryCatchVariables(FunctionDefinition const& _function)
{
struct TryCatchVarsVisitor : public ASTConstVisitor
{
bool visit(TryCatchClause const& _catchClause) override
{
if (_catchClause.parameters())
{
auto const& params = _catchClause.parameters()->parameters();
for (auto param: params)
vars.push_back(param.get());
}
return false;
}
vector<VariableDeclaration const*> vars;
} tryCatchVarsVisitor;
_function.accept(tryCatchVarsVisitor);
return tryCatchVarsVisitor.vars;
}
vector<VariableDeclaration const*> SMTEncoder::modifiersVariables(FunctionDefinition const& _function, ContractDefinition const* _contract)
+4 -1
View File
@@ -76,6 +76,7 @@ public:
static std::vector<VariableDeclaration const*> localVariablesIncludingModifiers(FunctionDefinition const& _function, ContractDefinition const* _contract);
static std::vector<VariableDeclaration const*> modifiersVariables(FunctionDefinition const& _function, ContractDefinition const* _contract);
static std::vector<VariableDeclaration const*> tryCatchVariables(FunctionDefinition const& _function);
/// @returns the ModifierDefinition of a ModifierInvocation if possible, or nullptr.
static ModifierDefinition const* resolveModifierInvocation(ModifierInvocation const& _invocation, ContractDefinition const* _contract);
@@ -130,7 +131,7 @@ protected:
bool visit(InlineAssembly const& _node) override;
void endVisit(Break const&) override {}
void endVisit(Continue const&) override {}
bool visit(TryCatchClause const& _node) override;
bool visit(TryStatement const&) override { return false; }
virtual void pushInlineFrame(CallableDeclaration const&);
virtual void popInlineFrame(CallableDeclaration const&);
@@ -237,6 +238,8 @@ protected:
void tupleAssignment(Expression const& _left, Expression const& _right);
/// Computes the right hand side of a compound assignment.
smtutil::Expression compoundAssignment(Assignment const& _assignment);
/// Handles assignment of the result of external call in try statement to the parameters of success clause
void tryCatchAssignment(std::vector<std::shared_ptr<VariableDeclaration>> const& _variables, smt::SymbolicVariable const& rhs);
/// Maps a variable to an SSA index.
using VariableIndices = std::unordered_map<VariableDeclaration const*, int>;