Index: cfe/trunk/docs/analyzer/DebugChecks.rst =================================================================== --- cfe/trunk/docs/analyzer/DebugChecks.rst +++ cfe/trunk/docs/analyzer/DebugChecks.rst @@ -138,6 +138,17 @@ clang_analyzer_warnIfReached(); // no-warning } +- void clang_analyzer_numTimesReached(); + + Same as above, but include the number of times this call expression + gets reached by the analyzer during the current analysis. + + Example usage:: + + for (int x = 0; x < 3; ++x) { + clang_analyzer_numTimesReached(); // expected-warning{{3}} + } + - void clang_analyzer_warnOnDeadSymbol(int); Subscribe for a delayed warning when the symbol that represents the value of @@ -180,6 +191,18 @@ clang_analyzer_explain(ptr); // expected-warning{{memory address '0'}} } +- void clang_analyzer_dump(a single argument of any type); + + Similar to clang_analyzer_explain, but produces a raw dump of the value, + same as SVal::dump(). + + Example usage:: + + void clang_analyzer_dump(int); + void foo(int x) { + clang_analyzer_dump(x); // expected-warning{{reg_$0}} + } + - size_t clang_analyzer_getExtent(void *); This function returns the value that represents the extent of a memory region @@ -197,6 +220,22 @@ clang_analyzer_explain(ys); // expected-warning{{'8'}} } +- void clang_analyzer_printState(); + + Dumps the current ProgramState to the stderr. Quickly lookup the program state + at any execution point without ViewExplodedGraph or re-compiling the program. + This is not very useful for writing tests (apart from testing how ProgramState + gets printed), but useful for debugging tests. Also, this method doesn't + produce a warning, so it gets printed on the console before all other + ExprInspection warnings. + + Example usage:: + + void foo() { + int x = 1; + clang_analyzer_printState(); // Read the stderr! + } + Statistics ========== Index: cfe/trunk/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp =================================================================== --- cfe/trunk/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ cfe/trunk/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -18,25 +18,41 @@ using namespace ento; namespace { -class ExprInspectionChecker : public Checker { +class ExprInspectionChecker : public Checker { mutable std::unique_ptr BT; + // These stats are per-analysis, not per-branch, hence they shouldn't + // stay inside the program state. + struct ReachedStat { + ExplodedNode *ExampleNode; + unsigned NumTimesReached; + }; + mutable llvm::DenseMap ReachedStats; + void analyzerEval(const CallExpr *CE, CheckerContext &C) const; void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const; void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const; + void analyzerNumTimesReached(const CallExpr *CE, CheckerContext &C) const; void analyzerCrash(const CallExpr *CE, CheckerContext &C) const; void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const; + void analyzerDump(const CallExpr *CE, CheckerContext &C) const; void analyzerExplain(const CallExpr *CE, CheckerContext &C) const; + void analyzerPrintState(const CallExpr *CE, CheckerContext &C) const; void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const; typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *, CheckerContext &C) const; - void reportBug(llvm::StringRef Msg, CheckerContext &C) const; + ExplodedNode *reportBug(llvm::StringRef Msg, CheckerContext &C) const; + ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR, + ExplodedNode *N) const; public: bool evalCall(const CallExpr *CE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + void checkEndAnalysis(ExplodedGraph &G, BugReporter &BR, + ExprEngine &Eng) const; }; } @@ -56,7 +72,12 @@ .Case("clang_analyzer_warnOnDeadSymbol", &ExprInspectionChecker::analyzerWarnOnDeadSymbol) .Case("clang_analyzer_explain", &ExprInspectionChecker::analyzerExplain) + .Case("clang_analyzer_dump", &ExprInspectionChecker::analyzerDump) .Case("clang_analyzer_getExtent", &ExprInspectionChecker::analyzerGetExtent) + .Case("clang_analyzer_printState", + &ExprInspectionChecker::analyzerPrintState) + .Case("clang_analyzer_numTimesReached", + &ExprInspectionChecker::analyzerNumTimesReached) .Default(nullptr); if (!Handler) @@ -98,16 +119,24 @@ } } -void ExprInspectionChecker::reportBug(llvm::StringRef Msg, - CheckerContext &C) const { - if (!BT) - BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - +ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, + CheckerContext &C) const { ExplodedNode *N = C.generateNonFatalErrorNode(); + reportBug(Msg, C.getBugReporter(), N); + return N; +} + +ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, + BugReporter &BR, + ExplodedNode *N) const { if (!N) - return; + return nullptr; + + if (!BT) + BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - C.emitReport(llvm::make_unique(*BT, Msg, N)); + BR.emitReport(llvm::make_unique(*BT, Msg, N)); + return N; } void ExprInspectionChecker::analyzerEval(const CallExpr *CE, @@ -127,6 +156,15 @@ reportBug("REACHABLE", C); } +void ExprInspectionChecker::analyzerNumTimesReached(const CallExpr *CE, + CheckerContext &C) const { + ++ReachedStats[CE].NumTimesReached; + if (!ReachedStats[CE].ExampleNode) { + // Later, in checkEndAnalysis, we'd throw a report against it. + ReachedStats[CE].ExampleNode = C.generateNonFatalErrorNode(); + } +} + void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const { const LocationContext *LC = C.getPredecessor()->getLocationContext(); @@ -144,22 +182,43 @@ void ExprInspectionChecker::analyzerExplain(const CallExpr *CE, CheckerContext &C) const { - if (CE->getNumArgs() == 0) + if (CE->getNumArgs() == 0) { reportBug("Missing argument for explaining", C); + return; + } SVal V = C.getSVal(CE->getArg(0)); SValExplainer Ex(C.getASTContext()); reportBug(Ex.Visit(V), C); } +void ExprInspectionChecker::analyzerDump(const CallExpr *CE, + CheckerContext &C) const { + if (CE->getNumArgs() == 0) { + reportBug("Missing argument for dumping", C); + return; + } + + SVal V = C.getSVal(CE->getArg(0)); + + llvm::SmallString<32> Str; + llvm::raw_svector_ostream OS(Str); + V.dumpToStream(OS); + reportBug(OS.str(), C); +} + void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const { - if (CE->getNumArgs() == 0) + if (CE->getNumArgs() == 0) { reportBug("Missing region for obtaining extent", C); + return; + } auto MR = dyn_cast_or_null(C.getSVal(CE->getArg(0)).getAsRegion()); - if (!MR) + if (!MR) { reportBug("Obtaining extent of a non-region", C); + return; + } ProgramStateRef State = C.getState(); State = State->BindExpr(CE, C.getLocationContext(), @@ -167,6 +226,11 @@ C.addTransition(State); } +void ExprInspectionChecker::analyzerPrintState(const CallExpr *CE, + CheckerContext &C) const { + C.getState()->dump(); +} + void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const { if (CE->getNumArgs() == 0) @@ -185,15 +249,28 @@ CheckerContext &C) const { ProgramStateRef State = C.getState(); const MarkedSymbolsTy &Syms = State->get(); + ExplodedNode *N = C.getPredecessor(); for (auto I = Syms.begin(), E = Syms.end(); I != E; ++I) { SymbolRef Sym = *I; if (!SymReaper.isDead(Sym)) continue; - reportBug("SYMBOL DEAD", C); + // The non-fatal error node should be the same for all reports. + if (ExplodedNode *BugNode = reportBug("SYMBOL DEAD", C)) + N = BugNode; State = State->remove(Sym); } - C.addTransition(State); + C.addTransition(State, N); +} + +void ExprInspectionChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &BR, + ExprEngine &Eng) const { + for (auto Item: ReachedStats) { + unsigned NumTimesReached = Item.second.NumTimesReached; + ExplodedNode *N = Item.second.ExampleNode; + + reportBug(std::to_string(NumTimesReached), BR, N); + } } void ExprInspectionChecker::analyzerCrash(const CallExpr *CE, Index: cfe/trunk/test/Analysis/expr-inspection.c =================================================================== --- cfe/trunk/test/Analysis/expr-inspection.c +++ cfe/trunk/test/Analysis/expr-inspection.c @@ -0,0 +1,22 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=debug.ExprInspection -verify %s 2>&1 | FileCheck %s + +// Self-tests for the debug.ExprInspection checker. + +void clang_analyzer_dump(int x); +void clang_analyzer_printState(); +void clang_analyzer_numTimesReached(); + +void foo(int x) { + clang_analyzer_dump(x); // expected-warning{{reg_$0}} + int y = 1; + clang_analyzer_printState(); + for (; y < 3; ++y) + clang_analyzer_numTimesReached(); // expected-warning{{2}} +} + +// CHECK: Store (direct and default bindings) +// CHECK-NEXT: (y,0,direct) : 1 S32b + +// CHECK: Expressions: +// CHECK-NEXT: clang_analyzer_printState : &code{clang_analyzer_printState} +// CHECK-NEXT: Ranges are empty. Index: cfe/trunk/test/Analysis/symbol-reaper.c =================================================================== --- cfe/trunk/test/Analysis/symbol-reaper.c +++ cfe/trunk/test/Analysis/symbol-reaper.c @@ -2,6 +2,7 @@ void clang_analyzer_eval(int); void clang_analyzer_warnOnDeadSymbol(int); +void clang_analyzer_numTimesReached(); int conjure_index(); @@ -10,6 +11,9 @@ int x = conjure_index(); clang_analyzer_warnOnDeadSymbol(x); } while(0); // expected-warning{{SYMBOL DEAD}} + + // Make sure we don't accidentally split state in ExprInspection. + clang_analyzer_numTimesReached(); // expected-warning{{1}} } // These tests verify the reaping of symbols that are only referenced as