Skip to content

Commit 89c26f6

Browse files
authored
[analyzer][NFC] Reorganize Z3 report refutation
This change keeps existing behavior, namely that if we hit a Z3 timeout we will accept the report as "satisfiable". This prepares for the commit "Harden safeguards for Z3 query times". https://discourse.llvm.org/t/analyzer-rfc-taming-z3-query-times/79520 Reviewers: NagyDonat, haoNoQ, Xazax-hun, mikhailramalho, Szelethus Reviewed By: NagyDonat Pull Request: #95128
1 parent 7dd7d57 commit 89c26f6

File tree

12 files changed

+419
-126
lines changed

12 files changed

+419
-126
lines changed

clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -597,29 +597,6 @@ class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor {
597597
PathSensitiveBugReport &BR) override;
598598
};
599599

600-
/// The bug visitor will walk all the nodes in a path and collect all the
601-
/// constraints. When it reaches the root node, will create a refutation
602-
/// manager and check if the constraints are satisfiable
603-
class FalsePositiveRefutationBRVisitor final : public BugReporterVisitor {
604-
private:
605-
/// Holds the constraints in a given path
606-
ConstraintMap Constraints;
607-
608-
public:
609-
FalsePositiveRefutationBRVisitor();
610-
611-
void Profile(llvm::FoldingSetNodeID &ID) const override;
612-
613-
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
614-
BugReporterContext &BRC,
615-
PathSensitiveBugReport &BR) override;
616-
617-
void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode,
618-
PathSensitiveBugReport &BR) override;
619-
void addConstraints(const ExplodedNode *N,
620-
bool OverwriteConstraintsOnExistingSyms);
621-
};
622-
623600
/// The visitor detects NoteTags and displays the event notes they contain.
624601
class TagVisitor : public BugReporterVisitor {
625602
public:
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===- Z3CrosscheckVisitor.h - Crosscheck reports with Z3 -------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines the visitor and utilities around it for Z3 report
10+
// refutation.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_Z3CROSSCHECKVISITOR_H
15+
#define LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_Z3CROSSCHECKVISITOR_H
16+
17+
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
18+
19+
namespace clang::ento {
20+
21+
/// The bug visitor will walk all the nodes in a path and collect all the
22+
/// constraints. When it reaches the root node, will create a refutation
23+
/// manager and check if the constraints are satisfiable.
24+
class Z3CrosscheckVisitor final : public BugReporterVisitor {
25+
public:
26+
struct Z3Result {
27+
std::optional<bool> IsSAT = std::nullopt;
28+
};
29+
explicit Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result);
30+
31+
void Profile(llvm::FoldingSetNodeID &ID) const override;
32+
33+
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
34+
BugReporterContext &BRC,
35+
PathSensitiveBugReport &BR) override;
36+
37+
void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode,
38+
PathSensitiveBugReport &BR) override;
39+
40+
private:
41+
void addConstraints(const ExplodedNode *N,
42+
bool OverwriteConstraintsOnExistingSyms);
43+
44+
/// Holds the constraints in a given path.
45+
ConstraintMap Constraints;
46+
Z3Result &Result;
47+
};
48+
49+
/// The oracle will decide if a report should be accepted or rejected based on
50+
/// the results of the Z3 solver.
51+
class Z3CrosscheckOracle {
52+
public:
53+
enum Z3Decision {
54+
AcceptReport, // The report was SAT.
55+
RejectReport, // The report was UNSAT or UNDEF.
56+
};
57+
58+
/// Makes a decision for accepting or rejecting the report based on the
59+
/// result of the corresponding Z3 query.
60+
static Z3Decision
61+
interpretQueryResult(const Z3CrosscheckVisitor::Z3Result &Query);
62+
};
63+
64+
} // namespace clang::ento
65+
66+
#endif // LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_Z3CROSSCHECKVISITOR_H

