Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -274,6 +274,8 @@ /// \sa StableReportFilename Optional StableReportFilename; + Optional SerializeStats; + /// \sa getGraphTrimInterval Optional GraphTrimInterval; @@ -528,6 +530,14 @@ /// which accepts the values "true" and "false". Default = false bool shouldWriteStableReportFilename(); + /// \return Whether the analyzer should + /// serialize statistics to plist output. + /// Statistics would be serialized in JSON format inside the main dictionary + /// under the \c statistics key. + /// Available only if compiled in assert mode or with LLVM statistics + /// explicitly enabled. + bool shouldSerializeStats(); + /// Returns whether irrelevant parts of a bug report path should be pruned /// out of the final output. /// Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -281,6 +281,12 @@ /* Default = */ false); } +bool AnalyzerOptions::shouldSerializeStats() { + return getBooleanOption(SerializeStats, + "serialize-stats", + /* Default = */ false); +} + int AnalyzerOptions::getOptionAsInteger(StringRef Name, int DefaultVal, const CheckerBase *C, bool SearchInParents) { Index: lib/StaticAnalyzer/Core/PlistDiagnostics.cpp =================================================================== --- lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -16,9 +16,12 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/HTMLRewrite.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" +#include "llvm/ADT/Statistic.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" using namespace clang; @@ -30,6 +33,7 @@ const std::string OutputFile; const LangOptions &LangOpts; const bool SupportsCrossFileDiagnostics; + const bool SerializeStatistics; public: PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, @@ -61,7 +65,8 @@ bool supportsMultipleFiles) : OutputFile(output), LangOpts(LO), - SupportsCrossFileDiagnostics(supportsMultipleFiles) {} + SupportsCrossFileDiagnostics(supportsMultipleFiles), + SerializeStatistics(AnalyzerOpts.shouldSerializeStats()) {} void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, @@ -484,6 +489,15 @@ o << " \n"; + if (llvm::AreStatisticsEnabled() && SerializeStatistics) { + o << " statistics\n"; + std::string stats; + llvm::raw_string_ostream os(stats); + llvm::PrintStatisticsJSON(os); + os.flush(); + EmitString(o, html::EscapeText(stats)) << '\n'; + } + // Finish. o << "\n"; } Index: lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp =================================================================== --- lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -186,7 +186,8 @@ std::unique_ptr Mgr; /// Time the analyzes time of each translation unit. - static llvm::Timer* TUTotalTimer; + std::unique_ptr AnalyzerTimers; + std::unique_ptr TUTotalTimer; /// The information about analyzed functions shared throughout the /// translation unit. @@ -199,15 +200,17 @@ OutDir(outdir), Opts(std::move(opts)), Plugins(plugins), Injector(injector) { DigestAnalyzerOptions(); - if (Opts->PrintStats) { - llvm::EnableStatistics(false); - TUTotalTimer = new llvm::Timer("time", "Analyzer Total Time"); + if (Opts->PrintStats || Opts->shouldSerializeStats()) { + AnalyzerTimers = llvm::make_unique( + "analyzer", "Analyzer timers"); + TUTotalTimer = llvm::make_unique( + "time", "Analyzer total time", *AnalyzerTimers); + llvm::EnableStatistics(/* PrintOnExit= */ false); } } ~AnalysisConsumer() override { if (Opts->PrintStats) { - delete TUTotalTimer; llvm::PrintStatistics(); } } @@ -392,8 +395,6 @@ //===----------------------------------------------------------------------===// // AnalysisConsumer implementation. //===----------------------------------------------------------------------===// -llvm::Timer* AnalysisConsumer::TUTotalTimer = nullptr; - bool AnalysisConsumer::HandleTopLevelDecl(DeclGroupRef DG) { storeTopLevelDecls(DG); return true; @@ -555,12 +556,6 @@ RecVisitorBR = nullptr; } - // Explicitly destroy the PathDiagnosticConsumer. This will flush its output. - // FIXME: This should be replaced with something that doesn't rely on - // side-effects in PathDiagnosticConsumer's destructor. This is required when - // used with option -disable-free. - Mgr.reset(); - if (TUTotalTimer) TUTotalTimer->stopTimer(); // Count how many basic blocks we have not covered. @@ -570,6 +565,11 @@ (FunctionSummaries.getTotalNumVisitedBasicBlocks() * 100) / NumBlocksInAnalyzedFunctions; + // Explicitly destroy the PathDiagnosticConsumer. This will flush its output. + // FIXME: This should be replaced with something that doesn't rely on + // side-effects in PathDiagnosticConsumer's destructor. This is required when + // used with option -disable-free. + Mgr.reset(); } std::string AnalysisConsumer::getFunctionName(const Decl *D) { Index: test/Analysis/analyzer-config.c =================================================================== --- test/Analysis/analyzer-config.c +++ test/Analysis/analyzer-config.c @@ -29,7 +29,8 @@ // CHECK-NEXT: min-cfg-size-treat-functions-as-large = 14 // CHECK-NEXT: mode = deep // CHECK-NEXT: region-store-small-struct-limit = 2 +// CHECK-NEXT: serialize-stats = false // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 20 +// CHECK-NEXT: num-entries = 21 Index: test/Analysis/analyzer-config.cpp =================================================================== --- test/Analysis/analyzer-config.cpp +++ test/Analysis/analyzer-config.cpp @@ -40,7 +40,8 @@ // CHECK-NEXT: min-cfg-size-treat-functions-as-large = 14 // CHECK-NEXT: mode = deep // CHECK-NEXT: region-store-small-struct-limit = 2 +// CHECK-NEXT: serialize-stats = false // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 25 +// CHECK-NEXT: num-entries = 26 Index: test/Analysis/plist-stats-output.c =================================================================== --- /dev/null +++ test/Analysis/plist-stats-output.c @@ -0,0 +1,14 @@ +// RUN: %clang_analyze_cc1 %s -analyzer-checker=core -analyzer-output=plist -analyzer-config serialize-stats=true -o %t.plist +// REQUIRES: asserts +// RUN: FileCheck --input-file=%t.plist %s + +int foo() {} + + +// CHECK: diagnostics +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: statistics +// CHECK-NEXT: { +// CHECK: } +// CHECK-NEXT: