Skip to content

Commit c608428

Browse files
committed
SMTChecker: Refactor processing invariants from the solver
Instead of extracting invariants as a single expression containing all predicates and their definitions, we represent invariants as a map from predicate names to the corresponding definitions. Additionally, we need to remember formal arguments of the predicate so we can apply substitutions to the definitions properly. I believe the previous representation was an artifact of how Z3 returns CHC model through its API. The new representation is more fitting for models extracted from SMT-LIB.
1 parent a448921 commit c608428

File tree

7 files changed

+65
-77
lines changed

7 files changed

+65
-77
lines changed

libsmtutil/CHCSmtLib2Interface.cpp

+12-15
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,18 @@ CHCSolverInterface::QueryResult CHCSmtLib2Interface::query(Expression const& _bl
102102
// However, with CHC solvers, the meaning is flipped, UNSAT -> UNSAFE and SAT -> SAFE.
103103
// So we have to flip the answer.
104104
if (boost::starts_with(response, "sat"))
105-
{
106-
auto maybeInvariants = invariantsFromSolverResponse(response);
107-
return {CheckResult::UNSATISFIABLE, maybeInvariants.value_or(Expression(true)), {}};
108-
}
109-
else if (boost::starts_with(response, "unsat"))
105+
return {CheckResult::UNSATISFIABLE, invariantsFromSolverResponse(response), {}};
106+
if (boost::starts_with(response, "unsat"))
110107
result = CheckResult::SATISFIABLE;
111108
else if (boost::starts_with(response, "unknown"))
112109
result = CheckResult::UNKNOWN;
113110
else
114111
result = CheckResult::ERROR;
115-
return {result, Expression(true), {}};
112+
return {result, {}, {}};
116113
}
117114
catch(smtutil::SMTSolverInteractionError const&)
118115
{
119-
return {CheckResult::ERROR, Expression(true), {}};
116+
return {CheckResult::ERROR, {}, {}};
120117
}
121118

122119
}
@@ -416,7 +413,7 @@ smtutil::Expression CHCSmtLib2Interface::ScopedParser::toSMTUtilExpression(SMTLi
416413
}
417414

418415

419-
std::optional<smtutil::Expression> CHCSmtLib2Interface::invariantsFromSolverResponse(std::string const& _response) const
416+
CHCSmtLib2Interface::Invariants CHCSmtLib2Interface::invariantsFromSolverResponse(std::string const& _response) const
420417
{
421418
std::stringstream ss(_response);
422419
std::string answer;
@@ -438,7 +435,7 @@ std::optional<smtutil::Expression> CHCSmtLib2Interface::invariantsFromSolverResp
438435
smtSolverInteractionRequire(parser.isEOF(), "Error during parsing CHC model");
439436
smtSolverInteractionRequire(!parsedOutput.empty(), "Error during parsing CHC model");
440437
auto& commands = parsedOutput.size() == 1 ? asSubExpressions(parsedOutput[0]) : parsedOutput;
441-
std::vector<Expression> definitions;
438+
Invariants definitions;
442439
for (auto& command: commands)
443440
{
444441
auto& args = asSubExpressions(command);
@@ -456,7 +453,7 @@ std::optional<smtutil::Expression> CHCSmtLib2Interface::invariantsFromSolverResp
456453
inlineLetExpressions(interpretation);
457454
ScopedParser scopedParser(m_context);
458455
auto const& formalArguments = asSubExpressions(args[2]);
459-
std::vector<Expression> predicateArgs;
456+
std::vector<std::string> predicateArguments;
460457
for (auto const& formalArgument: formalArguments)
461458
{
462459
smtSolverInteractionRequire(!isAtom(formalArgument), "Invalid format of CHC model");
@@ -465,7 +462,7 @@ std::optional<smtutil::Expression> CHCSmtLib2Interface::invariantsFromSolverResp
465462
smtSolverInteractionRequire(isAtom(nameSortPair[0]), "Invalid format of CHC model");
466463
SortPointer varSort = scopedParser.toSort(nameSortPair[1]);
467464
scopedParser.addVariableDeclaration(asAtom(nameSortPair[0]), varSort);
468-
predicateArgs.push_back(scopedParser.toSMTUtilExpression(nameSortPair[0]));
465+
predicateArguments.push_back(asAtom(nameSortPair[0]));
469466
}
470467

471468
auto parsedInterpretation = scopedParser.toSMTUtilExpression(interpretation);
@@ -476,11 +473,11 @@ std::optional<smtutil::Expression> CHCSmtLib2Interface::invariantsFromSolverResp
476473
return first.name < second.name;
477474
});
478475

