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/EntryPointStats.h" |
18 | #include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.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 | STAT_COUNTER(NumZ3QueriesDone, "Number of Z3 queries done" ); |
29 | STAT_COUNTER(NumTimesZ3TimedOut, "Number of times Z3 query timed out" ); |
30 | STAT_COUNTER(NumTimesZ3ExhaustedRLimit, |
31 | "Number of times Z3 query exhausted the rlimit" ); |
32 | STAT_COUNTER( |
33 | NumTimesZ3SpendsTooMuchTimeOnASingleEQClass, |
34 | "Number of times report equivalenece class was cut because it spent " |
35 | "too much time in Z3" ); |
36 | |
37 | STAT_COUNTER(NumTimesZ3QueryAcceptsReport, |
38 | "Number of Z3 queries accepting a report" ); |
39 | STAT_COUNTER(NumTimesZ3QueryRejectReport, |
40 | "Number of Z3 queries rejecting a report" ); |
41 | STAT_COUNTER(NumTimesZ3QueryRejectEQClass, |
42 | "Number of times rejecting an report equivalenece class" ); |
43 | |
44 | STAT_COUNTER(TimeSpentSolvingZ3Queries, |
45 | "Total time spent solving Z3 queries excluding retries" ); |
46 | STAT_MAX(MaxTimeSpentSolvingZ3Queries, |
47 | "Max time spent solving a Z3 query excluding retries" ); |
48 | |
49 | using namespace clang; |
50 | using namespace ento; |
51 | |
52 | Z3CrosscheckVisitor::Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result, |
53 | const AnalyzerOptions &Opts) |
54 | : Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result), |
55 | Opts(Opts) {} |
56 | |
57 | void Z3CrosscheckVisitor::finalizeVisitor(BugReporterContext &BRC, |
58 | const ExplodedNode *EndPathNode, |
59 | PathSensitiveBugReport &BR) { |
60 | // Collect new constraints |
61 | addConstraints(N: EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true); |
62 | |
63 | // Create a refutation manager |
64 | llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver(); |
65 | if (Opts.Z3CrosscheckRLimitThreshold) |
66 | RefutationSolver->setUnsignedParam(Key: "rlimit" , |
67 | Value: Opts.Z3CrosscheckRLimitThreshold); |
68 | if (Opts.Z3CrosscheckTimeoutThreshold) |
69 | RefutationSolver->setUnsignedParam(Key: "timeout" , |
70 | Value: 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 = SMTConv::getRangeExpr( |
79 | Solver&: RefutationSolver, Ctx, Sym, From: RangeIt->From(), To: RangeIt->To(), |
80 | /*InRange=*/true); |
81 | while ((++RangeIt) != Range.end()) { |
82 | SMTConstraints = RefutationSolver->mkOr( |
83 | LHS: SMTConstraints, RHS: SMTConv::getRangeExpr(Solver&: RefutationSolver, Ctx, Sym, |
84 | From: RangeIt->From(), To: RangeIt->To(), |
85 | /*InRange=*/true)); |
86 | } |
87 | RefutationSolver->addConstraint(Exp: SMTConstraints); |
88 | } |
89 | |
90 | auto GetUsedRLimit = [](const llvm::SMTSolverRef &Solver) { |
91 | return Solver->getStatistics()->getUnsigned("rlimit count" ); |
92 | }; |
93 | |
94 | auto AttemptOnce = [&](const llvm::SMTSolverRef &Solver) -> Z3Result { |
95 | auto getCurrentTime = llvm::TimeRecord::getCurrentTime; |
96 | unsigned InitialRLimit = GetUsedRLimit(Solver); |
97 | double Start = getCurrentTime(/*Start=*/true).getWallTime(); |
98 | std::optional<bool> IsSAT = Solver->check(); |
99 | double End = getCurrentTime(/*Start=*/false).getWallTime(); |
100 | return { |
101 | .IsSAT: IsSAT, |
102 | .Z3QueryTimeMilliseconds: static_cast<unsigned>((End - Start) * 1000), |
103 | .UsedRLimit: GetUsedRLimit(Solver) - InitialRLimit, |
104 | }; |
105 | }; |
106 | |
107 | // And check for satisfiability |
108 | unsigned MinQueryTimeAcrossAttempts = std::numeric_limits<unsigned>::max(); |
109 | for (unsigned I = 0; I < Opts.Z3CrosscheckMaxAttemptsPerQuery; ++I) { |
110 | Result = AttemptOnce(RefutationSolver); |
111 | Result.Z3QueryTimeMilliseconds = |
112 | std::min(a: MinQueryTimeAcrossAttempts, b: Result.Z3QueryTimeMilliseconds); |
113 | if (Result.IsSAT.has_value()) |
114 | return; |
115 | } |
116 | } |
117 | |
118 | void Z3CrosscheckVisitor::addConstraints( |
119 | const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) { |
120 | // Collect new constraints |
121 | ConstraintMap NewCs = getConstraintMap(State: N->getState()); |
122 | ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>(); |
123 | |
124 | // Add constraints if we don't have them yet |
125 | for (auto const &[Sym, Range] : NewCs) { |
126 | if (!Constraints.contains(K: Sym)) { |
127 | // This symbol is new, just add the constraint. |
128 | Constraints = CF.add(Old: Constraints, K: Sym, D: Range); |
129 | } else if (OverwriteConstraintsOnExistingSyms) { |
130 | // Overwrite the associated constraint of the Symbol. |
131 | Constraints = CF.remove(Old: Constraints, K: Sym); |
132 | Constraints = CF.add(Old: Constraints, K: Sym, D: Range); |
133 | } |
134 | } |
135 | } |
136 | |
137 | PathDiagnosticPieceRef |
138 | Z3CrosscheckVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &, |
139 | PathSensitiveBugReport &) { |
140 | addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false); |
141 | return nullptr; |
142 | } |
143 | |
144 | void Z3CrosscheckVisitor::Profile(llvm::FoldingSetNodeID &ID) const { |
145 | static int Tag = 0; |
146 | ID.AddPointer(Ptr: &Tag); |
147 | } |
148 | |
149 | Z3CrosscheckOracle::Z3Decision Z3CrosscheckOracle::interpretQueryResult( |
150 | const Z3CrosscheckVisitor::Z3Result &Query) { |
151 | ++NumZ3QueriesDone; |
152 | AccumulatedZ3QueryTimeInEqClass += Query.Z3QueryTimeMilliseconds; |
153 | TimeSpentSolvingZ3Queries += Query.Z3QueryTimeMilliseconds; |
154 | MaxTimeSpentSolvingZ3Queries.updateMax(Value: Query.Z3QueryTimeMilliseconds); |
155 | |
156 | if (Query.IsSAT && Query.IsSAT.value()) { |
157 | ++NumTimesZ3QueryAcceptsReport; |
158 | return AcceptReport; |
159 | } |
160 | |
161 | // Suggest cutting the EQClass if certain heuristics trigger. |
162 | if (Opts.Z3CrosscheckTimeoutThreshold && |
163 | Query.Z3QueryTimeMilliseconds >= Opts.Z3CrosscheckTimeoutThreshold) { |
164 | ++NumTimesZ3TimedOut; |
165 | ++NumTimesZ3QueryRejectEQClass; |
166 | return RejectEQClass; |
167 | } |
168 | |
169 | if (Opts.Z3CrosscheckRLimitThreshold && |
170 | Query.UsedRLimit >= Opts.Z3CrosscheckRLimitThreshold) { |
171 | ++NumTimesZ3ExhaustedRLimit; |
172 | ++NumTimesZ3QueryRejectEQClass; |
173 | return RejectEQClass; |
174 | } |
175 | |
176 | if (Opts.Z3CrosscheckEQClassTimeoutThreshold && |
177 | AccumulatedZ3QueryTimeInEqClass > |
178 | Opts.Z3CrosscheckEQClassTimeoutThreshold) { |
179 | ++NumTimesZ3SpendsTooMuchTimeOnASingleEQClass; |
180 | ++NumTimesZ3QueryRejectEQClass; |
181 | return RejectEQClass; |
182 | } |
183 | |
184 | // If no cutoff heuristics trigger, and the report is "unsat" or "undef", |
185 | // then reject the report. |
186 | ++NumTimesZ3QueryRejectReport; |
187 | return RejectReport; |
188 | } |
189 | |