From 07bcb966a7b0aed27da7971cbb876885080562e1 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Fri, 10 Oct 2025 13:38:27 +0200 Subject: [PATCH 01/21] [clang][analyzer] Record never-set statistics as empty CSV cells --- .../analyzer/developer-docs/Statistics.rst | 2 +- .../Core/PathSensitive/EntryPointStats.h | 2 +- .../StaticAnalyzer/Core/EntryPointStats.cpp | 69 +++++++++++++------ .../analyzer-stats/entry-point-stats.cpp | 30 ++++---- 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/clang/docs/analyzer/developer-docs/Statistics.rst b/clang/docs/analyzer/developer-docs/Statistics.rst index 4f2484a89a6af..355759d468282 100644 --- a/clang/docs/analyzer/developer-docs/Statistics.rst +++ b/clang/docs/analyzer/developer-docs/Statistics.rst @@ -22,7 +22,7 @@ However, note that with ``LLVM_ENABLE_STATS`` disabled, only storage of the valu If you want to define a statistic only for entry point, EntryPointStats.h has four classes at your disposal: -- ``UnsignedEPStat`` - an unsigned value assigned at most once per entry point. For example: "the number of source characters in an entry-point body". +- ``UnsignedEPStat`` - an unsigned value assigned at most once per entry point. For example: "the number of source characters in an entry-point body". If no value is assigned during analysis of an entry point, the corresponding CSV cell will be empty. - ``CounterEPStat`` - an additive statistic. It starts with 0 and you can add to it as many times as needed. For example: "the number of bugs discovered". - ``UnsignedMaxEPStat`` - a maximizing statistic. It starts with 0 and when you join it with a value, it picks the maximum of the previous value and the new one. For example, "the longest execution path of a bug". diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h index 389f17d36e65a..0a45deb33fcc9 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h @@ -85,7 +85,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/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index abfb176d6384d..33f5ccf6002ba 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -24,15 +24,21 @@ using namespace ento; namespace { struct Registry { + std::vector ExplicitlySetStats; + std::vector MaxStats; std::vector CounterStats; - std::vector UnsignedMaxStats; - std::vector UnsignedStats; bool IsLocked = false; struct Snapshot { const Decl *EntryPoint; - std::vector UnsignedStatValues; + // Explicitly set statistics may not have a value set, so they are separate + // from other unsigned statistics + std::vector> ExplicitlySetStatValues; + // 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 MaxOrCountStatValues; void dumpAsCSV(llvm::raw_ostream &OS) const; }; @@ -46,9 +52,12 @@ static llvm::ManagedStatic StatsRegistry; namespace { template void enumerateStatVectors(const Callback &Fn) { + // This order is important, it matches the order of the Snapshot fields: + // - ExplicitlySetStatValues + Fn(StatsRegistry->ExplicitlySetStats); + // - MaxOrCountStatValues + Fn(StatsRegistry->MaxStats); Fn(StatsRegistry->CounterStats); - Fn(StatsRegistry->UnsignedMaxStats); - Fn(StatsRegistry->UnsignedStats); } } // namespace @@ -101,30 +110,37 @@ UnsignedMaxEPStat::UnsignedMaxEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) { assert(!StatsRegistry->IsLocked); assert(!isRegistered(Name)); - StatsRegistry->UnsignedMaxStats.push_back(this); + StatsRegistry->MaxStats.push_back(this); } UnsignedEPStat::UnsignedEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) { assert(!StatsRegistry->IsLocked); assert(!isRegistered(Name)); - StatsRegistry->UnsignedStats.push_back(this); + StatsRegistry->ExplicitlySetStats.push_back(this); } -static std::vector consumeUnsignedStats() { - std::vector Result; - Result.reserve(StatsRegistry->CounterStats.size() + - StatsRegistry->UnsignedMaxStats.size() + - StatsRegistry->UnsignedStats.size()); - for (auto *M : StatsRegistry->CounterStats) { +static std::vector> +consumeExplicitlySetStats() { + std::vector> Result; + Result.reserve(StatsRegistry->ExplicitlySetStats.size()); + for (auto *M : StatsRegistry->ExplicitlySetStats) { Result.push_back(M->value()); M->reset(); } - for (auto *M : StatsRegistry->UnsignedMaxStats) { + return Result; +} + +static std::vector consumeMaxAndCounterStats() { + std::vector Result; + Result.reserve(StatsRegistry->CounterStats.size() + + StatsRegistry->MaxStats.size()); + // Order is important, it must match the order in enumerateStatVectors + for (auto *M : StatsRegistry->MaxStats) { Result.push_back(M->value()); M->reset(); } - for (auto *M : StatsRegistry->UnsignedStats) { + for (auto *M : StatsRegistry->CounterStats) { Result.push_back(M->value()); M->reset(); } @@ -150,20 +166,33 @@ static std::string getUSR(const Decl *D) { } void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { + 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 << ","; + }; + auto printAsUnsigned = [&OS](unsigned U) { OS << U; }; + OS << '"'; llvm::printEscapedString(getUSR(EntryPoint), OS); OS << "\",\""; OS << StatsRegistry->EscapedCPPFileName << "\",\""; llvm::printEscapedString( clang::AnalysisDeclContext::getFunctionName(EntryPoint), OS); - OS << "\""; - OS << (UnsignedStatValues.empty() ? "" : ","); - llvm::interleave(UnsignedStatValues, OS, [&OS](unsigned U) { OS << U; }, ","); + OS << "\","; + llvm::interleave(ExplicitlySetStatValues, OS, printAsUnsignOpt, ","); + commaIfNeeded(ExplicitlySetStatValues, MaxOrCountStatValues); + llvm::interleave(MaxOrCountStatValues, OS, printAsUnsigned, ","); } void EntryPointStat::takeSnapshot(const Decl *EntryPoint) { - auto UnsignedValues = consumeUnsignedStats(); - StatsRegistry->Snapshots.push_back({EntryPoint, std::move(UnsignedValues)}); + auto ExplicitlySetValues = consumeExplicitlySetStats(); + auto MaxOrCounterValues = consumeMaxAndCounterStats(); + StatsRegistry->Snapshots.push_back({EntryPoint, + std::move(ExplicitlySetValues), + std::move(MaxOrCounterValues)}); } void EntryPointStat::dumpStatsAsCSV(llvm::StringRef FileName) { diff --git a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp index 9cbe04550a8d3..0e9b4895554e9 100644 --- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp +++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp @@ -6,8 +6,15 @@ // // CHECK: { // CHECK-NEXT: "c:@F@fib#i#": { -// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", +// CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "fib(unsigned int)", +// CHECK-NEXT: "PathRunningTime": "", +// CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", +// CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", +// CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}", +// CHECK-NEXT: "MaxReachableSize": "{{[0-9]+}}", +// CHECK-NEXT: "MaxTimeSpentSolvingZ3Queries": "{{[0-9]+}}", +// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocks": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocksUnreachable": "{{[0-9]+}}", // CHECK-NEXT: "NumCTUSteps": "{{[0-9]+}}", @@ -33,18 +40,18 @@ // CHECK-NEXT: "NumTimesZ3SpendsTooMuchTimeOnASingleEQClass": "{{[0-9]+}}", // CHECK-NEXT: "NumTimesZ3TimedOut": "{{[0-9]+}}", // CHECK-NEXT: "NumZ3QueriesDone": "{{[0-9]+}}", -// CHECK-NEXT: "TimeSpentSolvingZ3Queries": "{{[0-9]+}}", +// CHECK-NEXT: "TimeSpentSolvingZ3Queries": "{{[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": "", // CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", // 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: }, -// CHECK-NEXT: "c:@F@main#I#**C#": { -// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", -// CHECK-NEXT: "DebugName": "main(int, char **)", // CHECK-NEXT: "NumBlocks": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocksUnreachable": "{{[0-9]+}}", // CHECK-NEXT: "NumCTUSteps": "{{[0-9]+}}", @@ -70,14 +77,7 @@ // CHECK-NEXT: "NumTimesZ3SpendsTooMuchTimeOnASingleEQClass": "{{[0-9]+}}", // CHECK-NEXT: "NumTimesZ3TimedOut": "{{[0-9]+}}", // CHECK-NEXT: "NumZ3QueriesDone": "{{[0-9]+}}", -// CHECK-NEXT: "TimeSpentSolvingZ3Queries": "{{[0-9]+}}", -// CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", -// CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", -// 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: "TimeSpentSolvingZ3Queries": "{{[0-9]+}}" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NOT: non_entry_point From ab54bbd48bb66824d5cf666c84d22dc7b3c47b22 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Fri, 10 Oct 2025 13:39:24 +0200 Subject: [PATCH 02/21] Fix path running time per entry point recording --- clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 5 ++++- clang/test/Analysis/analyzer-stats/entry-point-stats.cpp | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index cf01e2f37c662..e97004f1bbe52 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( @@ -788,6 +789,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 0e9b4895554e9..feaf92a8043ca 100644 --- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp +++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp @@ -8,7 +8,7 @@ // CHECK-NEXT: "c:@F@fib#i#": { // CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "fib(unsigned int)", -// CHECK-NEXT: "PathRunningTime": "", +// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}", @@ -45,7 +45,7 @@ // CHECK-NEXT: "c:@F@main#I#**C#": { // CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "main(int, char **)", -// CHECK-NEXT: "PathRunningTime": "", +// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}", From f83589b5cfec72114451ed08b5a0315960338bd0 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Fri, 10 Oct 2025 14:47:30 +0200 Subject: [PATCH 03/21] Add a test case demonstrating the use of explicitly set unsigned stats --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 ++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/UnsignedStatTesterChecker.cpp | 59 +++++++++++++++++++ .../analyzer-stats/entry-point-stats.cpp | 19 +++++- 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 4473c54d8d6e3..0db6794b2310a 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1502,6 +1502,10 @@ def TaintTesterChecker : Checker<"TaintTest">, HelpText<"Mark tainted symbols as such.">, Documentation; +def UnsignedStatTesterChecker : Checker<"UnsignedStatTester">, + HelpText<"Test checker for demonstrating UnsignedEPStat usage.">, + Documentation; + // This checker *technically* depends on SteamChecker, but we don't allow // dependency checkers to emit diagnostics, and a debug checker isn't worth // the chore needed to create a modeling portion on its own. Since this checker diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 29d2c4512d470..f70ae1a3e8a96 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -125,6 +125,7 @@ add_clang_library(clangStaticAnalyzerCheckers UninitializedObject/UninitializedPointee.cpp UnixAPIChecker.cpp UnreachableCodeChecker.cpp + UnsignedStatTesterChecker.cpp VforkChecker.cpp VLASizeChecker.cpp VAListChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp new file mode 100644 index 0000000000000..a302f506a285a --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp @@ -0,0 +1,59 @@ +//== UnsignedStatTesterChecker.cpp --------------------------- -*- C++ -*--=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This checker demonstrates the use of UnsignedEPStat for per-entry-point +// statistics. It conditionally sets a statistic based on the entry point name. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h" + +using namespace clang; +using namespace ento; + +#define DEBUG_TYPE "UnsignedStatTester" + +static UnsignedEPStat DemoStat("DemoStat"); + +namespace { +class UnsignedStatTesterChecker : public Checker { +public: + void checkBeginFunction(CheckerContext &C) const; +}; +} // namespace + +void UnsignedStatTesterChecker::checkBeginFunction(CheckerContext &C) const { + const Decl *D = C.getLocationContext()->getDecl(); + if (!D) + return; + + std::string Name = + D->getAsFunction() ? D->getAsFunction()->getNameAsString() : ""; + + // Conditionally set the statistic based on the function name + if (Name == "func_one") { + DemoStat.set(1); + } else if (Name == "func_two") { + DemoStat.set(2); + } else if (Name == "func_three") { + DemoStat.set(3); + } + // For any other function (e.g., "func_none"), don't set the statistic +} + +void ento::registerUnsignedStatTesterChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterUnsignedStatTesterChecker(const CheckerManager &mgr) { + return true; +} diff --git a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp index feaf92a8043ca..395d2b2fefa65 100644 --- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp +++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp @@ -1,5 +1,5 @@ // REQUIRES: asserts -// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.UnsignedStatTester \ // RUN: -analyzer-config dump-entry-point-stats-to-csv="%t.csv" \ // RUN: -verify %s // RUN: %csv2json "%t.csv" | FileCheck --check-prefix=CHECK %s @@ -8,6 +8,7 @@ // CHECK-NEXT: "c:@F@fib#i#": { // CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "fib(unsigned int)", +// CHECK-NEXT: "DemoStat": "", // CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", @@ -42,9 +43,22 @@ // CHECK-NEXT: "NumZ3QueriesDone": "{{[0-9]+}}", // CHECK-NEXT: "TimeSpentSolvingZ3Queries": "{{[0-9]+}}" // CHECK-NEXT: }, +// CHECK-NEXT: "c:@F@func_one#": { +// CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", +// CHECK-NEXT: "DebugName": "func_one()", +// CHECK: "DemoStat": "1", +// .... not interesting statistics +// CHECK: }, +// CHECK-NEXT: "c:@F@func_two#": { +// CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", +// CHECK-NEXT: "DebugName": "func_two()", +// CHECK: "DemoStat": "2", +// .... not interesting statistics +// CHECK: }, // CHECK-NEXT: "c:@F@main#I#**C#": { // CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "main(int, char **)", +// CHECK-NEXT: "DemoStat": "", // CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", @@ -102,3 +116,6 @@ int main(int argc, char **argv) { int i = non_entry_point(argc); return i; } + +void func_one() {} +void func_two() {} From 22ce0fbee6d4f22b5b41dc1f469c4dcf4f2bcc2f Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Fri, 10 Oct 2025 15:04:02 +0200 Subject: [PATCH 04/21] [NFC] Fix code formatting --- clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index 33f5ccf6002ba..ce25b23651ed4 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -120,8 +120,7 @@ UnsignedEPStat::UnsignedEPStat(llvm::StringLiteral Name) StatsRegistry->ExplicitlySetStats.push_back(this); } -static std::vector> -consumeExplicitlySetStats() { +static std::vector> consumeExplicitlySetStats() { std::vector> Result; Result.reserve(StatsRegistry->ExplicitlySetStats.size()); for (auto *M : StatsRegistry->ExplicitlySetStats) { From 8e7f252d5f6868675546bcefbf62dc17df1e6b16 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Fri, 10 Oct 2025 16:38:17 +0200 Subject: [PATCH 05/21] Fix the entry-pint-stats.cpp for Windows --- clang/test/Analysis/analyzer-stats/entry-point-stats.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp index 395d2b2fefa65..5f9114b558777 100644 --- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp +++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp @@ -6,7 +6,7 @@ // // CHECK: { // CHECK-NEXT: "c:@F@fib#i#": { -// CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", +// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "fib(unsigned int)", // CHECK-NEXT: "DemoStat": "", // CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", @@ -44,19 +44,19 @@ // CHECK-NEXT: "TimeSpentSolvingZ3Queries": "{{[0-9]+}}" // CHECK-NEXT: }, // CHECK-NEXT: "c:@F@func_one#": { -// CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", +// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "func_one()", // CHECK: "DemoStat": "1", // .... not interesting statistics // CHECK: }, // CHECK-NEXT: "c:@F@func_two#": { -// CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", +// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "func_two()", // CHECK: "DemoStat": "2", // .... not interesting statistics // CHECK: }, // CHECK-NEXT: "c:@F@main#I#**C#": { -// CHECK-NEXT: "File": "{{.*}}/entry-point-stats.cpp", +// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "main(int, char **)", // CHECK-NEXT: "DemoStat": "", // CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", From 8f6b909bf02994b40e679a8a3c23090f058fe5e5 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 11:04:24 +0200 Subject: [PATCH 06/21] Update clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Donát Nagy --- .../StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp index a302f506a285a..d869e3f009411 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp @@ -50,10 +50,10 @@ void UnsignedStatTesterChecker::checkBeginFunction(CheckerContext &C) const { // For any other function (e.g., "func_none"), don't set the statistic } -void ento::registerUnsignedStatTesterChecker(CheckerManager &mgr) { - mgr.registerChecker(); +void ento::registerUnsignedStatTesterChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); } -bool ento::shouldRegisterUnsignedStatTesterChecker(const CheckerManager &mgr) { +bool ento::shouldRegisterUnsignedStatTesterChecker(const CheckerManager &) { return true; } From 5ae6a1f57a2d27d75e95c1c95ac95526d7d7b31c Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 11:05:49 +0200 Subject: [PATCH 07/21] Update clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Donát Nagy --- .../Checkers/UnsignedStatTesterChecker.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp index d869e3f009411..9f86c09ab0389 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp @@ -32,12 +32,10 @@ class UnsignedStatTesterChecker : public Checker { } // namespace void UnsignedStatTesterChecker::checkBeginFunction(CheckerContext &C) const { - const Decl *D = C.getLocationContext()->getDecl(); - if (!D) - return; - - std::string Name = - D->getAsFunction() ? D->getAsFunction()->getNameAsString() : ""; + std::string Name; + if (const Decl *D = C.getLocationContext()->getDecl()) + if (const FunctionDecl *F = D->getAsFunction()) + Name = F->getNameAsString(); // Conditionally set the statistic based on the function name if (Name == "func_one") { From c4513fa66d3b8c882bbd4cfb0ae7f862f58cd56c Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 11:14:40 +0200 Subject: [PATCH 08/21] Remove unused variant --- clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp index 9f86c09ab0389..baf21f82ce84f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp @@ -42,8 +42,6 @@ void UnsignedStatTesterChecker::checkBeginFunction(CheckerContext &C) const { DemoStat.set(1); } else if (Name == "func_two") { DemoStat.set(2); - } else if (Name == "func_three") { - DemoStat.set(3); } // For any other function (e.g., "func_none"), don't set the statistic } From bfb742610d1df74ae1298c7d1ca74241e60715d6 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 11:16:44 +0200 Subject: [PATCH 09/21] [NFC] Add an explicit else case, clarify comment --- .../Checkers/UnsignedStatTesterChecker.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp index baf21f82ce84f..8eee2733b890b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp @@ -37,13 +37,14 @@ void UnsignedStatTesterChecker::checkBeginFunction(CheckerContext &C) const { if (const FunctionDecl *F = D->getAsFunction()) Name = F->getNameAsString(); - // Conditionally set the statistic based on the function name - if (Name == "func_one") { + // Conditionally set the statistic based on the function name (leaving it + // undefined for all other functions) + if (Name == "func_one") DemoStat.set(1); - } else if (Name == "func_two") { + else if (Name == "func_two") DemoStat.set(2); - } - // For any other function (e.g., "func_none"), don't set the statistic + else + ; // For any other function don't set the statistic } void ento::registerUnsignedStatTesterChecker(CheckerManager &Mgr) { From 6b2111722a205f7ea57e85103d1f6f2bc2ed067c Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 11:19:38 +0200 Subject: [PATCH 10/21] Update clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Donát Nagy --- clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp index 8eee2733b890b..38c3f78be29d8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp @@ -20,8 +20,6 @@ using namespace clang; using namespace ento; -#define DEBUG_TYPE "UnsignedStatTester" - static UnsignedEPStat DemoStat("DemoStat"); namespace { From bfd6037308ef92e25c921bb815476ad7e4e7fbd1 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 11:20:38 +0200 Subject: [PATCH 11/21] Update clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Donát Nagy --- clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp index 38c3f78be29d8..4336cb01608cc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp @@ -1,4 +1,4 @@ -//== UnsignedStatTesterChecker.cpp --------------------------- -*- C++ -*--=// +//=== UnsignedStatTesterChecker.cpp -----------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. From b796401cf97e1a7ed58b1485e0477ada209731b7 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 13:54:25 +0200 Subject: [PATCH 12/21] Move UnsignedStatTesterChecker to unit tests --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 - .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 - .../Checkers/UnsignedStatTesterChecker.cpp | 54 ------ .../analyzer-stats/entry-point-stats.cpp | 19 +- clang/unittests/StaticAnalyzer/CMakeLists.txt | 1 + .../StaticAnalyzer/UnsignedStatDemo.cpp | 165 ++++++++++++++++++ 6 files changed, 167 insertions(+), 77 deletions(-) delete mode 100644 clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp create mode 100644 clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 0db6794b2310a..4473c54d8d6e3 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1502,10 +1502,6 @@ def TaintTesterChecker : Checker<"TaintTest">, HelpText<"Mark tainted symbols as such.">, Documentation; -def UnsignedStatTesterChecker : Checker<"UnsignedStatTester">, - HelpText<"Test checker for demonstrating UnsignedEPStat usage.">, - Documentation; - // This checker *technically* depends on SteamChecker, but we don't allow // dependency checkers to emit diagnostics, and a debug checker isn't worth // the chore needed to create a modeling portion on its own. Since this checker diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index f70ae1a3e8a96..29d2c4512d470 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -125,7 +125,6 @@ add_clang_library(clangStaticAnalyzerCheckers UninitializedObject/UninitializedPointee.cpp UnixAPIChecker.cpp UnreachableCodeChecker.cpp - UnsignedStatTesterChecker.cpp VforkChecker.cpp VLASizeChecker.cpp VAListChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp deleted file mode 100644 index 4336cb01608cc..0000000000000 --- a/clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp +++ /dev/null @@ -1,54 +0,0 @@ -//=== UnsignedStatTesterChecker.cpp -----------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This checker demonstrates the use of UnsignedEPStat for per-entry-point -// statistics. It conditionally sets a statistic based on the entry point name. -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h" - -using namespace clang; -using namespace ento; - -static UnsignedEPStat DemoStat("DemoStat"); - -namespace { -class UnsignedStatTesterChecker : public Checker { -public: - void checkBeginFunction(CheckerContext &C) const; -}; -} // namespace - -void UnsignedStatTesterChecker::checkBeginFunction(CheckerContext &C) const { - std::string Name; - if (const Decl *D = C.getLocationContext()->getDecl()) - if (const FunctionDecl *F = D->getAsFunction()) - Name = F->getNameAsString(); - - // Conditionally set the statistic based on the function name (leaving it - // undefined for all other functions) - if (Name == "func_one") - DemoStat.set(1); - else if (Name == "func_two") - DemoStat.set(2); - else - ; // For any other function don't set the statistic -} - -void ento::registerUnsignedStatTesterChecker(CheckerManager &Mgr) { - Mgr.registerChecker(); -} - -bool ento::shouldRegisterUnsignedStatTesterChecker(const CheckerManager &) { - return true; -} diff --git a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp index 5f9114b558777..2a0caad5950ec 100644 --- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp +++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp @@ -1,5 +1,5 @@ // REQUIRES: asserts -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.UnsignedStatTester \ +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ // RUN: -analyzer-config dump-entry-point-stats-to-csv="%t.csv" \ // RUN: -verify %s // RUN: %csv2json "%t.csv" | FileCheck --check-prefix=CHECK %s @@ -8,7 +8,6 @@ // CHECK-NEXT: "c:@F@fib#i#": { // CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "fib(unsigned int)", -// CHECK-NEXT: "DemoStat": "", // CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", @@ -43,22 +42,9 @@ // CHECK-NEXT: "NumZ3QueriesDone": "{{[0-9]+}}", // CHECK-NEXT: "TimeSpentSolvingZ3Queries": "{{[0-9]+}}" // CHECK-NEXT: }, -// CHECK-NEXT: "c:@F@func_one#": { -// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", -// CHECK-NEXT: "DebugName": "func_one()", -// CHECK: "DemoStat": "1", -// .... not interesting statistics -// CHECK: }, -// CHECK-NEXT: "c:@F@func_two#": { -// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", -// CHECK-NEXT: "DebugName": "func_two()", -// CHECK: "DemoStat": "2", -// .... not interesting statistics -// CHECK: }, // CHECK-NEXT: "c:@F@main#I#**C#": { // CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "main(int, char **)", -// CHECK-NEXT: "DemoStat": "", // CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}", @@ -116,6 +102,3 @@ int main(int argc, char **argv) { int i = non_entry_point(argc); return i; } - -void func_one() {} -void func_two() {} diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt index 9e10c4a4e637d..caf686e2a92e2 100644 --- a/clang/unittests/StaticAnalyzer/CMakeLists.txt +++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -20,6 +20,7 @@ add_clang_unittest(StaticAnalysisTests SValSimplifyerTest.cpp SValTest.cpp TestReturnValueUnderConstruction.cpp + UnsignedStatDemo.cpp Z3CrosscheckOracleTest.cpp CLANG_LIBS clangBasic diff --git a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp new file mode 100644 index 0000000000000..6af3046df38b6 --- /dev/null +++ b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp @@ -0,0 +1,165 @@ +//=== UnsignedStatDemo.cpp --------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This checker demonstrates the use of UnsignedEPStat for per-entry-point +// statistics. It conditionally sets a statistic based on the entry point name. +// +//===----------------------------------------------------------------------===// + +#include "CheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/MemoryBuffer.h" +#include "gtest/gtest.h" +#include + +using namespace clang; +using namespace ento; + +static UnsignedEPStat DemoStat("DemoStat"); + +namespace { +class UnsignedStatTesterChecker : public Checker { +public: + void checkBeginFunction(CheckerContext &C) const { + std::string Name; + if (const Decl *D = C.getLocationContext()->getDecl()) + if (const FunctionDecl *F = D->getAsFunction()) + Name = F->getNameAsString(); + + // Conditionally set the statistic based on the function name (leaving it + // undefined for all other functions) + if (Name == "func_one") + DemoStat.set(1); + else if (Name == "func_two") + DemoStat.set(2); + else + ; // For any other function (e.g., "func_none") don't set the statistic + } +}; + +void addUnsignedStatTesterChecker(AnalysisASTConsumer &AnalysisConsumer, + AnalyzerOptions &AnOpts) { + AnOpts.CheckersAndPackages = {{"test.DemoStatChecker", true}}; + AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { + Registry.addChecker( + "test.DemoStatChecker", "DescriptionOfDemoStatChecker"); + }); +} + +// Find the index of a column in the CSV header. +// Returns std::nullopt if the column is not found. +static std::optional +findColumnIndex(llvm::ArrayRef Header, + llvm::StringRef ColumnName) { + for (size_t i = 0; i < Header.size(); ++i) { + llvm::StringRef ColName = Header[i].trim().trim('"'); + if (ColName == ColumnName) + return static_cast(i); + } + return std::nullopt; +} + +// Extract function name from a DebugName column value. +// E.g., "func_one()" -> "func_one", "main(int, char **)" -> "main" +static llvm::StringRef extractFunctionName(llvm::StringRef DebugName) { + size_t ParenPos = DebugName.find('('); + if (ParenPos != llvm::StringRef::npos) + return DebugName.substr(0, ParenPos); + return DebugName; +} + +// Parse CSV content and extract a mapping from one column to another. +// KeyColumn is used as the map key (e.g., "DebugName"). +// ValueColumn is used as the map value (e.g., "DemoStat"). +// If KeyTransform is true, extracts function name from key (strips parameters). +// Returns a map from key column values to value column values. +static llvm::StringMap +parseCSVColumnMapping(llvm::StringRef CSVContent, llvm::StringRef KeyColumn, + llvm::StringRef ValueColumn, bool KeyTransform = false) { + llvm::StringMap Result; + + // Parse CSV: first line is header, subsequent lines are data + llvm::SmallVector Lines; + CSVContent.split(Lines, '\n', -1, false); + if (Lines.size() < 2) // Need at least header + one data row + return Result; + + // Parse header to find column indices + llvm::SmallVector Header; + Lines[0].split(Header, ','); + std::optional KeyIdx = findColumnIndex(Header, KeyColumn); + std::optional ValueIdx = findColumnIndex(Header, ValueColumn); + + if (!KeyIdx || !ValueIdx) + return Result; + + // Parse data rows and extract mappings + for (size_t i = 1; i < Lines.size(); ++i) { + llvm::SmallVector Row; + Lines[i].split(Row, ','); + if (Row.size() <= std::max(*KeyIdx, *ValueIdx)) + continue; + + llvm::StringRef KeyVal = Row[*KeyIdx].trim().trim('"'); + llvm::StringRef ValueVal = Row[*ValueIdx].trim().trim('"'); + + // Apply transformation to key if requested (e.g., extract function name) + if (KeyTransform) + KeyVal = extractFunctionName(KeyVal); + + if (!KeyVal.empty()) + Result[KeyVal] = ValueVal.str(); + } + + return Result; +} + +TEST(UnsignedStat, ExplicitlySetUnsignedStatistic) { + llvm::SmallString<128> TempMetricsCsvPath; + std::error_code EC = + llvm::sys::fs::createTemporaryFile("ep_stats", "csv", TempMetricsCsvPath); + ASSERT_FALSE(EC); + std::vector Args = { + "-Xclang", "-analyzer-config", "-Xclang", + std::string("dump-entry-point-stats-to-csv=") + + TempMetricsCsvPath.str().str()}; + // Clean up on exit + auto Cleanup = llvm::make_scope_exit( + [&]() { llvm::sys::fs::remove(TempMetricsCsvPath); }); + EXPECT_TRUE(runCheckerOnCodeWithArgs( + R"cpp( + void func_one() {} + void func_two() {} + void func_none() {} + )cpp", + Args)); + + auto BufferOrError = llvm::MemoryBuffer::getFile(TempMetricsCsvPath); + ASSERT_TRUE(BufferOrError); + llvm::StringRef CSVContent = BufferOrError.get()->getBuffer(); + + // Parse the CSV and extract function statistics + llvm::StringMap FunctionStats = + parseCSVColumnMapping(CSVContent, "DebugName", "DemoStat"); + + // Verify the expected values + ASSERT_TRUE(FunctionStats.count("func_one()")); + EXPECT_EQ(FunctionStats["func_one()"], "1"); + + ASSERT_TRUE(FunctionStats.count("func_two()")); + EXPECT_EQ(FunctionStats["func_two()"], "2"); + + ASSERT_TRUE(FunctionStats.count("func_none()")); + EXPECT_EQ(FunctionStats["func_none()"], ""); // Not set, should be empty +} +} // namespace From 01b73615b08c4bc92c708214a4661eb2b674b3df Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 13:57:00 +0200 Subject: [PATCH 13/21] [NFC] Remove unnecessary functionality from parseCSVColumnMapping --- .../StaticAnalyzer/UnsignedStatDemo.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp index 6af3046df38b6..f9da9d8fcbb17 100644 --- a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp +++ b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp @@ -69,23 +69,13 @@ findColumnIndex(llvm::ArrayRef Header, return std::nullopt; } -// Extract function name from a DebugName column value. -// E.g., "func_one()" -> "func_one", "main(int, char **)" -> "main" -static llvm::StringRef extractFunctionName(llvm::StringRef DebugName) { - size_t ParenPos = DebugName.find('('); - if (ParenPos != llvm::StringRef::npos) - return DebugName.substr(0, ParenPos); - return DebugName; -} - // Parse CSV content and extract a mapping from one column to another. // KeyColumn is used as the map key (e.g., "DebugName"). // ValueColumn is used as the map value (e.g., "DemoStat"). -// If KeyTransform is true, extracts function name from key (strips parameters). // Returns a map from key column values to value column values. static llvm::StringMap parseCSVColumnMapping(llvm::StringRef CSVContent, llvm::StringRef KeyColumn, - llvm::StringRef ValueColumn, bool KeyTransform = false) { + llvm::StringRef ValueColumn) { llvm::StringMap Result; // Parse CSV: first line is header, subsequent lines are data @@ -113,10 +103,6 @@ parseCSVColumnMapping(llvm::StringRef CSVContent, llvm::StringRef KeyColumn, llvm::StringRef KeyVal = Row[*KeyIdx].trim().trim('"'); llvm::StringRef ValueVal = Row[*ValueIdx].trim().trim('"'); - // Apply transformation to key if requested (e.g., extract function name) - if (KeyTransform) - KeyVal = extractFunctionName(KeyVal); - if (!KeyVal.empty()) Result[KeyVal] = ValueVal.str(); } From 245a196e987649bf9083967d55c0eb48bdacc6c7 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 13:59:34 +0200 Subject: [PATCH 14/21] [NFC] Simplify range for loop --- clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp index f9da9d8fcbb17..e3d1063b147df 100644 --- a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp +++ b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp @@ -16,6 +16,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/MemoryBuffer.h" @@ -94,9 +95,9 @@ parseCSVColumnMapping(llvm::StringRef CSVContent, llvm::StringRef KeyColumn, return Result; // Parse data rows and extract mappings - for (size_t i = 1; i < Lines.size(); ++i) { + for (auto Line : llvm::drop_begin(Lines)) { llvm::SmallVector Row; - Lines[i].split(Row, ','); + Line.split(Row, ','); if (Row.size() <= std::max(*KeyIdx, *ValueIdx)) continue; From 2a129669ffb0256b05a10aa1a9e2c97b733ab3b9 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 14:08:29 +0200 Subject: [PATCH 15/21] Avoid displaying the timers if they are used only for per-EP stats --- .../Frontend/AnalysisConsumer.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index e97004f1bbe52..51c28084ca7b0 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -126,6 +126,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, std::unique_ptr SyntaxCheckTimer; std::unique_ptr ExprEngineTimer; std::unique_ptr BugReporterTimer; + bool ShouldClearTimersToPreventDisplayingThem; /// The information about analyzed functions shared throughout the /// translation unit. @@ -155,6 +156,12 @@ class AnalysisConsumer : public AnalysisASTConsumer, *AnalyzerTimers); } + // Avoid displaying the timers created above in case we only want to record + // per-entry-point stats. + ShouldClearTimersToPreventDisplayingThem = !Opts.AnalyzerDisplayProgress && + !Opts.PrintStats && + !Opts.ShouldSerializeStats; + if (Opts.PrintStats || Opts.ShouldSerializeStats) { llvm::EnableStatistics(/* DoPrintOnExit= */ false); } @@ -277,6 +284,9 @@ class AnalysisConsumer : public AnalysisASTConsumer, checkerMgr->runCheckersOnASTDecl(D, *Mgr, *RecVisitorBR); if (SyntaxCheckTimer) SyntaxCheckTimer->stopTimer(); + if (ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } return true; } @@ -570,6 +580,9 @@ void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); if (SyntaxCheckTimer) SyntaxCheckTimer->stopTimer(); + if (ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } // Run the AST-only checks using the order in which functions are defined. // If inlining is not turned on, use the simplest function order for path @@ -746,6 +759,9 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, llvm::TimeRecord CheckerEndTime = SyntaxCheckTimer->getTotalTime(); CheckerEndTime -= CheckerStartTime; DisplayTime(CheckerEndTime); + if (ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } } @@ -792,6 +808,9 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, PathRunningTime.set(static_cast( std::lround(ExprEngineEndTime.getWallTime() * 1000))); DisplayTime(ExprEngineEndTime); + if (ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } if (!Mgr->options.DumpExplodedGraphTo.empty()) @@ -802,6 +821,9 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, Eng.ViewGraph(Mgr->options.TrimGraph); flushReports(BugReporterTimer.get(), Eng.getBugReporter()); + if (ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } //===----------------------------------------------------------------------===// From 4d6230499917ad9c99bde776ee6998cc9dde9aee Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 14:11:43 +0200 Subject: [PATCH 16/21] Fix nullptr dereference --- clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 51c28084ca7b0..68f66f99f5350 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -284,7 +284,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, checkerMgr->runCheckersOnASTDecl(D, *Mgr, *RecVisitorBR); if (SyntaxCheckTimer) SyntaxCheckTimer->stopTimer(); - if (ShouldClearTimersToPreventDisplayingThem) { + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { AnalyzerTimers->clear(); } } @@ -580,7 +580,7 @@ void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); if (SyntaxCheckTimer) SyntaxCheckTimer->stopTimer(); - if (ShouldClearTimersToPreventDisplayingThem) { + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { AnalyzerTimers->clear(); } @@ -759,7 +759,7 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, llvm::TimeRecord CheckerEndTime = SyntaxCheckTimer->getTotalTime(); CheckerEndTime -= CheckerStartTime; DisplayTime(CheckerEndTime); - if (ShouldClearTimersToPreventDisplayingThem) { + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { AnalyzerTimers->clear(); } } @@ -808,7 +808,7 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, PathRunningTime.set(static_cast( std::lround(ExprEngineEndTime.getWallTime() * 1000))); DisplayTime(ExprEngineEndTime); - if (ShouldClearTimersToPreventDisplayingThem) { + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { AnalyzerTimers->clear(); } } @@ -821,7 +821,7 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, Eng.ViewGraph(Mgr->options.TrimGraph); flushReports(BugReporterTimer.get(), Eng.getBugReporter()); - if (ShouldClearTimersToPreventDisplayingThem) { + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { AnalyzerTimers->clear(); } } From 197103e3d961b3afaaecab2800d7ca5e592ce47d Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 14:20:08 +0200 Subject: [PATCH 17/21] [NFC] Avoid constructing a string for a comparison's sake --- clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp index e3d1063b147df..4e3fdf7f61158 100644 --- a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp +++ b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp @@ -32,10 +32,10 @@ namespace { class UnsignedStatTesterChecker : public Checker { public: void checkBeginFunction(CheckerContext &C) const { - std::string Name; + StringRef Name; if (const Decl *D = C.getLocationContext()->getDecl()) if (const FunctionDecl *F = D->getAsFunction()) - Name = F->getNameAsString(); + Name = F->getName(); // Conditionally set the statistic based on the function name (leaving it // undefined for all other functions) From 79705dc53892ec032818aa89bec043e64bf0478f Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 14:36:53 +0200 Subject: [PATCH 18/21] [NFC] use std::find instead of a manual enumeration of col headers --- clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp index 4e3fdf7f61158..2d1323b9c17d8 100644 --- a/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp +++ b/clang/unittests/StaticAnalyzer/UnsignedStatDemo.cpp @@ -62,11 +62,9 @@ void addUnsignedStatTesterChecker(AnalysisASTConsumer &AnalysisConsumer, static std::optional findColumnIndex(llvm::ArrayRef Header, llvm::StringRef ColumnName) { - for (size_t i = 0; i < Header.size(); ++i) { - llvm::StringRef ColName = Header[i].trim().trim('"'); - if (ColName == ColumnName) - return static_cast(i); - } + auto Iter = llvm::find(Header, ColumnName); + if (Iter != Header.end()) + return std::distance(Header.begin(), Iter); return std::nullopt; } From a00774df4aadf8d5f55dea2e8a18c75f5e01b3aa Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 14:37:54 +0200 Subject: [PATCH 19/21] [NFC] Use upper-camel-case names for lambdas --- clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index ce25b23651ed4..bbad88649d342 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -165,14 +165,14 @@ static std::string getUSR(const Decl *D) { } void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { - auto printAsUnsignOpt = [&OS](std::optional U) { + auto PrintAsUnsignOpt = [&OS](std::optional U) { OS << (U.has_value() ? std::to_string(*U) : ""); }; - auto commaIfNeeded = [&OS](const auto &Vec1, const auto &Vec2) { + auto CommaIfNeeded = [&OS](const auto &Vec1, const auto &Vec2) { if (!Vec1.empty() && !Vec2.empty()) OS << ","; }; - auto printAsUnsigned = [&OS](unsigned U) { OS << U; }; + auto PrintAsUnsigned = [&OS](unsigned U) { OS << U; }; OS << '"'; llvm::printEscapedString(getUSR(EntryPoint), OS); @@ -181,9 +181,9 @@ void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { llvm::printEscapedString( clang::AnalysisDeclContext::getFunctionName(EntryPoint), OS); OS << "\","; - llvm::interleave(ExplicitlySetStatValues, OS, printAsUnsignOpt, ","); - commaIfNeeded(ExplicitlySetStatValues, MaxOrCountStatValues); - llvm::interleave(MaxOrCountStatValues, OS, printAsUnsigned, ","); + llvm::interleave(ExplicitlySetStatValues, OS, PrintAsUnsignOpt, ","); + CommaIfNeeded(ExplicitlySetStatValues, MaxOrCountStatValues); + llvm::interleave(MaxOrCountStatValues, OS, PrintAsUnsigned, ","); } void EntryPointStat::takeSnapshot(const Decl *EntryPoint) { From 7eee805d1fc81111f7c428871f074487794c5761 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 16:21:21 +0200 Subject: [PATCH 20/21] Clear statistics snapshots between unit tests --- .../StaticAnalyzer/Core/PathSensitive/EntryPointStats.h | 3 ++- clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp | 9 ++++++++- clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h index 0a45deb33fcc9..f3c6a629d9f2e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h @@ -9,6 +9,7 @@ #ifndef CLANG_INCLUDE_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_ENTRYPOINTSTATS_H #define CLANG_INCLUDE_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_ENTRYPOINTSTATS_H +#include "clang/AST/ASTContext.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringRef.h" @@ -25,7 +26,7 @@ class EntryPointStat { public: llvm::StringLiteral name() const { return Name; } - static void lockRegistry(llvm::StringRef CPPFileName); + static void lockRegistry(llvm::StringRef CPPFileName, ASTContext &Ctx); static void takeSnapshot(const Decl *EntryPoint); static void dumpStatsAsCSV(llvm::raw_ostream &OS); diff --git a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index bbad88649d342..9233b3572e18d 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -59,6 +59,9 @@ template void enumerateStatVectors(const Callback &Fn) { Fn(StatsRegistry->MaxStats); Fn(StatsRegistry->CounterStats); } + +void clearSnapshots(void*) { StatsRegistry->Snapshots.clear(); } + } // namespace static void checkStatName(const EntryPointStat *M) { @@ -78,7 +81,7 @@ static void checkStatName(const EntryPointStat *M) { } } -void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName) { +void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName, ASTContext &Ctx) { auto CmpByNames = [](const EntryPointStat *L, const EntryPointStat *R) { return L->name() < R->name(); }; @@ -89,6 +92,10 @@ void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName) { StatsRegistry->IsLocked = true; llvm::raw_string_ostream OS(StatsRegistry->EscapedCPPFileName); llvm::printEscapedString(CPPFileName, OS); + // Make sure snapshots (that reference function Decl's) do not persist after + // the AST is destroyed. This is especially relevant in the context of unit + // tests that construct and destruct multiple ASTs in the same process. + Ctx.AddDeallocation(clearSnapshots, nullptr); } [[maybe_unused]] static bool isRegistered(llvm::StringLiteral Name) { diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 68f66f99f5350..daec86a51e841 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -140,7 +140,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, Injector(std::move(injector)), CTU(CI), MacroExpansions(CI.getLangOpts()) { - EntryPointStat::lockRegistry(getMainFileName(CI.getInvocation())); + EntryPointStat::lockRegistry(getMainFileName(CI.getInvocation()), CI.getASTContext()); DigestAnalyzerOptions(); if (Opts.AnalyzerDisplayProgress || Opts.PrintStats || From ccea7fe85d3c46e73ee7853f7fa5c60b6c44ddab Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 13 Oct 2025 16:31:43 +0200 Subject: [PATCH 21/21] [NFC] Fix formatting --- clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp | 5 +++-- clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index 9233b3572e18d..c207a7b97917a 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -60,7 +60,7 @@ template void enumerateStatVectors(const Callback &Fn) { Fn(StatsRegistry->CounterStats); } -void clearSnapshots(void*) { StatsRegistry->Snapshots.clear(); } +void clearSnapshots(void *) { StatsRegistry->Snapshots.clear(); } } // namespace @@ -81,7 +81,8 @@ static void checkStatName(const EntryPointStat *M) { } } -void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName, ASTContext &Ctx) { +void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName, + ASTContext &Ctx) { auto CmpByNames = [](const EntryPointStat *L, const EntryPointStat *R) { return L->name() < R->name(); }; diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index daec86a51e841..4efde59aab763 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -140,7 +140,8 @@ class AnalysisConsumer : public AnalysisASTConsumer, Injector(std::move(injector)), CTU(CI), MacroExpansions(CI.getLangOpts()) { - EntryPointStat::lockRegistry(getMainFileName(CI.getInvocation()), CI.getASTContext()); + EntryPointStat::lockRegistry(getMainFileName(CI.getInvocation()), + CI.getASTContext()); DigestAnalyzerOptions(); if (Opts.AnalyzerDisplayProgress || Opts.PrintStats ||