mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #2993 from ethereum/trackVariables
SMT checker for various things
This commit is contained in:
commit
f47604c14b
@ -23,9 +23,12 @@
|
||||
#include <libsolidity/formal/SMTLib2Interface.h>
|
||||
#endif
|
||||
|
||||
#include <libsolidity/formal/VariableUsage.h>
|
||||
|
||||
#include <libsolidity/interface/ErrorReporter.h>
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -44,28 +47,15 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback con
|
||||
|
||||
void SMTChecker::analyze(SourceUnit const& _source)
|
||||
{
|
||||
m_variableUsage = make_shared<VariableUsage>(_source);
|
||||
if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
|
||||
{
|
||||
m_interface->reset();
|
||||
m_currentSequenceCounter.clear();
|
||||
m_nextFreeSequenceCounter.clear();
|
||||
_source.accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
if (_varDecl.value())
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
_varDecl.location(),
|
||||
"Assertion checker does not yet support this."
|
||||
);
|
||||
}
|
||||
else if (_varDecl.isLocalOrReturn())
|
||||
createVariable(_varDecl, true);
|
||||
else if (_varDecl.isCallableParameter())
|
||||
createVariable(_varDecl, false);
|
||||
if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value())
|
||||
assignment(_varDecl, *_varDecl.value());
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(FunctionDefinition const& _function)
|
||||
@ -75,20 +65,22 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
|
||||
_function.location(),
|
||||
"Assertion checker does not yet support constructors and functions with modifiers."
|
||||
);
|
||||
// TODO actually we probably also have to reset all local variables and similar things.
|
||||
m_currentFunction = &_function;
|
||||
m_interface->push();
|
||||
// We only handle local variables, so we clear at the beginning of the function.
|
||||
// If we add storage variables, those should be cleared differently.
|
||||
m_interface->reset();
|
||||
m_currentSequenceCounter.clear();
|
||||
m_nextFreeSequenceCounter.clear();
|
||||
m_conditionalExecutionHappened = false;
|
||||
initializeLocalVariables(_function);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SMTChecker::endVisit(FunctionDefinition const&)
|
||||
{
|
||||
// TOOD we could check for "reachability", i.e. satisfiability here.
|
||||
// We only handle local variables, so we clear everything.
|
||||
// We only handle local variables, so we clear at the beginning of the function.
|
||||
// If we add storage variables, those should be cleared differently.
|
||||
m_currentSequenceCounter.clear();
|
||||
m_nextFreeSequenceCounter.clear();
|
||||
m_interface->pop();
|
||||
m_currentFunction = nullptr;
|
||||
}
|
||||
|
||||
@ -96,57 +88,84 @@ bool SMTChecker::visit(IfStatement const& _node)
|
||||
{
|
||||
_node.condition().accept(*this);
|
||||
|
||||
// TODO Check if condition is always true
|
||||
checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE.");
|
||||
|
||||
auto countersAtStart = m_currentSequenceCounter;
|
||||
m_interface->push();
|
||||
m_interface->addAssertion(expr(_node.condition()));
|
||||
_node.trueStatement().accept(*this);
|
||||
auto countersAtEndOfTrue = m_currentSequenceCounter;
|
||||
m_interface->pop();
|
||||
|
||||
decltype(m_currentSequenceCounter) countersAtEndOfFalse;
|
||||
visitBranch(_node.trueStatement(), expr(_node.condition()));
|
||||
vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement());
|
||||
if (_node.falseStatement())
|
||||
{
|
||||
m_currentSequenceCounter = countersAtStart;
|
||||
m_interface->push();
|
||||
m_interface->addAssertion(!expr(_node.condition()));
|
||||
_node.falseStatement()->accept(*this);
|
||||
countersAtEndOfFalse = m_currentSequenceCounter;
|
||||
m_interface->pop();
|
||||
visitBranch(*_node.falseStatement(), !expr(_node.condition()));
|
||||
touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement());
|
||||
}
|
||||
else
|
||||
countersAtEndOfFalse = countersAtStart;
|
||||
|
||||
// Reset all values that have been touched.
|
||||
resetVariables(touchedVariables);
|
||||
|
||||
// TODO this should use a previously generated side-effect structure
|
||||
|
||||
solAssert(countersAtEndOfFalse.size() == countersAtEndOfTrue.size(), "");
|
||||
for (auto const& declCounter: countersAtEndOfTrue)
|
||||
{
|
||||
solAssert(countersAtEndOfFalse.count(declCounter.first), "");
|
||||
auto decl = declCounter.first;
|
||||
int trueCounter = countersAtEndOfTrue.at(decl);
|
||||
int falseCounter = countersAtEndOfFalse.at(decl);
|
||||
if (trueCounter == falseCounter)
|
||||
continue; // Was not modified
|
||||
newValue(*decl);
|
||||
setValue(*decl, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(WhileStatement const& _node)
|
||||
{
|
||||
_node.condition().accept(*this);
|
||||
auto touchedVariables = m_variableUsage->touchedVariables(_node);
|
||||
resetVariables(touchedVariables);
|
||||
if (_node.isDoWhile())
|
||||
{
|
||||
visitBranch(_node.body());
|
||||
// TODO the assertions generated in the body should still be active in the condition
|
||||
_node.condition().accept(*this);
|
||||
checkBooleanNotConstant(_node.condition(), "Do-while loop condition is always $VALUE.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_node.condition().accept(*this);
|
||||
checkBooleanNotConstant(_node.condition(), "While loop condition is always $VALUE.");
|
||||
|
||||
//m_interface->push();
|
||||
//m_interface->addAssertion(expr(_node.condition()));
|
||||
// TDOO clear knowledge (increment sequence numbers and add bounds assertions ) apart from assertions
|
||||
visitBranch(_node.body(), expr(_node.condition()));
|
||||
}
|
||||
resetVariables(touchedVariables);
|
||||
|
||||
// TODO combine similar to if
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(ForStatement const& _node)
|
||||
{
|
||||
if (_node.initializationExpression())
|
||||
_node.initializationExpression()->accept(*this);
|
||||
|
||||
// Do not reset the init expression part.
|
||||
auto touchedVariables =
|
||||
m_variableUsage->touchedVariables(_node.body());
|
||||
if (_node.condition())
|
||||
touchedVariables += m_variableUsage->touchedVariables(*_node.condition());
|
||||
if (_node.loopExpression())
|
||||
touchedVariables += m_variableUsage->touchedVariables(*_node.loopExpression());
|
||||
// Remove duplicates
|
||||
std::sort(touchedVariables.begin(), touchedVariables.end());
|
||||
touchedVariables.erase(std::unique(touchedVariables.begin(), touchedVariables.end()), touchedVariables.end());
|
||||
|
||||
resetVariables(touchedVariables);
|
||||
|
||||
if (_node.condition())
|
||||
{
|
||||
_node.condition()->accept(*this);
|
||||
checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE.");
|
||||
}
|
||||
|
||||
VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter;
|
||||
m_interface->push();
|
||||
if (_node.condition())
|
||||
m_interface->addAssertion(expr(*_node.condition()));
|
||||
_node.body().accept(*this);
|
||||
if (_node.loopExpression())
|
||||
_node.loopExpression()->accept(*this);
|
||||
|
||||
m_interface->pop();
|
||||
|
||||
m_conditionalExecutionHappened = true;
|
||||
m_currentSequenceCounter = sequenceCountersStart;
|
||||
|
||||
resetVariables(touchedVariables);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
|
||||
@ -160,8 +179,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
|
||||
{
|
||||
if (_varDecl.initialValue())
|
||||
// TODO more checks?
|
||||
// TODO add restrictions about type (might be assignment from smaller type)
|
||||
m_interface->addAssertion(newValue(*_varDecl.declarations()[0]) == expr(*_varDecl.initialValue()));
|
||||
assignment(*_varDecl.declarations()[0], *_varDecl.initialValue());
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
@ -190,9 +208,7 @@ void SMTChecker::endVisit(Assignment const& _assignment)
|
||||
{
|
||||
Declaration const* decl = identifier->annotation().referencedDeclaration;
|
||||
if (knownVariable(*decl))
|
||||
// TODO more checks?
|
||||
// TODO add restrictions about type (might be assignment from smaller type)
|
||||
m_interface->addAssertion(newValue(*decl) == expr(_assignment.rightHandSide()));
|
||||
assignment(*decl, _assignment.rightHandSide());
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
_assignment.location(),
|
||||
@ -269,23 +285,17 @@ void SMTChecker::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
Declaration const* decl = _identifier.annotation().referencedDeclaration;
|
||||
solAssert(decl, "");
|
||||
if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
|
||||
if (_identifier.annotation().lValueRequested)
|
||||
{
|
||||
m_interface->addAssertion(expr(_identifier) == currentValue(*decl));
|
||||
return;
|
||||
// Will be translated as part of the node that requested the lvalue.
|
||||
}
|
||||
else if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
|
||||
m_interface->addAssertion(expr(_identifier) == currentValue(*decl));
|
||||
else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
|
||||
{
|
||||
if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require)
|
||||
return;
|
||||
// TODO for others, clear our knowledge about storage and memory
|
||||
}
|
||||
m_errorReporter.warning(
|
||||
_identifier.location(),
|
||||
"Assertion checker does not yet support the type of this expression (" +
|
||||
_identifier.annotation().type->toString() +
|
||||
")."
|
||||
);
|
||||
}
|
||||
|
||||
void SMTChecker::endVisit(Literal const& _literal)
|
||||
@ -298,10 +308,12 @@ void SMTChecker::endVisit(Literal const& _literal)
|
||||
|
||||
m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal)));
|
||||
}
|
||||
else if (type.category() == Type::Category::Bool)
|
||||
m_interface->addAssertion(expr(_literal) == smt::Expression(_literal.token() == Token::TrueLiteral ? true : false));
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
_literal.location(),
|
||||
"Assertion checker does not yet support the type of this expression (" +
|
||||
"Assertion checker does not yet support the type of this literal (" +
|
||||
_literal.annotation().type->toString() +
|
||||
")."
|
||||
);
|
||||
@ -386,6 +398,7 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op)
|
||||
solAssert(_op.annotation().commonType, "");
|
||||
if (_op.annotation().commonType->category() == Type::Category::Bool)
|
||||
{
|
||||
// @TODO check that both of them are not constant
|
||||
if (_op.getOperator() == Token::And)
|
||||
m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression()));
|
||||
else
|
||||
@ -395,7 +408,33 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op)
|
||||
m_errorReporter.warning(
|
||||
_op.location(),
|
||||
"Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations"
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
void SMTChecker::assignment(Declaration const& _variable, Expression const& _value)
|
||||
{
|
||||
// TODO more checks?
|
||||
// TODO add restrictions about type (might be assignment from smaller type)
|
||||
m_interface->addAssertion(newValue(_variable) == expr(_value));
|
||||
}
|
||||
|
||||
void SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition)
|
||||
{
|
||||
visitBranch(_statement, &_condition);
|
||||
}
|
||||
|
||||
void SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition)
|
||||
{
|
||||
VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter;
|
||||
|
||||
m_interface->push();
|
||||
if (_condition)
|
||||
m_interface->addAssertion(*_condition);
|
||||
_statement.accept(*this);
|
||||
m_interface->pop();
|
||||
|
||||
m_conditionalExecutionHappened = true;
|
||||
m_currentSequenceCounter = sequenceCountersStart;
|
||||
}
|
||||
|
||||
void SMTChecker::checkCondition(
|
||||
@ -433,19 +472,13 @@ void SMTChecker::checkCondition(
|
||||
}
|
||||
smt::CheckResult result;
|
||||
vector<string> values;
|
||||
try
|
||||
{
|
||||
tie(result, values) = m_interface->check(expressionsToEvaluate);
|
||||
}
|
||||
catch (smt::SolverError const& _e)
|
||||
{
|
||||
string description("Error querying SMT solver");
|
||||
if (_e.comment())
|
||||
description += ": " + *_e.comment();
|
||||
m_errorReporter.warning(_location, description);
|
||||
return;
|
||||
}
|
||||
tie(result, values) = checkSatisifableAndGenerateModel(expressionsToEvaluate);
|
||||
|
||||
string conditionalComment;
|
||||
if (m_conditionalExecutionHappened)
|
||||
conditionalComment =
|
||||
"\nNote that some information is erased after conditional execution of parts of the code.\n"
|
||||
"You can re-introduce information using require().";
|
||||
switch (result)
|
||||
{
|
||||
case smt::CheckResult::SATISFIABLE:
|
||||
@ -457,27 +490,17 @@ void SMTChecker::checkCondition(
|
||||
message << " for:\n";
|
||||
solAssert(values.size() == expressionNames.size(), "");
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
{
|
||||
string formattedValue = values.at(i);
|
||||
try
|
||||
{
|
||||
// Parse and re-format nicely
|
||||
formattedValue = formatNumber(bigint(formattedValue));
|
||||
}
|
||||
catch (...) { }
|
||||
|
||||
message << " " << expressionNames.at(i) << " = " << formattedValue << "\n";
|
||||
}
|
||||
message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
|
||||
}
|
||||
else
|
||||
message << ".";
|
||||
m_errorReporter.warning(_location, message.str());
|
||||
m_errorReporter.warning(_location, message.str() + conditionalComment);
|
||||
break;
|
||||
}
|
||||
case smt::CheckResult::UNSATISFIABLE:
|
||||
break;
|
||||
case smt::CheckResult::UNKNOWN:
|
||||
m_errorReporter.warning(_location, _description + " might happen here.");
|
||||
m_errorReporter.warning(_location, _description + " might happen here." + conditionalComment);
|
||||
break;
|
||||
case smt::CheckResult::ERROR:
|
||||
m_errorReporter.warning(_location, "Error trying to invoke SMT solver.");
|
||||
@ -488,7 +511,110 @@ void SMTChecker::checkCondition(
|
||||
m_interface->pop();
|
||||
}
|
||||
|
||||
void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setToZero)
|
||||
void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string const& _description)
|
||||
{
|
||||
// Do not check for const-ness if this is a constant.
|
||||
if (dynamic_cast<Literal const*>(&_condition))
|
||||
return;
|
||||
|
||||
m_interface->push();
|
||||
m_interface->addAssertion(expr(_condition));
|
||||
auto positiveResult = checkSatisifable();
|
||||
m_interface->pop();
|
||||
|
||||
m_interface->push();
|
||||
m_interface->addAssertion(!expr(_condition));
|
||||
auto negatedResult = checkSatisifable();
|
||||
m_interface->pop();
|
||||
|
||||
if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR)
|
||||
m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver.");
|
||||
else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE)
|
||||
{
|
||||
// everything fine.
|
||||
}
|
||||
else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE)
|
||||
m_errorReporter.warning(_condition.location(), "Condition unreachable.");
|
||||
else
|
||||
{
|
||||
string value;
|
||||
if (positiveResult == smt::CheckResult::SATISFIABLE)
|
||||
{
|
||||
solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, "");
|
||||
value = "true";
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, "");
|
||||
solAssert(negatedResult == smt::CheckResult::SATISFIABLE, "");
|
||||
value = "false";
|
||||
}
|
||||
m_errorReporter.warning(_condition.location(), boost::algorithm::replace_all_copy(_description, "$VALUE", value));
|
||||
}
|
||||
}
|
||||
|
||||
pair<smt::CheckResult, vector<string>>
|
||||
SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate)
|
||||
{
|
||||
smt::CheckResult result;
|
||||
vector<string> values;
|
||||
try
|
||||
{
|
||||
tie(result, values) = m_interface->check(_expressionsToEvaluate);
|
||||
}
|
||||
catch (smt::SolverError const& _e)
|
||||
{
|
||||
string description("Error querying SMT solver");
|
||||
if (_e.comment())
|
||||
description += ": " + *_e.comment();
|
||||
m_errorReporter.warning(description);
|
||||
result = smt::CheckResult::ERROR;
|
||||
}
|
||||
|
||||
for (string& value: values)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse and re-format nicely
|
||||
value = formatNumber(bigint(value));
|
||||
}
|
||||
catch (...) { }
|
||||
}
|
||||
|
||||
return make_pair(result, values);
|
||||
}
|
||||
|
||||
smt::CheckResult SMTChecker::checkSatisifable()
|
||||
{
|
||||
return checkSatisifableAndGenerateModel({}).first;
|
||||
}
|
||||
|
||||
void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function)
|
||||
{
|
||||
for (auto const& variable: _function.localVariables())
|
||||
if (createVariable(*variable))
|
||||
setZeroValue(*variable);
|
||||
|
||||
for (auto const& param: _function.parameters())
|
||||
if (createVariable(*param))
|
||||
setUnknownValue(*param);
|
||||
|
||||
if (_function.returnParameterList())
|
||||
for (auto const& retParam: _function.returnParameters())
|
||||
if (createVariable(*retParam))
|
||||
setZeroValue(*retParam);
|
||||
}
|
||||
|
||||
void SMTChecker::resetVariables(vector<Declaration const*> _variables)
|
||||
{
|
||||
for (auto const* decl: _variables)
|
||||
{
|
||||
newValue(*decl);
|
||||
setUnknownValue(*decl);
|
||||
}
|
||||
}
|
||||
|
||||
bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
if (dynamic_cast<IntegerType const*>(_varDecl.type().get()))
|
||||
{
|
||||
@ -498,13 +624,16 @@ void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setTo
|
||||
m_currentSequenceCounter[&_varDecl] = 0;
|
||||
m_nextFreeSequenceCounter[&_varDecl] = 1;
|
||||
m_variables.emplace(&_varDecl, m_interface->newFunction(uniqueSymbol(_varDecl), smt::Sort::Int, smt::Sort::Int));
|
||||
setValue(_varDecl, _setToZero);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
_varDecl.location(),
|
||||
"Assertion checker does not yet support the type of this variable."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string SMTChecker::uniqueSymbol(Declaration const& _decl)
|
||||
@ -535,23 +664,22 @@ smt::Expression SMTChecker::valueAtSequence(const Declaration& _decl, int _seque
|
||||
|
||||
smt::Expression SMTChecker::newValue(Declaration const& _decl)
|
||||
{
|
||||
solAssert(m_currentSequenceCounter.count(&_decl), "");
|
||||
solAssert(m_nextFreeSequenceCounter.count(&_decl), "");
|
||||
m_currentSequenceCounter[&_decl] = m_nextFreeSequenceCounter[&_decl]++;
|
||||
return currentValue(_decl);
|
||||
}
|
||||
|
||||
void SMTChecker::setValue(Declaration const& _decl, bool _setToZero)
|
||||
void SMTChecker::setZeroValue(Declaration const& _decl)
|
||||
{
|
||||
solAssert(_decl.type()->category() == Type::Category::Integer, "");
|
||||
m_interface->addAssertion(currentValue(_decl) == 0);
|
||||
}
|
||||
|
||||
void SMTChecker::setUnknownValue(Declaration const& _decl)
|
||||
{
|
||||
auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type());
|
||||
|
||||
if (_setToZero)
|
||||
m_interface->addAssertion(currentValue(_decl) == 0);
|
||||
else
|
||||
{
|
||||
m_interface->addAssertion(currentValue(_decl) >= minValue(intType));
|
||||
m_interface->addAssertion(currentValue(_decl) <= maxValue(intType));
|
||||
}
|
||||
m_interface->addAssertion(currentValue(_decl) >= minValue(intType));
|
||||
m_interface->addAssertion(currentValue(_decl) <= maxValue(intType));
|
||||
}
|
||||
|
||||
smt::Expression SMTChecker::minValue(IntegerType const& _t)
|
||||
|
@ -17,8 +17,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
#include <libsolidity/formal/SolverInterface.h>
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
|
||||
#include <map>
|
||||
@ -29,6 +32,7 @@ namespace dev
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class VariableUsage;
|
||||
class ErrorReporter;
|
||||
|
||||
class SMTChecker: private ASTConstVisitor
|
||||
@ -48,6 +52,7 @@ private:
|
||||
virtual void endVisit(FunctionDefinition const& _node) override;
|
||||
virtual bool visit(IfStatement const& _node) override;
|
||||
virtual bool visit(WhileStatement const& _node) override;
|
||||
virtual bool visit(ForStatement const& _node) override;
|
||||
virtual void endVisit(VariableDeclarationStatement const& _node) override;
|
||||
virtual void endVisit(ExpressionStatement const& _node) override;
|
||||
virtual void endVisit(Assignment const& _node) override;
|
||||
@ -61,6 +66,14 @@ private:
|
||||
void compareOperation(BinaryOperation const& _op);
|
||||
void booleanOperation(BinaryOperation const& _op);
|
||||
|
||||
void assignment(Declaration const& _variable, Expression const& _value);
|
||||
|
||||
// Visits the branch given by the statement, pushes and pops the SMT checker.
|
||||
// @param _condition if present, asserts that this condition is true within the branch.
|
||||
void visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr);
|
||||
void visitBranch(Statement const& _statement, smt::Expression _condition);
|
||||
|
||||
/// Check that a condition can be satisfied.
|
||||
void checkCondition(
|
||||
smt::Expression _condition,
|
||||
SourceLocation const& _location,
|
||||
@ -68,8 +81,24 @@ private:
|
||||
std::string const& _additionalValueName = "",
|
||||
smt::Expression* _additionalValue = nullptr
|
||||
);
|
||||
/// Checks that a boolean condition is not constant. Do not warn if the expression
|
||||
/// is a literal constant.
|
||||
/// @param _description the warning string, $VALUE will be replaced by the constant value.
|
||||
void checkBooleanNotConstant(
|
||||
Expression const& _condition,
|
||||
std::string const& _description
|
||||
);
|
||||
|
||||
void createVariable(VariableDeclaration const& _varDecl, bool _setToZero);
|
||||
std::pair<smt::CheckResult, std::vector<std::string>>
|
||||
checkSatisifableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
|
||||
|
||||
smt::CheckResult checkSatisifable();
|
||||
|
||||
void initializeLocalVariables(FunctionDefinition const& _function);
|
||||
void resetVariables(std::vector<Declaration const*> _variables);
|
||||
/// Tries to create an uninitialized variable and returns true on success.
|
||||
/// This fails if the type is not supported.
|
||||
bool createVariable(VariableDeclaration const& _varDecl);
|
||||
|
||||
static std::string uniqueSymbol(Declaration const& _decl);
|
||||
static std::string uniqueSymbol(Expression const& _expr);
|
||||
@ -87,12 +116,16 @@ private:
|
||||
/// sequence number to this value and returns the expression.
|
||||
smt::Expression newValue(Declaration const& _decl);
|
||||
|
||||
/// Sets the value of the declaration either to zero or to its intrinsic range.
|
||||
void setValue(Declaration const& _decl, bool _setToZero);
|
||||
/// Sets the value of the declaration to zero.
|
||||
void setZeroValue(Declaration const& _decl);
|
||||
/// Resets the variable to an unknown value (in its range).
|
||||
void setUnknownValue(Declaration const& decl);
|
||||
|
||||
static smt::Expression minValue(IntegerType const& _t);
|
||||
static smt::Expression maxValue(IntegerType const& _t);
|
||||
|
||||
using VariableSequenceCounters = std::map<Declaration const*, int>;
|
||||
|
||||
/// Returns the expression corresponding to the AST node. Creates a new expression
|
||||
/// if it does not exist yet.
|
||||
smt::Expression expr(Expression const& _e);
|
||||
@ -101,6 +134,8 @@ private:
|
||||
smt::Expression var(Declaration const& _decl);
|
||||
|
||||
std::shared_ptr<smt::SolverInterface> m_interface;
|
||||
std::shared_ptr<VariableUsage> m_variableUsage;
|
||||
bool m_conditionalExecutionHappened = false;
|
||||
std::map<Declaration const*, int> m_currentSequenceCounter;
|
||||
std::map<Declaration const*, int> m_nextFreeSequenceCounter;
|
||||
std::map<Expression const*, smt::Expression> m_expressions;
|
||||
|
@ -52,6 +52,7 @@ class Expression
|
||||
{
|
||||
friend class SolverInterface;
|
||||
public:
|
||||
explicit Expression(bool _v): name(_v ? "true" : "false") {}
|
||||
Expression(size_t _number): name(std::to_string(_number)) {}
|
||||
Expression(u256 const& _number): name(_number.str()) {}
|
||||
Expression(bigint const& _number): name(_number.str()) {}
|
||||
|
80
libsolidity/formal/VariableUsage.cpp
Normal file
80
libsolidity/formal/VariableUsage.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/formal/VariableUsage.h>
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
VariableUsage::VariableUsage(ASTNode const& _node)
|
||||
{
|
||||
auto nodeFun = [&](ASTNode const& n) -> bool
|
||||
{
|
||||
if (Identifier const* identifier = dynamic_cast<decltype(identifier)>(&n))
|
||||
{
|
||||
Declaration const* declaration = identifier->annotation().referencedDeclaration;
|
||||
solAssert(declaration, "");
|
||||
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
if (
|
||||
varDecl->isLocalVariable() &&
|
||||
identifier->annotation().lValueRequested &&
|
||||
varDecl->annotation().type->isValueType()
|
||||
)
|
||||
m_touchedVariable[&n] = varDecl;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto edgeFun = [&](ASTNode const& _parent, ASTNode const& _child)
|
||||
{
|
||||
if (m_touchedVariable.count(&_child) || m_children.count(&_child))
|
||||
m_children[&_parent].push_back(&_child);
|
||||
};
|
||||
|
||||
ASTReduce reducer(nodeFun, edgeFun);
|
||||
_node.accept(reducer);
|
||||
}
|
||||
|
||||
vector<Declaration const*> VariableUsage::touchedVariables(ASTNode const& _node) const
|
||||
{
|
||||
if (!m_children.count(&_node) && !m_touchedVariable.count(&_node))
|
||||
return {};
|
||||
|
||||
set<Declaration const*> touched;
|
||||
vector<ASTNode const*> toVisit;
|
||||
toVisit.push_back(&_node);
|
||||
|
||||
while (!toVisit.empty())
|
||||
{
|
||||
ASTNode const* n = toVisit.back();
|
||||
toVisit.pop_back();
|
||||
if (m_children.count(n))
|
||||
{
|
||||
solAssert(!m_touchedVariable.count(n), "");
|
||||
toVisit += m_children.at(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(m_touchedVariable.count(n), "");
|
||||
touched.insert(m_touchedVariable.at(n));
|
||||
}
|
||||
}
|
||||
|
||||
return {touched.begin(), touched.end()};
|
||||
}
|
50
libsolidity/formal/VariableUsage.h
Normal file
50
libsolidity/formal/VariableUsage.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class ASTNode;
|
||||
class Declaration;
|
||||
|
||||
/**
|
||||
* This class collects information about which local variables of value type
|
||||
* are modified in which parts of the AST.
|
||||
*/
|
||||
class VariableUsage
|
||||
{
|
||||
public:
|
||||
explicit VariableUsage(ASTNode const& _node);
|
||||
|
||||
std::vector<Declaration const*> touchedVariables(ASTNode const& _node) const;
|
||||
|
||||
private:
|
||||
// Variable touched by a specific AST node.
|
||||
std::map<ASTNode const*, Declaration const*> m_touchedVariable;
|
||||
std::map<ASTNode const*, std::vector<ASTNode const*>> m_children;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -91,7 +91,7 @@ pair<CheckResult, vector<string>> Z3Interface::check(vector<Expression> const& _
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
if (result != CheckResult::UNSATISFIABLE)
|
||||
if (result != CheckResult::UNSATISFIABLE && !_expressionsToEvaluate.empty())
|
||||
{
|
||||
z3::model m = m_solver.get_model();
|
||||
for (Expression const& e: _expressionsToEvaluate)
|
||||
@ -139,8 +139,13 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
|
||||
}
|
||||
else if (arguments.empty())
|
||||
{
|
||||
// We assume it is an integer...
|
||||
return m_context.int_val(n.c_str());
|
||||
if (n == "true")
|
||||
return m_context.bool_val(true);
|
||||
else if (n == "false")
|
||||
return m_context.bool_val(false);
|
||||
else
|
||||
// We assume it is an integer...
|
||||
return m_context.int_val(n.c_str());
|
||||
}
|
||||
|
||||
solAssert(arity.count(n) && arity.at(n) == arguments.size(), "");
|
||||
|
@ -105,6 +105,359 @@ BOOST_AUTO_TEST_CASE(warn_on_struct)
|
||||
CHECK_WARNING_ALLOW_MULTI(text, "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(simple_assert)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint a) public pure { assert(a == 2); }
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here for");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(simple_assert_with_require)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint a) public pure { require(a < 10); assert(a < 20); }
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(assignment_in_declaration)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f() public pure { uint a = 2; assert(a == 2); }
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(use_before_declaration)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f() public pure { a = 3; uint a = 2; assert(a == 2); }
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
text = R"(
|
||||
contract C {
|
||||
function f() public pure { assert(a == 0); uint a = 2; assert(a == 2); }
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_call_does_not_clear_local_vars)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f() public {
|
||||
uint a = 3;
|
||||
this.f();
|
||||
assert(a == 3);
|
||||
f();
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(branches_clear_variables)
|
||||
{
|
||||
// Only clears accessed variables
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
uint a = 3;
|
||||
if (x > 10) {
|
||||
}
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
// It is just a plain clear and will not combine branches.
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
uint a = 3;
|
||||
if (x > 10) {
|
||||
a = 3;
|
||||
}
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
// Clear also works on the else branch
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
uint a = 3;
|
||||
if (x > 10) {
|
||||
} else {
|
||||
a = 3;
|
||||
}
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
// Variable is not cleared, if it is only read.
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
uint a = 3;
|
||||
if (x > 10) {
|
||||
assert(a == 3);
|
||||
} else {
|
||||
assert(a == 3);
|
||||
}
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(branches_assert_condition)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
if (x > 10) {
|
||||
assert(x > 9);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(x < 11);
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
if (x > 10) {
|
||||
assert(x > 9);
|
||||
}
|
||||
else if (x > 2)
|
||||
{
|
||||
assert(x <= 10 && x > 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(0 <= x && x <= 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ways_to_clear_variables)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
uint a = 3;
|
||||
if (x > 10) {
|
||||
a++;
|
||||
}
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
uint a = 3;
|
||||
if (x > 10) {
|
||||
++a;
|
||||
}
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
uint a = 3;
|
||||
if (x > 10) {
|
||||
a = 5;
|
||||
}
|
||||
assert(a == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(while_loop_simple)
|
||||
{
|
||||
// Check that variables are cleared
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
x = 2;
|
||||
while (x > 1) {
|
||||
x = 2;
|
||||
}
|
||||
assert(x == 2);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
// Check that condition is assumed.
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
while (x == 2) {
|
||||
assert(x == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
// Check that condition is not assumed after the body anymore
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
while (x == 2) {
|
||||
}
|
||||
assert(x == 2);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
// Check that negation of condition is not assumed after the body anymore
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
while (x == 2) {
|
||||
}
|
||||
assert(x != 2);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
// Check that side-effects of condition are taken into account
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
x = 7;
|
||||
while ((x = 5) > 0) {
|
||||
}
|
||||
assert(x == 7);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation happens here");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(constant_condition)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
if (x >= 0) { revert(); }
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Condition is always true");
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
if (x >= 10) { if (x < 10) { revert(); } }
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Condition is always false");
|
||||
// a plain literal constant is fine
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint) public pure {
|
||||
if (true) { revert(); }
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_loop)
|
||||
{
|
||||
string text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
require(x == 2);
|
||||
for (;;) {}
|
||||
assert(x == 2);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
for (; x == 2; ) {
|
||||
assert(x == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
for (uint y = 2; x < 10; ) {
|
||||
assert(y == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
for (uint y = 2; x < 10; y = 3) {
|
||||
assert(y == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation");
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
for (uint y = 2; x < 10; ) {
|
||||
y = 3;
|
||||
}
|
||||
assert(y == 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation");
|
||||
text = R"(
|
||||
contract C {
|
||||
function f(uint x) public pure {
|
||||
for (uint y = 2; x < 10; ) {
|
||||
y = 3;
|
||||
}
|
||||
assert(y == 2);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "Assertion violation");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user