clang/include/clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ class SMTConstraintManager : public clang::ento::SimpleConstraintManager {
3434
public:
3535
SMTConstraintManager(clang::ento::ExprEngine *EE,
3636
clang::ento::SValBuilder &SB)
37-
: SimpleConstraintManager(EE, SB) {}
37+
: SimpleConstraintManager(EE, SB) {
38+
Solver->setBoolParam("model", true); // Enable model finding
39+
Solver->setUnsignedParam("timeout", 15000 /*milliseconds*/);
40+
}
3841
virtual ~SMTConstraintManager() = default;
3942

4043
//===------------------------------------------------------------------===//

clang/lib/StaticAnalyzer/Core/BugReporter.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
3636
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
3737
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
38+
#include "clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h"
3839
#include "clang/StaticAnalyzer/Core/Checker.h"
3940
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
4041
#include "clang/StaticAnalyzer/Core/CheckerRegistryData.h"
@@ -86,6 +87,11 @@ STATISTIC(MaxValidBugClassSize,
8687
"The maximum number of bug reports in the same equivalence class "
8788
"where at least one report is valid (not suppressed)");
8889

90+
STATISTIC(NumTimesReportPassesZ3, "Number of reports passed Z3");
91+
STATISTIC(NumTimesReportRefuted, "Number of reports refuted by Z3");
92+
STATISTIC(NumTimesReportEQClassWasExhausted,
93+
"Number of times all reports of an equivalence class was refuted");
94+
8995
BugReporterVisitor::~BugReporterVisitor() = default;
9096

9197
void BugReporterContext::anchor() {}
@@ -2864,21 +2870,31 @@ std::optional<PathDiagnosticBuilder> PathDiagnosticBuilder::findValidReport(
28642870
// If crosscheck is enabled, remove all visitors, add the refutation
28652871
// visitor and check again
28662872
R->clearVisitors();
2867-
R->addVisitor<FalsePositiveRefutationBRVisitor>();
2873+
Z3CrosscheckVisitor::Z3Result CrosscheckResult;
2874+
R->addVisitor<Z3CrosscheckVisitor>(CrosscheckResult);
28682875

28692876
// We don't overwrite the notes inserted by other visitors because the
28702877
// refutation manager does not add any new note to the path
28712878
generateVisitorsDiagnostics(R, BugPath->ErrorNode, BRC);
2879+
switch (Z3CrosscheckOracle::interpretQueryResult(CrosscheckResult)) {
2880+
case Z3CrosscheckOracle::RejectReport:
2881+
++NumTimesReportRefuted;
2882+
R->markInvalid("Infeasible constraints", /*Data=*/nullptr);
2883+
continue;
2884+
case Z3CrosscheckOracle::AcceptReport:
2885+
++NumTimesReportPassesZ3;
2886+
break;
2887+
}
28722888
}
28732889

2874-
// Check if the bug is still valid
2875-
if (R->isValid())
2876-
return PathDiagnosticBuilder(
2877-
std::move(BRC), std::move(BugPath->BugPath), BugPath->Report,
2878-
BugPath->ErrorNode, std::move(visitorNotes));
2890+
assert(R->isValid());
2891+
return PathDiagnosticBuilder(std::move(BRC), std::move(BugPath->BugPath),
2892+
BugPath->Report, BugPath->ErrorNode,
2893+
std::move(visitorNotes));
28792894
}
28802895
}
28812896

2897+
++NumTimesReportEQClassWasExhausted;
28822898
return {};
28832899
}
28842900

clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3446,82 +3446,6 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC,
34463446
return nullptr;
34473447
}
34483448

3449-
//===----------------------------------------------------------------------===//
3450-
// Implementation of FalsePositiveRefutationBRVisitor.
3451-
//===----------------------------------------------------------------------===//
3452-
3453-
FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor()
3454-
: Constraints(ConstraintMap::Factory().getEmptyMap()) {}
3455-
3456-
void FalsePositiveRefutationBRVisitor::finalizeVisitor(
3457-
BugReporterContext &BRC, const ExplodedNode *EndPathNode,
3458-
PathSensitiveBugReport &BR) {
3459-
// Collect new constraints
3460-
addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true);
3461-
3462-
// Create a refutation manager
3463-
llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver();
3464-
ASTContext &Ctx = BRC.getASTContext();
3465-
3466-
// Add constraints to the solver
3467-
for (const auto &I : Constraints) {
3468-
const SymbolRef Sym = I.first;
3469-
auto RangeIt = I.second.begin();
3470-
3471-
llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr(
3472-
RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(),
3473-
/*InRange=*/true);
3474-
while ((++RangeIt) != I.second.end()) {
3475-
SMTConstraints = RefutationSolver->mkOr(
3476-
SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
3477-
RangeIt->From(), RangeIt->To(),
3478-
/*InRange=*/true));
3479-
}
3480-
3481-
RefutationSolver->addConstraint(SMTConstraints);
3482-
}
3483-
3484-
// And check for satisfiability
3485-
std::optional<bool> IsSAT = RefutationSolver->check();
3486-
if (!IsSAT)
3487-
return;
3488-
3489-
if (!*IsSAT)
3490-
BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext());
3491-
}
3492-
3493-
void FalsePositiveRefutationBRVisitor::addConstraints(
3494-
const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
3495-
// Collect new constraints
3496-
ConstraintMap NewCs = getConstraintMap(N->getState());
3497-
ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>();
3498-
3499-
// Add constraints if we don't have them yet
3500-
for (auto const &C : NewCs) {
3501-
const SymbolRef &Sym = C.first;
3502-
if (!Constraints.contains(Sym)) {
3503-
// This symbol is new, just add the constraint.
3504-
Constraints = CF.add(Constraints, Sym, C.second);
3505-
} else if (OverwriteConstraintsOnExistingSyms) {
3506-
// Overwrite the associated constraint of the Symbol.
3507-
Constraints = CF.remove(Constraints, Sym);
3508-
Constraints = CF.add(Constraints, Sym, C.second);
3509-
}
3510-
}
3511-
}
3512-
3513-
PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode(
3514-
const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) {
3515-
addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
3516-
return nullptr;
3517-
}
3518-
3519-
void FalsePositiveRefutationBRVisitor::Profile(
3520-
llvm::FoldingSetNodeID &ID) const {
3521-
static int Tag = 0;
3522-
ID.AddPointer(&Tag);
3523-
}
3524-
35253449
//===----------------------------------------------------------------------===//
35263450
// Implementation of TagVisitor.
35273451
//===----------------------------------------------------------------------===//