479-
Expression predicate(asAtom(args[1]), predicateArgs, SortProvider::boolSort);
480-
// FIXME: Why do we need to represent the predicate as Expression?
481-
definitions.push_back(predicate == parsedInterpretation);
476+
std::string const& predicateName = asAtom(args[1]);
477+
smtSolverInteractionRequire(!definitions.contains(predicateName), "Predicates must be unique");
478+
definitions.emplace(predicateName, InvariantInfo{parsedInterpretation, predicateArguments});
482479
}
483-
return Expression::mkAnd(std::move(definitions));
480+
return definitions;
484481
}
485482

486483
namespace

libsmtutil/CHCSmtLib2Interface.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ class CHCSmtLib2Interface: public CHCSolverInterface
9494
/// Communicates with the solver via the callback. Throws SMTSolverError on error.
9595
virtual std::string querySolver(std::string const& _input);
9696

97-
/// Translates CHC solver response with a model to our representation of invariants. Returns None on error.
98-
std::optional<smtutil::Expression> invariantsFromSolverResponse(std::string const& _response) const;
97+
/// Translates CHC solver response with a model to our representation of invariants.
98+
Invariants invariantsFromSolverResponse(std::string const& _response) const;
9999

100100
std::set<std::string> collectVariableNames(Expression const& _expr) const;
101101

libsmtutil/CHCSolverInterface.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <libsmtutil/SolverInterface.h>
2626

2727
#include <map>
28+
#include <unordered_map>
2829
#include <vector>
2930

3031
namespace solidity::smtutil
@@ -49,10 +50,19 @@ class CHCSolverInterface : public SolverInterface
4950
std::map<unsigned, std::vector<unsigned>> edges;
5051
};
5152

53+
struct InvariantInfo
54+
{
55+
/// Predicate definition as SMT expression
56+
Expression expression;
57+
/// Names of the formal arguments of the predicate definition
58+
std::vector<std::string> variableNames;
59+
};
60+
/// Maps predicate to its definition as given by the solver and the formal arguments of the predicate
61+
using Invariants = std::unordered_map<std::string, InvariantInfo>;
5262
struct QueryResult
5363
{
5464
CheckResult answer;
55-
Expression invariant;
65+
Invariants invariants;
5666
CexGraph cex;
5767
};
5868
/// Takes a function application _expr and checks for reachability.

libsolidity/formal/CHC.cpp

+17-33
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@
3434
#include <libsolutil/Algorithms.h>
3535
#include <libsolutil/StringUtils.h>
3636

