clang 24.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
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.
28STAT_COUNTER(NumZ3QueriesDone, "Number of Z3 queries done");
29STAT_COUNTER(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
30STAT_COUNTER(NumTimesZ3ExhaustedRLimit,
31 "Number of times Z3 query exhausted the rlimit");
33 NumTimesZ3SpendsTooMuchTimeOnASingleEQClass,
34 "Number of times report equivalenece class was cut because it spent "
35 "too much time in Z3");
36
37STAT_COUNTER(NumTimesZ3QueryAcceptsReport,
38 "Number of Z3 queries accepting a report");
39STAT_COUNTER(NumTimesZ3QueryRejectReport,
40 "Number of Z3 queries rejecting a report");
41STAT_COUNTER(NumTimesZ3QueryRejectEQClass,
42 "Number of times rejecting an report equivalenece class");
43
44STAT_COUNTER(TimeSpentSolvingZ3Queries,
45 "Total time spent solving Z3 queries excluding retries");
46STAT_MAX(MaxTimeSpentSolvingZ3Queries,
47 "Max time spent solving a Z3 query excluding retries");
48
49using namespace clang;
50using namespace ento;
51
53 const AnalyzerOptions &Opts)
54 : Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result),
55 Opts(Opts) {}
56
60 // Collect new constraints
61 addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true);
62
63 // Create a refutation manager
64 llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver();
65 if (Opts.Z3CrosscheckRLimitThreshold)
66 RefutationSolver->setUnsignedParam("rlimit",
67 Opts.Z3CrosscheckRLimitThreshold);
68 if (Opts.Z3CrosscheckTimeoutThreshold)
69 RefutationSolver->setUnsignedParam("timeout",
70 Opts.Z3CrosscheckTimeoutThreshold); // ms
71
72 ASTContext &Ctx = BRC.getASTContext();
73
74 // Add constraints to the solver
75 for (const auto &[Sym, Range] : Constraints) {
76 auto RangeIt = Range.begin();
77
78 llvm::SMTExprRef SMTConstraints =
79 SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym, RangeIt->From(),
80 RangeIt->To(),
81 /*InRange=*/true)
82 .value();
83 while ((++RangeIt) != Range.end()) {
84 SMTConstraints = RefutationSolver->mkOr(
85 SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
86 RangeIt->From(), RangeIt->To(),
87 /*InRange=*/true)
88 .value());
89 }
90 RefutationSolver->addConstraint(SMTConstraints);
91 }
92
93 auto GetUsedRLimit = [](const llvm::SMTSolverRef &Solver) {
94 return Solver->getStatistics()->getUnsigned("rlimit count");
95 };
96
97 auto AttemptOnce = [&](const llvm::SMTSolverRef &Solver) -> Z3Result {
98 auto getCurrentTime = llvm::TimeRecord::getCurrentTime;
99 unsigned InitialRLimit = GetUsedRLimit(Solver);
100 double Start = getCurrentTime(/*Start=*/true).getWallTime();
101 std::optional<bool> IsSAT = Solver->check();
102 double End = getCurrentTime(/*Start=*/false).getWallTime();
103 return {
104 IsSAT,
105 static_cast<unsigned>((End - Start) * 1000),
106 GetUsedRLimit(Solver) - InitialRLimit,
107 };
108 };
109
110 // And check for satisfiability
111 unsigned MinQueryTimeAcrossAttempts = std::numeric_limits<unsigned>::max();
112 for (unsigned I = 0; I < Opts.Z3CrosscheckMaxAttemptsPerQuery; ++I) {
113 Result = AttemptOnce(RefutationSolver);
114 Result.Z3QueryTimeMilliseconds =
115 std::min(MinQueryTimeAcrossAttempts, Result.Z3QueryTimeMilliseconds);
116 if (Result.IsSAT.has_value())
117 return;
118 }
119}
120
121void Z3CrosscheckVisitor::addConstraints(
122 const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
123 // Collect new constraints
125 ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>();
126
127 // Add constraints if we don't have them yet
128 for (auto const &[Sym, Range] : NewCs) {
129 if (!Constraints.contains(Sym)) {
130 // This symbol is new, just add the constraint.
131 Constraints = CF.add(Constraints, Sym, Range);
132 } else if (OverwriteConstraintsOnExistingSyms) {
133 // Overwrite the associated constraint of the Symbol.
134 Constraints = CF.remove(Constraints, Sym);
135 Constraints = CF.add(Constraints, Sym, Range);
136 }
137 }
138}
139
143 addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
144 return nullptr;
145}
146
147void Z3CrosscheckVisitor::Profile(llvm::FoldingSetNodeID &ID) const {
148 static int Tag = 0;
149 ID.AddPointer(&Tag);
150}
151
153 const Z3CrosscheckVisitor::Z3Result &Query) {
154 ++NumZ3QueriesDone;
155 AccumulatedZ3QueryTimeInEqClass += Query.Z3QueryTimeMilliseconds;
156 TimeSpentSolvingZ3Queries += Query.Z3QueryTimeMilliseconds;
157 MaxTimeSpentSolvingZ3Queries.updateMax(Query.Z3QueryTimeMilliseconds);
158
159 if (Query.IsSAT && Query.IsSAT.value()) {
160 ++NumTimesZ3QueryAcceptsReport;
161 return AcceptReport;
162 }
163
164 // Suggest cutting the EQClass if certain heuristics trigger.
165 if (Opts.Z3CrosscheckTimeoutThreshold &&
166 Query.Z3QueryTimeMilliseconds >= Opts.Z3CrosscheckTimeoutThreshold) {
167 ++NumTimesZ3TimedOut;
168 ++NumTimesZ3QueryRejectEQClass;
169 return RejectEQClass;
170 }
171
172 if (Opts.Z3CrosscheckRLimitThreshold &&
173 Query.UsedRLimit >= Opts.Z3CrosscheckRLimitThreshold) {
174 ++NumTimesZ3ExhaustedRLimit;
175 ++NumTimesZ3QueryRejectEQClass;
176 return RejectEQClass;
177 }
178
179 if (Opts.Z3CrosscheckEQClassTimeoutThreshold &&
180 AccumulatedZ3QueryTimeInEqClass >
181 Opts.Z3CrosscheckEQClassTimeoutThreshold) {
182 ++NumTimesZ3SpendsTooMuchTimeOnASingleEQClass;
183 ++NumTimesZ3QueryRejectEQClass;
184 return RejectEQClass;
185 }
186
187 // If no cutoff heuristics trigger, and the report is "unsat" or "undef",
188 // then reject the report.
189 ++NumTimesZ3QueryRejectReport;
190 return RejectReport;
191}
#define STAT_COUNTER(VARNAME, DESC)
#define STAT_MAX(VARNAME, DESC)
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition ASTContext.h:223
Stores options for the analyzer from the command line.
ASTContext & getASTContext() const
const ProgramStateRef & getState() const
A Range represents the closed range [from, to].
static std::optional< llvm::SMTExprRef > getRangeExpr(llvm::SMTSolverRef &Solver, ASTContext &Ctx, SymbolRef Sym, const llvm::APSInt &From, const llvm::APSInt &To, bool InRange)
Definition SMTConv.h:600
Z3Decision interpretQueryResult(const Z3CrosscheckVisitor::Z3Result &Meta)
Updates the internal state with the new Z3Result and makes a decision how to proceed:
void finalizeVisitor(const ExplodedNode *EndPathNode, BugReporterContext &BRC, PathSensitiveBugReport &BR) override
Last function called on the visitor, no further calls to VisitNode would follow.
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)
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.