diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h index 448e40269ca2d..d02f77194ad8c 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h @@ -47,7 +47,7 @@ class BoolEPStat : public EntryPointStat { public: explicit BoolEPStat(llvm::StringLiteral Name); - unsigned value() const { return Value && *Value; } + std::optional value() const { return Value; } void set(bool V) { assert(!Value.has_value()); Value = V; @@ -98,7 +98,7 @@ class UnsignedEPStat : public EntryPointStat { public: explicit UnsignedEPStat(llvm::StringLiteral Name); - unsigned value() const { return Value.value_or(0); } + std::optional value() const { return Value; } void reset() { Value.reset(); } void set(unsigned V) { assert(!Value.has_value()); diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h index 761395260a0cf..db4aec7c84754 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h @@ -48,6 +48,9 @@ class FunctionSummariesTy { /// The number of times the function has been inlined. unsigned TimesInlined : 32; + /// Running time for syntax-based AST analysis in milliseconds. + std::optional SyntaxRunningTime = std::nullopt; + FunctionSummary() : TotalBasicBlocks(0), InlineChecked(0), MayInline(0), TimesInlined(0) {} @@ -69,6 +72,11 @@ class FunctionSummariesTy { return I; } + FunctionSummary const *findSummary(const Decl *D) const { + auto I = Map.find(D); + return I == Map.end() ? nullptr : &I->second; + } + void markMayInline(const Decl *D) { MapTy::iterator I = findOrInsertSummary(D); I->second.InlineChecked = 1; diff --git a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index 62ae62f2f2154..33c5df8132beb 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -33,7 +33,15 @@ struct Registry { struct Snapshot { const Decl *EntryPoint; - std::vector BoolStatValues; + // Boolean statistics are always set explicitly. If they are not set, their + // value is absent resulting in empty CSV cells + std::vector> BoolStatValues; + // Explicitly set statistics may not have a value set, so they are separate + // from other unsigned statistics + std::vector> UnsignedExplicitlySetStatValues; + // These are counting and maximizing statistics that initialize to 0, which + // is meaningful even if they are never updated, so their value is always + // present. std::vector UnsignedStatValues; void dumpAsCSV(llvm::raw_ostream &OS) const; @@ -48,10 +56,14 @@ static llvm::ManagedStatic StatsRegistry; namespace { template void enumerateStatVectors(const Callback &Fn) { + // This order is important, it matches the order of Snapshot 3 fields: + // - BoolStatValues Fn(StatsRegistry->BoolStats); + // - UnsignedExplicitlySetStatValues + Fn(StatsRegistry->UnsignedStats); + // - UnsignedStatValues Fn(StatsRegistry->CounterStats); Fn(StatsRegistry->UnsignedMaxStats); - Fn(StatsRegistry->UnsignedStats); } } // namespace @@ -120,11 +132,21 @@ UnsignedEPStat::UnsignedEPStat(llvm::StringLiteral Name) StatsRegistry->UnsignedStats.push_back(this); } +static std::vector> +consumeUnsignedExplicitlySetStats() { + std::vector> Result; + Result.reserve(StatsRegistry->UnsignedStats.size()); + for (auto *M : StatsRegistry->UnsignedStats) { + Result.push_back(M->value()); + M->reset(); + } + return Result; +} + static std::vector consumeUnsignedStats() { std::vector Result; Result.reserve(StatsRegistry->CounterStats.size() + - StatsRegistry->UnsignedMaxStats.size() + - StatsRegistry->UnsignedStats.size()); + StatsRegistry->UnsignedMaxStats.size()); for (auto *M : StatsRegistry->CounterStats) { Result.push_back(M->value()); M->reset(); @@ -133,10 +155,6 @@ static std::vector consumeUnsignedStats() { Result.push_back(M->value()); M->reset(); } - for (auto *M : StatsRegistry->UnsignedStats) { - Result.push_back(M->value()); - M->reset(); - } return Result; } @@ -159,6 +177,17 @@ static std::string getUSR(const Decl *D) { } void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { + auto printAsBoolOpt = [&OS](std::optional B) { + OS << (B.has_value() ? (*B ? "true" : "false") : ""); + }; + auto printAsUnsignOpt = [&OS](std::optional U) { + OS << (U.has_value() ? std::to_string(*U) : ""); + }; + auto commaIfNeeded = [&OS](const auto &Vec1, const auto &Vec2) { + if (!Vec1.empty() && !Vec2.empty()) + OS << ","; + }; + OS << '"'; llvm::printEscapedString(getUSR(EntryPoint), OS); OS << "\",\""; @@ -166,14 +195,15 @@ void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { llvm::printEscapedString( clang::AnalysisDeclContext::getFunctionName(EntryPoint), OS); OS << "\","; - auto PrintAsBool = [&OS](bool B) { OS << (B ? "true" : "false"); }; - llvm::interleave(BoolStatValues, OS, PrintAsBool, ","); - OS << ((BoolStatValues.empty() || UnsignedStatValues.empty()) ? "" : ","); + llvm::interleave(BoolStatValues, OS, printAsBoolOpt, ","); + commaIfNeeded(BoolStatValues, UnsignedExplicitlySetStatValues); + llvm::interleave(UnsignedExplicitlySetStatValues, OS, printAsUnsignOpt, ","); + commaIfNeeded(UnsignedExplicitlySetStatValues, UnsignedStatValues); llvm::interleave(UnsignedStatValues, OS, [&OS](unsigned U) { OS << U; }, ","); } -static std::vector consumeBoolStats() { - std::vector Result; +static std::vector> consumeBoolStats() { + std::vector> Result; Result.reserve(StatsRegistry->BoolStats.size()); for (auto *M : StatsRegistry->BoolStats) { Result.push_back(M->value()); @@ -184,9 +214,11 @@ static std::vector consumeBoolStats() { void EntryPointStat::takeSnapshot(const Decl *EntryPoint) { auto BoolValues = consumeBoolStats(); + auto UnsignedExplicitlySetValues = consumeUnsignedExplicitlySetStats(); auto UnsignedValues = consumeUnsignedStats(); - StatsRegistry->Snapshots.push_back( - {EntryPoint, std::move(BoolValues), std::move(UnsignedValues)}); + StatsRegistry->Snapshots.push_back({EntryPoint, std::move(BoolValues), + std::move(UnsignedExplicitlySetValues), + std::move(UnsignedValues)}); } void EntryPointStat::dumpStatsAsCSV(llvm::StringRef FileName) { diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index cf01e2f37c662..a5efa901b0714 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -39,6 +39,7 @@ #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include #include #include @@ -142,7 +143,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, DigestAnalyzerOptions(); if (Opts.AnalyzerDisplayProgress || Opts.PrintStats || - Opts.ShouldSerializeStats) { + Opts.ShouldSerializeStats || !Opts.DumpEntryPointStatsToCSV.empty()) { AnalyzerTimers = std::make_unique( "analyzer", "Analyzer timers"); SyntaxCheckTimer = std::make_unique( @@ -706,6 +707,7 @@ AnalysisConsumer::getModeForDecl(Decl *D, AnalysisMode Mode) { } static UnsignedEPStat PathRunningTime("PathRunningTime"); +static UnsignedEPStat SyntaxRunningTime("SyntaxRunningTime"); void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, ExprEngine::InliningModes IMode, @@ -744,6 +746,8 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, SyntaxCheckTimer->stopTimer(); llvm::TimeRecord CheckerEndTime = SyntaxCheckTimer->getTotalTime(); CheckerEndTime -= CheckerStartTime; + FunctionSummaries.findOrInsertSummary(D)->second.SyntaxRunningTime = + std::lround(CheckerEndTime.getWallTime() * 1000); DisplayTime(CheckerEndTime); } } @@ -758,6 +762,36 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, } } +namespace { +template +static clang::Decl *preferDefinitionImpl(clang::Decl *D) { + if (auto *X = dyn_cast(D)) + if (auto *Def = X->getDefinition()) + return Def; + return D; +} + +template <> clang::Decl *preferDefinitionImpl(clang::Decl *D) { + if (const auto *X = dyn_cast(D)) { + for (auto *I : X->redecls()) + if (I->hasBody()) + return I; + } + return D; +} + +static Decl *getDefinitionOrCanonicalDecl(Decl *D) { + assert(D); + D = D->getCanonicalDecl(); + D = preferDefinitionImpl(D); + D = preferDefinitionImpl(D); + D = preferDefinitionImpl(D); + D = preferDefinitionImpl(D); + assert(D); + return D; +} +} // namespace + //===----------------------------------------------------------------------===// // Path-sensitive checking. //===----------------------------------------------------------------------===// @@ -774,6 +808,16 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, if (!Mgr->getAnalysisDeclContext(D)->getAnalysis()) return; + const Decl *DefDecl = getDefinitionOrCanonicalDecl(D); + + // Get the SyntaxRunningTime from the function summary, because it is computed + // during the AM_Syntax analysis, which is done at a different point in time + // and in different order, but always before AM_Path. + if (const auto *Summary = FunctionSummaries.findSummary(DefDecl); + Summary && Summary->SyntaxRunningTime.has_value()) { + SyntaxRunningTime.set(*Summary->SyntaxRunningTime); + } + ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode); // Execute the worklist algorithm. @@ -788,6 +832,8 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, ExprEngineTimer->stopTimer(); llvm::TimeRecord ExprEngineEndTime = ExprEngineTimer->getTotalTime(); ExprEngineEndTime -= ExprEngineStartTime; + PathRunningTime.set(static_cast( + std::lround(ExprEngineEndTime.getWallTime() * 1000))); DisplayTime(ExprEngineEndTime); } diff --git a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp index 9cbe04550a8d3..b9099c6248108 100644 --- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp +++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp @@ -8,6 +8,8 @@ // CHECK-NEXT: "c:@F@fib#i#": { // CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "fib(unsigned int)", +// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", +// CHECK-NEXT: "SyntaxRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocks": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocksUnreachable": "{{[0-9]+}}", // CHECK-NEXT: "NumCTUSteps": "{{[0-9]+}}", @@ -39,12 +41,13 @@ // CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxReachableSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxTimeSpentSolvingZ3Queries": "{{[0-9]+}}", -// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}", -// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}" +// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}" // CHECK-NEXT: }, // CHECK-NEXT: "c:@F@main#I#**C#": { // CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "main(int, char **)", +// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", +// CHECK-NEXT: "SyntaxRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocks": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocksUnreachable": "{{[0-9]+}}", // CHECK-NEXT: "NumCTUSteps": "{{[0-9]+}}", @@ -76,8 +79,7 @@ // CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxReachableSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxTimeSpentSolvingZ3Queries": "{{[0-9]+}}", -// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}", -// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}" +// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NOT: non_entry_point