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