1 //==- ExprInspectionChecker.cpp - Used for regression tests ------*- C++ -*-==// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "ClangSACheckers.h" 11 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 12 #include "clang/StaticAnalyzer/Core/Checker.h" 13 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 14 #include "clang/StaticAnalyzer/Checkers/SValExplainer.h" 15 #include "llvm/ADT/StringSwitch.h" 16 #include "llvm/Support/ScopedPrinter.h" 17 18 using namespace clang; 19 using namespace ento; 20 21 namespace { 22 class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols, 23 check::EndAnalysis> { 24 mutable std::unique_ptr<BugType> BT; 25 26 // These stats are per-analysis, not per-branch, hence they shouldn't 27 // stay inside the program state. 28 struct ReachedStat { 29 ExplodedNode *ExampleNode; 30 unsigned NumTimesReached; 31 }; 32 mutable llvm::DenseMap<const CallExpr *, ReachedStat> ReachedStats; 33 34 void analyzerEval(const CallExpr *CE, CheckerContext &C) const; 35 void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const; 36 void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const; 37 void analyzerNumTimesReached(const CallExpr *CE, CheckerContext &C) const; 38 void analyzerCrash(const CallExpr *CE, CheckerContext &C) const; 39 void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const; 40 void analyzerDump(const CallExpr *CE, CheckerContext &C) const; 41 void analyzerExplain(const CallExpr *CE, CheckerContext &C) const; 42 void analyzerPrintState(const CallExpr *CE, CheckerContext &C) const; 43 void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const; 44 45 typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *, 46 CheckerContext &C) const; 47 48 ExplodedNode *reportBug(llvm::StringRef Msg, CheckerContext &C) const; 49 ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR, 50 ExplodedNode *N) const; 51 52 public: 53 bool evalCall(const CallExpr *CE, CheckerContext &C) const; 54 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 55 void checkEndAnalysis(ExplodedGraph &G, BugReporter &BR, 56 ExprEngine &Eng) const; 57 }; 58 } 59 60 REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, SymbolRef) 61 62 bool ExprInspectionChecker::evalCall(const CallExpr *CE, 63 CheckerContext &C) const { 64 // These checks should have no effect on the surrounding environment 65 // (globals should not be invalidated, etc), hence the use of evalCall. 66 FnCheck Handler = llvm::StringSwitch<FnCheck>(C.getCalleeName(CE)) 67 .Case("clang_analyzer_eval", &ExprInspectionChecker::analyzerEval) 68 .Case("clang_analyzer_checkInlined", 69 &ExprInspectionChecker::analyzerCheckInlined) 70 .Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash) 71 .Case("clang_analyzer_warnIfReached", 72 &ExprInspectionChecker::analyzerWarnIfReached) 73 .Case("clang_analyzer_warnOnDeadSymbol", 74 &ExprInspectionChecker::analyzerWarnOnDeadSymbol) 75 .StartsWith("clang_analyzer_explain", &ExprInspectionChecker::analyzerExplain) 76 .StartsWith("clang_analyzer_dump", &ExprInspectionChecker::analyzerDump) 77 .Case("clang_analyzer_getExtent", &ExprInspectionChecker::analyzerGetExtent) 78 .Case("clang_analyzer_printState", 79 &ExprInspectionChecker::analyzerPrintState) 80 .Case("clang_analyzer_numTimesReached", 81 &ExprInspectionChecker::analyzerNumTimesReached) 82 .Default(nullptr); 83 84 if (!Handler) 85 return false; 86 87 (this->*Handler)(CE, C); 88 return true; 89 } 90 91 static const char *getArgumentValueString(const CallExpr *CE, 92 CheckerContext &C) { 93 if (CE->getNumArgs() == 0) 94 return "Missing assertion argument"; 95 96 ExplodedNode *N = C.getPredecessor(); 97 const LocationContext *LC = N->getLocationContext(); 98 ProgramStateRef State = N->getState(); 99 100 const Expr *Assertion = CE->getArg(0); 101 SVal AssertionVal = State->getSVal(Assertion, LC); 102 103 if (AssertionVal.isUndef()) 104 return "UNDEFINED"; 105 106 ProgramStateRef StTrue, StFalse; 107 std::tie(StTrue, StFalse) = 108 State->assume(AssertionVal.castAs<DefinedOrUnknownSVal>()); 109 110 if (StTrue) { 111 if (StFalse) 112 return "UNKNOWN"; 113 else 114 return "TRUE"; 115 } else { 116 if (StFalse) 117 return "FALSE"; 118 else 119 llvm_unreachable("Invalid constraint; neither true or false."); 120 } 121 } 122 123 ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, 124 CheckerContext &C) const { 125 ExplodedNode *N = C.generateNonFatalErrorNode(); 126 reportBug(Msg, C.getBugReporter(), N); 127 return N; 128 } 129 130 ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, 131 BugReporter &BR, 132 ExplodedNode *N) const { 133 if (!N) 134 return nullptr; 135 136 if (!BT) 137 BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); 138 139 BR.emitReport(llvm::make_unique<BugReport>(*BT, Msg, N)); 140 return N; 141 } 142 143 void ExprInspectionChecker::analyzerEval(const CallExpr *CE, 144 CheckerContext &C) const { 145 const LocationContext *LC = C.getPredecessor()->getLocationContext(); 146 147 // A specific instantiation of an inlined function may have more constrained 148 // values than can generally be assumed. Skip the check. 149 if (LC->getCurrentStackFrame()->getParent() != nullptr) 150 return; 151 152 reportBug(getArgumentValueString(CE, C), C); 153 } 154 155 void ExprInspectionChecker::analyzerWarnIfReached(const CallExpr *CE, 156 CheckerContext &C) const { 157 reportBug("REACHABLE", C); 158 } 159 160 void ExprInspectionChecker::analyzerNumTimesReached(const CallExpr *CE, 161 CheckerContext &C) const { 162 ++ReachedStats[CE].NumTimesReached; 163 if (!ReachedStats[CE].ExampleNode) { 164 // Later, in checkEndAnalysis, we'd throw a report against it. 165 ReachedStats[CE].ExampleNode = C.generateNonFatalErrorNode(); 166 } 167 } 168 169 void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE, 170 CheckerContext &C) const { 171 const LocationContext *LC = C.getPredecessor()->getLocationContext(); 172 173 // An inlined function could conceivably also be analyzed as a top-level 174 // function. We ignore this case and only emit a message (TRUE or FALSE) 175 // when we are analyzing it as an inlined function. This means that 176 // clang_analyzer_checkInlined(true) should always print TRUE, but 177 // clang_analyzer_checkInlined(false) should never actually print anything. 178 if (LC->getCurrentStackFrame()->getParent() == nullptr) 179 return; 180 181 reportBug(getArgumentValueString(CE, C), C); 182 } 183 184 void ExprInspectionChecker::analyzerExplain(const CallExpr *CE, 185 CheckerContext &C) const { 186 if (CE->getNumArgs() == 0) { 187 reportBug("Missing argument for explaining", C); 188 return; 189 } 190 191 SVal V = C.getSVal(CE->getArg(0)); 192 SValExplainer Ex(C.getASTContext()); 193 reportBug(Ex.Visit(V), C); 194 } 195 196 void ExprInspectionChecker::analyzerDump(const CallExpr *CE, 197 CheckerContext &C) const { 198 if (CE->getNumArgs() == 0) { 199 reportBug("Missing argument for dumping", C); 200 return; 201 } 202 203 SVal V = C.getSVal(CE->getArg(0)); 204 205 llvm::SmallString<32> Str; 206 llvm::raw_svector_ostream OS(Str); 207 V.dumpToStream(OS); 208 reportBug(OS.str(), C); 209 } 210 211 void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE, 212 CheckerContext &C) const { 213 if (CE->getNumArgs() == 0) { 214 reportBug("Missing region for obtaining extent", C); 215 return; 216 } 217 218 auto MR = dyn_cast_or_null<SubRegion>(C.getSVal(CE->getArg(0)).getAsRegion()); 219 if (!MR) { 220 reportBug("Obtaining extent of a non-region", C); 221 return; 222 } 223 224 ProgramStateRef State = C.getState(); 225 State = State->BindExpr(CE, C.getLocationContext(), 226 MR->getExtent(C.getSValBuilder())); 227 C.addTransition(State); 228 } 229 230 void ExprInspectionChecker::analyzerPrintState(const CallExpr *CE, 231 CheckerContext &C) const { 232 C.getState()->dump(); 233 } 234 235 void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE, 236 CheckerContext &C) const { 237 if (CE->getNumArgs() == 0) 238 return; 239 SVal Val = C.getSVal(CE->getArg(0)); 240 SymbolRef Sym = Val.getAsSymbol(); 241 if (!Sym) 242 return; 243 244 ProgramStateRef State = C.getState(); 245 State = State->add<MarkedSymbols>(Sym); 246 C.addTransition(State); 247 } 248 249 void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper, 250 CheckerContext &C) const { 251 ProgramStateRef State = C.getState(); 252 const MarkedSymbolsTy &Syms = State->get<MarkedSymbols>(); 253 ExplodedNode *N = C.getPredecessor(); 254 for (auto I = Syms.begin(), E = Syms.end(); I != E; ++I) { 255 SymbolRef Sym = *I; 256 if (!SymReaper.isDead(Sym)) 257 continue; 258 259 // The non-fatal error node should be the same for all reports. 260 if (ExplodedNode *BugNode = reportBug("SYMBOL DEAD", C)) 261 N = BugNode; 262 State = State->remove<MarkedSymbols>(Sym); 263 } 264 C.addTransition(State, N); 265 } 266 267 void ExprInspectionChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &BR, 268 ExprEngine &Eng) const { 269 for (auto Item: ReachedStats) { 270 unsigned NumTimesReached = Item.second.NumTimesReached; 271 ExplodedNode *N = Item.second.ExampleNode; 272 273 reportBug(llvm::to_string(NumTimesReached), BR, N); 274 } 275 } 276 277 void ExprInspectionChecker::analyzerCrash(const CallExpr *CE, 278 CheckerContext &C) const { 279 LLVM_BUILTIN_TRAP; 280 } 281 282 void ento::registerExprInspectionChecker(CheckerManager &Mgr) { 283 Mgr.registerChecker<ExprInspectionChecker>(); 284 } 285 286