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