clang/lib/StaticAnalyzer/Core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ add_clang_library(clangStaticAnalyzerCore
5151
SymbolManager.cpp
5252
TextDiagnostics.cpp
5353
WorkList.cpp
54+
Z3CrosscheckVisitor.cpp
5455

5556
LINK_LIBS
5657
clangAST
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//===- Z3CrosscheckVisitor.cpp - Crosscheck reports with Z3 -----*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file declares the visitor and utilities around it for Z3 report
10+
// refutation.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h"
15+
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
16+
#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h"
17+
#include "llvm/ADT/Statistic.h"
18+
#include "llvm/Support/SMTAPI.h"
19+
20+
#define DEBUG_TYPE "Z3CrosscheckOracle"
21+
22+
STATISTIC(NumZ3QueriesDone, "Number of Z3 queries done");
23+
STATISTIC(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
24+
25+
STATISTIC(NumTimesZ3QueryAcceptsReport,
26+
"Number of Z3 queries accepting a report");
27+
STATISTIC(NumTimesZ3QueryRejectReport,
28+
"Number of Z3 queries rejecting a report");
29+
30+
using namespace clang;
31+
using namespace ento;
32+
33+
Z3CrosscheckVisitor::Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result)
34+
: Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result) {}
35+
36+
void Z3CrosscheckVisitor::finalizeVisitor(BugReporterContext &BRC,
37+
const ExplodedNode *EndPathNode,
38+
PathSensitiveBugReport &BR) {
39+
// Collect new constraints
40+
addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true);
41+
42+
// Create a refutation manager
43+
llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver();
44+
RefutationSolver->setBoolParam("model", true); // Enable model finding
45+
RefutationSolver->setUnsignedParam("timeout", 15000); // ms
46+
47+
ASTContext &Ctx = BRC.getASTContext();
48+
49+
// Add constraints to the solver
50+
for (const auto &[Sym, Range] : Constraints) {
51+
auto RangeIt = Range.begin();
52+
53+
llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr(
54+
RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(),
55+
/*InRange=*/true);
56+
while ((++RangeIt) != Range.end()) {
57+
SMTConstraints = RefutationSolver->mkOr(
58+
SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
59+
RangeIt->From(), RangeIt->To(),
60+
/*InRange=*/true));
61+
}
62+
RefutationSolver->addConstraint(SMTConstraints);
63+
}
64+
65+
// And check for satisfiability
66+
std::optional<bool> IsSAT = RefutationSolver->check();
67+
Result = Z3Result{IsSAT};
68+
}
69+
70+
void Z3CrosscheckVisitor::addConstraints(
71+
const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
72+
// Collect new constraints
73+
ConstraintMap NewCs = getConstraintMap(N->getState());
74+
ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>();
75+
76+
// Add constraints if we don't have them yet
77+
for (auto const &[Sym, Range] : NewCs) {
78+
if (!Constraints.contains(Sym)) {
79+
// This symbol is new, just add the constraint.
80+
Constraints = CF.add(Constraints, Sym, Range);
81+
} else if (OverwriteConstraintsOnExistingSyms) {
82+
// Overwrite the associated constraint of the Symbol.
83+
Constraints = CF.remove(Constraints, Sym);
84+
Constraints = CF.add(Constraints, Sym, Range);
85+
}
86+
}
87+
}
88+
89+
PathDiagnosticPieceRef
90+
Z3CrosscheckVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &,
91+
PathSensitiveBugReport &) {
92+
addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
93+
return nullptr;
94+
}
95+
96+
void Z3CrosscheckVisitor::Profile(llvm::FoldingSetNodeID &ID) const {
97+
static int Tag = 0;
98+
ID.AddPointer(&Tag);
99+
}
100+
101+
Z3CrosscheckOracle::Z3Decision Z3CrosscheckOracle::interpretQueryResult(
102+
const Z3CrosscheckVisitor::Z3Result &Query) {
103+
++NumZ3QueriesDone;
104+
105+
if (!Query.IsSAT.has_value()) {
106+
// For backward compatibility, let's accept the first timeout.
107+
++NumTimesZ3TimedOut;
108+
return AcceptReport;
109+
}
110+
111+
if (Query.IsSAT.value()) {
112+
++NumTimesZ3QueryAcceptsReport;
113+
return AcceptReport; // sat
114+
}
115+
116+
++NumTimesZ3QueryRejectReport;
117+
return RejectReport; // unsat
118+
}

0 commit comments

Comments
 (0)