37-
#include <boost/algorithm/string.hpp>
38-
3937
#include <range/v3/algorithm/for_each.hpp>
38+
#include <range/v3/algorithm/none_of.hpp>
4039
#include <range/v3/view.hpp>
4140
#include <range/v3/view/enumerate.hpp>
4241
#include <range/v3/view/reverse.hpp>
@@ -1895,7 +1894,7 @@ CHCSolverInterface::QueryResult CHC::query(smtutil::Expression const& _query, la
18951894
2339_error,
18961895
"CHC: Requested query:\n" + smtLibCode
18971896
);
1898-
return {.answer = CheckResult::UNKNOWN, .invariant = smtutil::Expression(true), .cex = {}};
1897+
return {.answer = CheckResult::UNKNOWN, .invariants = {}, .cex = {}};
18991898
}
19001899
auto result = m_interface->query(_query);
19011900
switch (result.answer)
@@ -2137,7 +2136,7 @@ void CHC::checkVerificationTargets()
21372136
namespace
21382137
{
21392138
std::map<Predicate const*, std::set<std::string>> collectInvariants(
2140-
smtutil::Expression const& _proof,
2139+
CHCSmtLib2Interface::Invariants const& _invariants,
21412140
std::set<Predicate const*> const& _predicates,
21422141
ModelCheckerInvariants const& _invariantsSetting
21432142
)
@@ -2148,38 +2147,23 @@ std::map<Predicate const*, std::set<std::string>> collectInvariants(
21482147
if (_invariantsSetting.has(InvariantType::Reentrancy))
21492148
targets.insert("nondet_interface_");
21502149

2151-
std::map<std::string, std::pair<smtutil::Expression, smtutil::Expression>> equalities;
2152-
// Collect equalities where one of the sides is a predicate we're interested in.
2153-
util::BreadthFirstSearch<smtutil::Expression const*>{{&_proof}}.run([&](auto&& _expr, auto&& _addChild) {
2154-
if (_expr->name == "=")
2155-
for (auto const& t: targets)
2156-
{
2157-
auto arg0 = _expr->arguments.at(0);
2158-
auto arg1 = _expr->arguments.at(1);
2159-
if (boost::algorithm::starts_with(arg0.name, t))
2160-
equalities.insert({arg0.name, {arg0, std::move(arg1)}});
2161-
else if (boost::algorithm::starts_with(arg1.name, t))
2162-
equalities.insert({arg1.name, {arg1, std::move(arg0)}});
2163-
}
2164-
for (auto const& arg: _expr->arguments)
2165-
_addChild(&arg);
2166-
});
2167-
21682150
std::map<Predicate const*, std::set<std::string>> invariants;
2169-
for (auto pred: _predicates)
2151+
for (auto const* pred: _predicates)
21702152
{
2171-
auto predName = pred->functor().name;
2172-
if (!equalities.count(predName))
2153+
smtAssert(pred);
2154+
auto const& predName = pred->functor().name;
2155+
if (!_invariants.contains(predName))
2156+
continue;
2157+
if (ranges::none_of(targets, [&](auto const& _target) { return predName.starts_with(_target); }))
21732158
continue;
21742159

2175-
solAssert(pred->contextContract(), "");
2160+
smtAssert(pred->contextContract());
21762161

2177-
auto const& [predExpr, invExpr] = equalities.at(predName);
2162+
auto const& [definition, formalArguments] = _invariants.at(predName);
21782163

2179-
static std::set<std::string> const ignore{"true", "false"};
2180-
auto r = substitute(invExpr, pred->expressionSubstitution(predExpr));
2164+
auto r = substitute(definition, pred->expressionSubstitution(formalArguments));
21812165
// No point in reporting true/false as invariants.
2182-
if (!ignore.count(r.name))
2166+
if (r.name != "true" && r.name != "false")
21832167
invariants[pred].insert(toSolidityStr(r));
21842168
}
21852169
return invariants;
@@ -2205,7 +2189,7 @@ void CHC::checkAndReportTarget(
22052189
placeholder.constraints && placeholder.errorExpression == _target.errorId
22062190
);
22072191
auto const& location = _target.errorNode->location();
2208-
auto [result, invariant, model] = query(error(), location);
2192+
auto [result, invariants, model] = query(error(), location);
22092193
if (result == CheckResult::UNSATISFIABLE)
22102194
{
22112195
m_safeTargets[_target.errorNode].insert(_target);
@@ -2214,9 +2198,9 @@ void CHC::checkAndReportTarget(
22142198
predicates.insert(pred);
22152199
for (auto const* pred: m_nondetInterfaces | ranges::views::values)
22162200
predicates.insert(pred);
2217-
std::map<Predicate const*, std::set<std::string>> invariants = collectInvariants(invariant, predicates, m_settings.invariants);
2218-
for (auto pred: invariants | ranges::views::keys)
2219-
m_invariants[pred] += std::move(invariants.at(pred));
2201+
std::map<Predicate const*, std::set<std::string>> invariantStrings = collectInvariants(invariants, predicates, m_settings.invariants);
2202+
for (auto pred: invariantStrings | ranges::views::keys)
2203+
m_invariants[pred] += std::move(invariantStrings.at(pred));
22202204
}
22212205
else if (result == CheckResult::SATISFIABLE)
22222206
{

libsolidity/formal/Predicate.cpp

+15-15
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@
2727
#include <libsolidity/ast/TypeProvider.h>
2828

2929
#include <boost/algorithm/string/join.hpp>
30-
#include <boost/algorithm/string.hpp>
3130

3231
#include <range/v3/view.hpp>
3332
#include <utility>
3433

35-
using boost::algorithm::starts_with;
3634
using namespace solidity;
3735
using namespace solidity::smtutil;
3836
using namespace solidity::frontend;
@@ -434,28 +432,29 @@ std::pair<std::vector<std::optional<std::string>>, std::vector<VariableDeclarati
434432
return {formatExpressions(outValuesInScope, outTypes), localVarsInScope};
435433
}
436434

437-
std::map<std::string, std::string> Predicate::expressionSubstitution(smtutil::Expression const& _predExpr) const
435+
std::map<std::string, std::string> Predicate::expressionSubstitution(std::vector<std::string> const& _predArgs) const
438436
{
439437
std::map<std::string, std::string> subst;
440438
std::string predName = functor().name;
441439

442-
solAssert(contextContract(), "");
440+
smtAssert(contextContract());
443441
auto const& stateVars = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(*contextContract());
444442

445-
auto nArgs = _predExpr.arguments.size();
443+
auto const nArgs = _predArgs.size();
446444

447445
// The signature of an interface predicate is
448446
// interface(this, abiFunctions, (optionally) bytesConcatFunctions, cryptoFunctions, blockchainState, stateVariables).
449447
// An invariant for an interface predicate is a contract
450448
// invariant over its state, for example `x <= 0`.
451449
if (isInterface())
452450
{
451+
smtAssert(nArgs > 0);
453452
size_t shift = txValuesIndex();
454-
solAssert(starts_with(predName, "interface"), "");
455-
subst[_predExpr.arguments.at(0).name] = "address(this)";
456-
solAssert(nArgs == stateVars.size() + shift, "");
453+
smtAssert(predName.starts_with("interface"));
454+
subst[_predArgs.at(0)] = "address(this)";
455+
smtAssert(nArgs == stateVars.size() + shift);
457456
for (size_t i = nArgs - stateVars.size(); i < nArgs; ++i)
458-
subst[_predExpr.arguments.at(i).name] = stateVars.at(i - shift)->name();
457+
subst[_predArgs.at(i)] = stateVars.at(i - shift)->name();
459458
}
460459
// The signature of a nondet interface predicate is
461460
// nondet_interface(error, this, abiFunctions, (optionally) bytesConcatFunctions, cryptoFunctions, blockchainState, stateVariables, blockchainState', stateVariables').
@@ -466,14 +465,15 @@ std::map<std::string, std::string> Predicate::expressionSubstitution(smtutil::Ex
466465
// `(x <= 0) => (x' <= 100)`.
467466
else if (isNondetInterface())
468467
{
469-
solAssert(starts_with(predName, "nondet_interface"), "");
470-
subst[_predExpr.arguments.at(0).name] = "<errorCode>";
471-
subst[_predExpr.arguments.at(1).name] = "address(this)";
472-
solAssert(nArgs == stateVars.size() * 2 + firstArgIndex(), "");
468+
smtAssert(nArgs > 1);
469+
smtAssert(predName.starts_with("nondet_interface"));
470+
subst[_predArgs.at(0)] = "<errorCode>";
471+
subst[_predArgs.at(1)] = "address(this)";
472+
smtAssert(nArgs == stateVars.size() * 2 + firstArgIndex());
473473
for (size_t i = nArgs - stateVars.size(), s = 0; i < nArgs; ++i, ++s)
474-
subst[_predExpr.arguments.at(i).name] = stateVars.at(s)->name() + "'";
474+
subst[_predArgs.at(i)] = stateVars.at(s)->name() + "'";
475475
for (size_t i = nArgs - (stateVars.size() * 2 + 1), s = 0; i < nArgs - (stateVars.size() + 1); ++i, ++s)
476-
subst[_predExpr.arguments.at(i).name] = stateVars.at(s)->name();
476+
subst[_predArgs.at(i)] = stateVars.at(s)->name();
477477
}
478478

479479
return subst;

libsolidity/formal/Predicate.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ class Predicate
179179
/// @returns the values of the local variables used by this predicate.
180180
std::pair<std::vector<std::optional<std::string>>, std::vector<VariableDeclaration const*>> localVariableValues(std::vector<smtutil::Expression> const& _args) const;
181181

182-
/// @returns a substitution map from the arguments of _predExpr
182+
/// @returns a substitution map from the predicate arguments @p _predArgs
183183
/// to a Solidity-like expression.
184-
std::map<std::string, std::string> expressionSubstitution(smtutil::Expression const& _predExprs) const;
184+
std::map<std::string, std::string> expressionSubstitution(std::vector<std::string> const& _predArgs) const;
185185

186186
private:
187187
/// Recursively fills _array from _expr.

libsolidity/formal/Z3CHCSmtLib2Interface.cpp

+6-9
Original file line numberDiff line numberDiff line change
@@ -90,26 +90,23 @@ CHCSolverInterface::QueryResult Z3CHCSmtLib2Interface::query(smtutil::Expression
9090
#endif
9191
setupSmtCallback(true);
9292
if (!boost::starts_with(response, "unsat"))
93-
return {CheckResult::SATISFIABLE, Expression(true), {}};
94-
return {CheckResult::SATISFIABLE, Expression(true), graphFromZ3Answer(response)};
93+
return {CheckResult::SATISFIABLE, {}, {}};
94+
return {CheckResult::SATISFIABLE, {}, graphFromZ3Answer(response)};
9595
}
9696

9797
CheckResult result;
9898
if (boost::starts_with(response, "sat"))
99-
{
100-
auto maybeInvariants = invariantsFromSolverResponse(response);
101-
return {CheckResult::UNSATISFIABLE, maybeInvariants.value_or(Expression(true)), {}};
102-
}
103-
else if (boost::starts_with(response, "unknown"))
99+
return {CheckResult::UNSATISFIABLE, invariantsFromSolverResponse(response), {}};
100+
if (boost::starts_with(response, "unknown"))
104101
result = CheckResult::UNKNOWN;
105102
else
106103
result = CheckResult::ERROR;
107104

108-
return {result, Expression(true), {}};
105+
return {result, {}, {}};
109106
}
110107
catch(smtutil::SMTSolverInteractionError const&)
111108
{
112-
return {CheckResult::ERROR, Expression(true), {}};
109+
return {CheckResult::ERROR, {}, {}};
113110
}
114111
}
115112

0 commit comments

Comments
 (0)