xref: /llvm-project/clang/lib/StaticAnalyzer/Core/Z3CrosscheckVisitor.cpp (revision 55391f85acc7e7a14ea2ef3c1a4bd8f3df990426)
1b3b0d09cSBalazs Benics //===- Z3CrosscheckVisitor.cpp - Crosscheck reports with Z3 -----*- C++ -*-===//
2b3b0d09cSBalazs Benics //
3b3b0d09cSBalazs Benics // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4b3b0d09cSBalazs Benics // See https://llvm.org/LICENSE.txt for license information.
5b3b0d09cSBalazs Benics // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6b3b0d09cSBalazs Benics //
7b3b0d09cSBalazs Benics //===----------------------------------------------------------------------===//
8b3b0d09cSBalazs Benics //
9b3b0d09cSBalazs Benics //  This file declares the visitor and utilities around it for Z3 report
10b3b0d09cSBalazs Benics //  refutation.
11b3b0d09cSBalazs Benics //
12b3b0d09cSBalazs Benics //===----------------------------------------------------------------------===//
13b3b0d09cSBalazs Benics 
14b3b0d09cSBalazs Benics #include "clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h"
15ae570d82SBalazs Benics #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
16b3b0d09cSBalazs Benics #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
17b3b0d09cSBalazs Benics #include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h"
18b3b0d09cSBalazs Benics #include "llvm/ADT/Statistic.h"
19b3b0d09cSBalazs Benics #include "llvm/Support/SMTAPI.h"
20ae570d82SBalazs Benics #include "llvm/Support/Timer.h"
21b3b0d09cSBalazs Benics 
22b3b0d09cSBalazs Benics #define DEBUG_TYPE "Z3CrosscheckOracle"
23b3b0d09cSBalazs Benics 
24*55391f85SBalazs Benics // Queries attempted at most `Z3CrosscheckMaxAttemptsPerQuery` number of times.
25*55391f85SBalazs Benics // Multiple `check()` calls might be called on the same query if previous
26*55391f85SBalazs Benics // attempts of the same query resulted in UNSAT for any reason. Each query is
27*55391f85SBalazs Benics // only counted once for these statistics, the retries are not accounted for.
28b3b0d09cSBalazs Benics STATISTIC(NumZ3QueriesDone, "Number of Z3 queries done");
29b3b0d09cSBalazs Benics STATISTIC(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
30ae570d82SBalazs Benics STATISTIC(NumTimesZ3ExhaustedRLimit,
31ae570d82SBalazs Benics           "Number of times Z3 query exhausted the rlimit");
32ae570d82SBalazs Benics STATISTIC(NumTimesZ3SpendsTooMuchTimeOnASingleEQClass,
33ae570d82SBalazs Benics           "Number of times report equivalenece class was cut because it spent "
34ae570d82SBalazs Benics           "too much time in Z3");
35b3b0d09cSBalazs Benics 
36b3b0d09cSBalazs Benics STATISTIC(NumTimesZ3QueryAcceptsReport,
37b3b0d09cSBalazs Benics           "Number of Z3 queries accepting a report");
38b3b0d09cSBalazs Benics STATISTIC(NumTimesZ3QueryRejectReport,
39b3b0d09cSBalazs Benics           "Number of Z3 queries rejecting a report");
40ae570d82SBalazs Benics STATISTIC(NumTimesZ3QueryRejectEQClass,
41ae570d82SBalazs Benics           "Number of times rejecting an report equivalenece class");
42b3b0d09cSBalazs Benics 
43b3b0d09cSBalazs Benics using namespace clang;
44b3b0d09cSBalazs Benics using namespace ento;
45b3b0d09cSBalazs Benics 
46ae570d82SBalazs Benics Z3CrosscheckVisitor::Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result,
47ae570d82SBalazs Benics                                          const AnalyzerOptions &Opts)
48ae570d82SBalazs Benics     : Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result),
49ae570d82SBalazs Benics       Opts(Opts) {}
50b3b0d09cSBalazs Benics 
51b3b0d09cSBalazs Benics void Z3CrosscheckVisitor::finalizeVisitor(BugReporterContext &BRC,
52b3b0d09cSBalazs Benics                                           const ExplodedNode *EndPathNode,
53b3b0d09cSBalazs Benics                                           PathSensitiveBugReport &BR) {
54b3b0d09cSBalazs Benics   // Collect new constraints
55b3b0d09cSBalazs Benics   addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true);
56b3b0d09cSBalazs Benics 
57b3b0d09cSBalazs Benics   // Create a refutation manager
58b3b0d09cSBalazs Benics   llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver();
59ae570d82SBalazs Benics   if (Opts.Z3CrosscheckRLimitThreshold)
60ae570d82SBalazs Benics     RefutationSolver->setUnsignedParam("rlimit",
61ae570d82SBalazs Benics                                        Opts.Z3CrosscheckRLimitThreshold);
62ae570d82SBalazs Benics   if (Opts.Z3CrosscheckTimeoutThreshold)
63ae570d82SBalazs Benics     RefutationSolver->setUnsignedParam("timeout",
64ae570d82SBalazs Benics                                        Opts.Z3CrosscheckTimeoutThreshold); // ms
65b3b0d09cSBalazs Benics 
66b3b0d09cSBalazs Benics   ASTContext &Ctx = BRC.getASTContext();
67b3b0d09cSBalazs Benics 
68b3b0d09cSBalazs Benics   // Add constraints to the solver
69b3b0d09cSBalazs Benics   for (const auto &[Sym, Range] : Constraints) {
70b3b0d09cSBalazs Benics     auto RangeIt = Range.begin();
71b3b0d09cSBalazs Benics 
72b3b0d09cSBalazs Benics     llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr(
73b3b0d09cSBalazs Benics         RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(),
74b3b0d09cSBalazs Benics         /*InRange=*/true);
75b3b0d09cSBalazs Benics     while ((++RangeIt) != Range.end()) {
76b3b0d09cSBalazs Benics       SMTConstraints = RefutationSolver->mkOr(
77b3b0d09cSBalazs Benics           SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
78b3b0d09cSBalazs Benics                                                 RangeIt->From(), RangeIt->To(),
79b3b0d09cSBalazs Benics                                                 /*InRange=*/true));
80b3b0d09cSBalazs Benics     }
81b3b0d09cSBalazs Benics     RefutationSolver->addConstraint(SMTConstraints);
82b3b0d09cSBalazs Benics   }
83b3b0d09cSBalazs Benics 
84*55391f85SBalazs Benics   auto GetUsedRLimit = [](const llvm::SMTSolverRef &Solver) {
85*55391f85SBalazs Benics     return Solver->getStatistics()->getUnsigned("rlimit count");
86ae570d82SBalazs Benics   };
87*55391f85SBalazs Benics 
88*55391f85SBalazs Benics   auto AttemptOnce = [&](const llvm::SMTSolverRef &Solver) -> Z3Result {
89*55391f85SBalazs Benics     constexpr auto getCurrentTime = llvm::TimeRecord::getCurrentTime;
90*55391f85SBalazs Benics     unsigned InitialRLimit = GetUsedRLimit(Solver);
91*55391f85SBalazs Benics     double Start = getCurrentTime(/*Start=*/true).getWallTime();
92*55391f85SBalazs Benics     std::optional<bool> IsSAT = Solver->check();
93*55391f85SBalazs Benics     double End = getCurrentTime(/*Start=*/false).getWallTime();
94*55391f85SBalazs Benics     return {
95*55391f85SBalazs Benics         IsSAT,
96*55391f85SBalazs Benics         static_cast<unsigned>((End - Start) * 1000),
97*55391f85SBalazs Benics         GetUsedRLimit(Solver) - InitialRLimit,
98*55391f85SBalazs Benics     };
99*55391f85SBalazs Benics   };
100*55391f85SBalazs Benics 
101*55391f85SBalazs Benics   // And check for satisfiability
102*55391f85SBalazs Benics   unsigned MinQueryTimeAcrossAttempts = std::numeric_limits<unsigned>::max();
103*55391f85SBalazs Benics   for (unsigned I = 0; I < Opts.Z3CrosscheckMaxAttemptsPerQuery; ++I) {
104*55391f85SBalazs Benics     Result = AttemptOnce(RefutationSolver);
105*55391f85SBalazs Benics     Result.Z3QueryTimeMilliseconds =
106*55391f85SBalazs Benics         std::min(MinQueryTimeAcrossAttempts, Result.Z3QueryTimeMilliseconds);
107*55391f85SBalazs Benics     if (Result.IsSAT.has_value())
108*55391f85SBalazs Benics       return;
109*55391f85SBalazs Benics   }
110b3b0d09cSBalazs Benics }
111b3b0d09cSBalazs Benics 
112b3b0d09cSBalazs Benics void Z3CrosscheckVisitor::addConstraints(
113b3b0d09cSBalazs Benics     const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
114b3b0d09cSBalazs Benics   // Collect new constraints
115b3b0d09cSBalazs Benics   ConstraintMap NewCs = getConstraintMap(N->getState());
116b3b0d09cSBalazs Benics   ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>();
117b3b0d09cSBalazs Benics 
118b3b0d09cSBalazs Benics   // Add constraints if we don't have them yet
119b3b0d09cSBalazs Benics   for (auto const &[Sym, Range] : NewCs) {
120b3b0d09cSBalazs Benics     if (!Constraints.contains(Sym)) {
121b3b0d09cSBalazs Benics       // This symbol is new, just add the constraint.
122b3b0d09cSBalazs Benics       Constraints = CF.add(Constraints, Sym, Range);
123b3b0d09cSBalazs Benics     } else if (OverwriteConstraintsOnExistingSyms) {
124b3b0d09cSBalazs Benics       // Overwrite the associated constraint of the Symbol.
125b3b0d09cSBalazs Benics       Constraints = CF.remove(Constraints, Sym);
126b3b0d09cSBalazs Benics       Constraints = CF.add(Constraints, Sym, Range);
127b3b0d09cSBalazs Benics     }
128b3b0d09cSBalazs Benics   }
129b3b0d09cSBalazs Benics }
130b3b0d09cSBalazs Benics 
131b3b0d09cSBalazs Benics PathDiagnosticPieceRef
132b3b0d09cSBalazs Benics Z3CrosscheckVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &,
133b3b0d09cSBalazs Benics                                PathSensitiveBugReport &) {
134b3b0d09cSBalazs Benics   addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
135b3b0d09cSBalazs Benics   return nullptr;
136b3b0d09cSBalazs Benics }
137b3b0d09cSBalazs Benics 
138b3b0d09cSBalazs Benics void Z3CrosscheckVisitor::Profile(llvm::FoldingSetNodeID &ID) const {
139b3b0d09cSBalazs Benics   static int Tag = 0;
140b3b0d09cSBalazs Benics   ID.AddPointer(&Tag);
141b3b0d09cSBalazs Benics }
142b3b0d09cSBalazs Benics 
143b3b0d09cSBalazs Benics Z3CrosscheckOracle::Z3Decision Z3CrosscheckOracle::interpretQueryResult(
144b3b0d09cSBalazs Benics     const Z3CrosscheckVisitor::Z3Result &Query) {
145b3b0d09cSBalazs Benics   ++NumZ3QueriesDone;
146ae570d82SBalazs Benics   AccumulatedZ3QueryTimeInEqClass += Query.Z3QueryTimeMilliseconds;
147b3b0d09cSBalazs Benics 
148ae570d82SBalazs Benics   if (Query.IsSAT && Query.IsSAT.value()) {
149ae570d82SBalazs Benics     ++NumTimesZ3QueryAcceptsReport;
150b3b0d09cSBalazs Benics     return AcceptReport;
151b3b0d09cSBalazs Benics   }
152b3b0d09cSBalazs Benics 
153ae570d82SBalazs Benics   // Suggest cutting the EQClass if certain heuristics trigger.
154ae570d82SBalazs Benics   if (Opts.Z3CrosscheckTimeoutThreshold &&
155ae570d82SBalazs Benics       Query.Z3QueryTimeMilliseconds >= Opts.Z3CrosscheckTimeoutThreshold) {
156ae570d82SBalazs Benics     ++NumTimesZ3TimedOut;
157ae570d82SBalazs Benics     ++NumTimesZ3QueryRejectEQClass;
158ae570d82SBalazs Benics     return RejectEQClass;
159b3b0d09cSBalazs Benics   }
160b3b0d09cSBalazs Benics 
161ae570d82SBalazs Benics   if (Opts.Z3CrosscheckRLimitThreshold &&
162ae570d82SBalazs Benics       Query.UsedRLimit >= Opts.Z3CrosscheckRLimitThreshold) {
163ae570d82SBalazs Benics     ++NumTimesZ3ExhaustedRLimit;
164ae570d82SBalazs Benics     ++NumTimesZ3QueryRejectEQClass;
165ae570d82SBalazs Benics     return RejectEQClass;
166ae570d82SBalazs Benics   }
167ae570d82SBalazs Benics 
168ae570d82SBalazs Benics   if (Opts.Z3CrosscheckEQClassTimeoutThreshold &&
169ae570d82SBalazs Benics       AccumulatedZ3QueryTimeInEqClass >
170ae570d82SBalazs Benics           Opts.Z3CrosscheckEQClassTimeoutThreshold) {
171ae570d82SBalazs Benics     ++NumTimesZ3SpendsTooMuchTimeOnASingleEQClass;
172ae570d82SBalazs Benics     ++NumTimesZ3QueryRejectEQClass;
173ae570d82SBalazs Benics     return RejectEQClass;
174ae570d82SBalazs Benics   }
175ae570d82SBalazs Benics 
176ae570d82SBalazs Benics   // If no cutoff heuristics trigger, and the report is "unsat" or "undef",
177ae570d82SBalazs Benics   // then reject the report.
178b3b0d09cSBalazs Benics   ++NumTimesZ3QueryRejectReport;
179ae570d82SBalazs Benics   return RejectReport;
180b3b0d09cSBalazs Benics }
181