clang 20.0.0git
Z3CrosscheckVisitor.cpp
Go to the documentation of this file.
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
18#include "llvm/ADT/Statistic.h"
19#include "llvm/Support/SMTAPI.h"
20#include "llvm/Support/Timer.h"
21
22#define DEBUG_TYPE "Z3CrosscheckOracle"
23
24// Queries attempted at most `Z3CrosscheckMaxAttemptsPerQuery` number of times.
25// Multiple `check()` calls might be called on the same query if previous
26// attempts of the same query resulted in UNSAT for any reason. Each query is
27// only counted once for these statistics, the retries are not accounted for.
28STATISTIC(NumZ3QueriesDone, "Number of Z3 queries done");
29STATISTIC(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
30STATISTIC(NumTimesZ3ExhaustedRLimit,
31 "Number of times Z3 query exhausted the rlimit");
32STATISTIC(NumTimesZ3SpendsTooMuchTimeOnASingleEQClass,
33 "Number of times report equivalenece class was cut because it spent "
34 "too much time in Z3");
35
36STATISTIC(NumTimesZ3QueryAcceptsReport,
37 "Number of Z3 queries accepting a report");
38STATISTIC(NumTimesZ3QueryRejectReport,
39 "Number of Z3 queries rejecting a report");
40STATISTIC(NumTimesZ3QueryRejectEQClass,
41 "Number of times rejecting an report equivalenece class");
42
43using namespace clang;
44using namespace ento;
45
47 const AnalyzerOptions &Opts)
48 : Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result),
49 Opts(Opts) {}
50
52 const ExplodedNode *EndPathNode,
54 // Collect new constraints
55 addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true);
56
57 // Create a refutation manager
58 llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver();
59 if (Opts.Z3CrosscheckRLimitThreshold)
60 RefutationSolver->setUnsignedParam("rlimit",
61 Opts.Z3CrosscheckRLimitThreshold);
62 if (Opts.Z3CrosscheckTimeoutThreshold)
63 RefutationSolver->setUnsignedParam("timeout",
64 Opts.Z3CrosscheckTimeoutThreshold); // ms
65
66 ASTContext &Ctx = BRC.getASTContext();
67
68 // Add constraints to the solver
69 for (const auto &[Sym, Range] : Constraints) {
70 auto RangeIt = Range.begin();
71
72 llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr(
73 RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(),
74 /*InRange=*/true);
75 while ((++RangeIt) != Range.end()) {
76 SMTConstraints = RefutationSolver->mkOr(
77 SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
78 RangeIt->From(), RangeIt->To(),
79 /*InRange=*/true));
80 }
81 RefutationSolver->addConstraint(SMTConstraints);
82 }
83
84 auto GetUsedRLimit = [](const llvm::SMTSolverRef &Solver) {
85 return Solver->getStatistics()->getUnsigned("rlimit count");
86 };
87
88 auto AttemptOnce = [&](const llvm::SMTSolverRef &Solver) -> Z3Result {
89 constexpr auto getCurrentTime = llvm::TimeRecord::getCurrentTime;
90 unsigned InitialRLimit = GetUsedRLimit(Solver);
91 double Start = getCurrentTime(/*Start=*/true).getWallTime();
92 std::optional<bool> IsSAT = Solver->check();
93 double End = getCurrentTime(/*Start=*/false).getWallTime();
94 return {
95 IsSAT,
96 static_cast<unsigned>((End - Start) * 1000),
97 GetUsedRLimit(Solver) - InitialRLimit,
98 };
99 };
100
101 // And check for satisfiability
102 unsigned MinQueryTimeAcrossAttempts = std::numeric_limits<unsigned>::max();
103 for (unsigned I = 0; I < Opts.Z3CrosscheckMaxAttemptsPerQuery; ++I) {
104 Result = AttemptOnce(RefutationSolver);
106 std::min(MinQueryTimeAcrossAttempts, Result.Z3QueryTimeMilliseconds);
107 if (Result.IsSAT.has_value())
108 return;
109 }
110}
111
112void Z3CrosscheckVisitor::addConstraints(
113 const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
114 // Collect new constraints
116 ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>();
117
118 // Add constraints if we don't have them yet
119 for (auto const &[Sym, Range] : NewCs) {
120 if (!Constraints.contains(Sym)) {
121 // This symbol is new, just add the constraint.
122 Constraints = CF.add(Constraints, Sym, Range);
123 } else if (OverwriteConstraintsOnExistingSyms) {
124 // Overwrite the associated constraint of the Symbol.
125 Constraints = CF.remove(Constraints, Sym);
126 Constraints = CF.add(Constraints, Sym, Range);
127 }
128 }
129}
130
134 addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
135 return nullptr;
136}
137
138void Z3CrosscheckVisitor::Profile(llvm::FoldingSetNodeID &ID) const {
139 static int Tag = 0;
140 ID.AddPointer(&Tag);
141}
142
144 const Z3CrosscheckVisitor::Z3Result &Query) {
145 ++NumZ3QueriesDone;
146 AccumulatedZ3QueryTimeInEqClass += Query.Z3QueryTimeMilliseconds;
147
148 if (Query.IsSAT && Query.IsSAT.value()) {
149 ++NumTimesZ3QueryAcceptsReport;
150 return AcceptReport;
151 }
152
153 // Suggest cutting the EQClass if certain heuristics trigger.
154 if (Opts.Z3CrosscheckTimeoutThreshold &&
155 Query.Z3QueryTimeMilliseconds >= Opts.Z3CrosscheckTimeoutThreshold) {
156 ++NumTimesZ3TimedOut;
157 ++NumTimesZ3QueryRejectEQClass;
158 return RejectEQClass;
159 }
160
161 if (Opts.Z3CrosscheckRLimitThreshold &&
162 Query.UsedRLimit >= Opts.Z3CrosscheckRLimitThreshold) {
163 ++NumTimesZ3ExhaustedRLimit;
164 ++NumTimesZ3QueryRejectEQClass;
165 return RejectEQClass;
166 }
167
168 if (Opts.Z3CrosscheckEQClassTimeoutThreshold &&
169 AccumulatedZ3QueryTimeInEqClass >
170 Opts.Z3CrosscheckEQClassTimeoutThreshold) {
171 ++NumTimesZ3SpendsTooMuchTimeOnASingleEQClass;
172 ++NumTimesZ3QueryRejectEQClass;
173 return RejectEQClass;
174 }
175
176 // If no cutoff heuristics trigger, and the report is "unsat" or "undef",
177 // then reject the report.
178 ++NumTimesZ3QueryRejectReport;
179 return RejectReport;
180}
STATISTIC(NumZ3QueriesDone, "Number of Z3 queries done")
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:188
Stores options for the analyzer from the command line.
ASTContext & getASTContext() const
Definition: BugReporter.h:733
const ProgramStateRef & getState() const
A Range represents the closed range [from, to].
static llvm::SMTExprRef getRangeExpr(llvm::SMTSolverRef &Solver, ASTContext &Ctx, SymbolRef Sym, const llvm::APSInt &From, const llvm::APSInt &To, bool InRange)
Definition: SMTConv.h:532
Z3Decision interpretQueryResult(const Z3CrosscheckVisitor::Z3Result &Meta)
Updates the internal state with the new Z3Result and makes a decision how to proceed:
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) override
Return a diagnostic piece which should be associated with the given node.
void Profile(llvm::FoldingSetNodeID &ID) const override
Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result, const AnalyzerOptions &Opts)
void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode, PathSensitiveBugReport &BR) override
Last function called on the visitor, no further calls to VisitNode would follow.
llvm::ImmutableMap< SymbolRef, RangeSet > ConstraintMap
@ CF
Indicates that the tracked object is a CF object.
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
ConstraintMap getConstraintMap(ProgramStateRef State)
The JSON file list parser is used to communicate input to InstallAPI.