xref: /llvm-project/clang/lib/StaticAnalyzer/Core/Z3CrosscheckVisitor.cpp (revision 55391f85acc7e7a14ea2ef3c1a4bd8f3df990426)
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/AnalyzerOptions.h"
16 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h"
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.
28 STATISTIC(NumZ3QueriesDone, "Number of Z3 queries done");
29 STATISTIC(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
30 STATISTIC(NumTimesZ3ExhaustedRLimit,
31           "Number of times Z3 query exhausted the rlimit");
32 STATISTIC(NumTimesZ3SpendsTooMuchTimeOnASingleEQClass,
33           "Number of times report equivalenece class was cut because it spent "
34           "too much time in Z3");
35 
36 STATISTIC(NumTimesZ3QueryAcceptsReport,
37           "Number of Z3 queries accepting a report");
38 STATISTIC(NumTimesZ3QueryRejectReport,
39           "Number of Z3 queries rejecting a report");
40 STATISTIC(NumTimesZ3QueryRejectEQClass,
41           "Number of times rejecting an report equivalenece class");
42 
43 using namespace clang;
44 using namespace ento;
45 
46 Z3CrosscheckVisitor::Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result,
47                                          const AnalyzerOptions &Opts)
48     : Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result),
49       Opts(Opts) {}
50 
51 void Z3CrosscheckVisitor::finalizeVisitor(BugReporterContext &BRC,
52                                           const ExplodedNode *EndPathNode,
53                                           PathSensitiveBugReport &BR) {
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);
105     Result.Z3QueryTimeMilliseconds =
106         std::min(MinQueryTimeAcrossAttempts, Result.Z3QueryTimeMilliseconds);
107     if (Result.IsSAT.has_value())
108       return;
109   }
110 }
111 
112 void Z3CrosscheckVisitor::addConstraints(
113     const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
114   // Collect new constraints
115   ConstraintMap NewCs = getConstraintMap(N->getState());
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 
131 PathDiagnosticPieceRef
132 Z3CrosscheckVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &,
133                                PathSensitiveBugReport &) {
134   addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
135   return nullptr;
136 }
137 
138 void Z3CrosscheckVisitor::Profile(llvm::FoldingSetNodeID &ID) const {
139   static int Tag = 0;
140   ID.AddPointer(&Tag);
141 }
142 
143 Z3CrosscheckOracle::Z3Decision Z3CrosscheckOracle::interpretQueryResult(
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 }
181