[clang] [clang][analyzer] Add SyntaxRunningTime per-entry-point metric (PR #163341)
Arseniy Zaostrovnykh via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 20 07:59:10 PDT 2025
https://github.com/necto updated https://github.com/llvm/llvm-project/pull/163341
>From 164cd40a4948a5e0e0095d86158e4bfc288d6a5b Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 14 Oct 2025 10:13:15 +0200
Subject: [PATCH 1/3] [clang][analyzer] Add SyntaxRunningTime per-entry-point
metric
Per-entry-point metrics are captured during the path-sensitive analysis
time. For that reason, it is not trivial to add the syntax-only analysis
time as it runs in a separate stage. Luckily syntax-only analysis is
done before path-senstivie analysis.
I use the function summary field to keep the syntax-only anlaysis time
once syntax analysis is done, and then forward it to the per-EP metrics
snapshot during the path-sensitive analysis.
--
CPP-7099
---
.../Core/PathSensitive/FunctionSummary.h | 8 ++++
.../Frontend/AnalysisConsumer.cpp | 43 +++++++++++++++++++
.../analyzer-stats/entry-point-stats.cpp | 2 +
3 files changed, 53 insertions(+)
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<unsigned> 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/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
index 4efde59aab763..1775280fa7e1e 100644
--- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
+++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
@@ -721,6 +721,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,
@@ -759,6 +760,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);
if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) {
AnalyzerTimers->clear();
@@ -776,6 +779,36 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode,
}
}
+namespace {
+template <typename DeclT>
+static clang::Decl *preferDefinitionImpl(clang::Decl *D) {
+ if (auto *X = dyn_cast<DeclT>(D))
+ if (auto *Def = X->getDefinition())
+ return Def;
+ return D;
+}
+
+template <> clang::Decl *preferDefinitionImpl<ObjCMethodDecl>(clang::Decl *D) {
+ if (const auto *X = dyn_cast<ObjCMethodDecl>(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<VarDecl>(D);
+ D = preferDefinitionImpl<FunctionDecl>(D);
+ D = preferDefinitionImpl<TagDecl>(D);
+ D = preferDefinitionImpl<ObjCMethodDecl>(D);
+ assert(D);
+ return D;
+}
+} // namespace
+
//===----------------------------------------------------------------------===//
// Path-sensitive checking.
//===----------------------------------------------------------------------===//
@@ -792,6 +825,16 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D,
if (!Mgr->getAnalysisDeclContext(D)->getAnalysis<RelaxedLiveVariables>())
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.
diff --git a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp
index 2a0caad5950ec..c0c13d88a278d 100644
--- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp
+++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp
@@ -9,6 +9,7 @@
// 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: "MaxBugClassSize": "{{[0-9]+}}",
// CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}",
// CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}",
@@ -46,6 +47,7 @@
// 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: "MaxBugClassSize": "{{[0-9]+}}",
// CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}",
// CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}",
>From ceb77a73c8f46ee97c205f8b643469fc554a923d Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 14 Oct 2025 11:06:33 +0200
Subject: [PATCH 2/3] [NFC] Remove redundant anonymous namespace
---
clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
index 1775280fa7e1e..0e1679f5c7e04 100644
--- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
+++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
@@ -779,7 +779,6 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode,
}
}
-namespace {
template <typename DeclT>
static clang::Decl *preferDefinitionImpl(clang::Decl *D) {
if (auto *X = dyn_cast<DeclT>(D))
@@ -807,7 +806,6 @@ static Decl *getDefinitionOrCanonicalDecl(Decl *D) {
assert(D);
return D;
}
-} // namespace
//===----------------------------------------------------------------------===//
// Path-sensitive checking.
>From 13b4f6918a746e54e4484bfe5ab850ed8db5e9c9 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 14 Oct 2025 11:31:33 +0200
Subject: [PATCH 3/3] Use the same defining declaration of D as
getAnalysisDeclContext
---
.../Frontend/AnalysisConsumer.cpp | 35 +++----------------
1 file changed, 4 insertions(+), 31 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
index 0e1679f5c7e04..62513357a21a8 100644
--- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
+++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
@@ -779,34 +779,6 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode,
}
}
-template <typename DeclT>
-static clang::Decl *preferDefinitionImpl(clang::Decl *D) {
- if (auto *X = dyn_cast<DeclT>(D))
- if (auto *Def = X->getDefinition())
- return Def;
- return D;
-}
-
-template <> clang::Decl *preferDefinitionImpl<ObjCMethodDecl>(clang::Decl *D) {
- if (const auto *X = dyn_cast<ObjCMethodDecl>(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<VarDecl>(D);
- D = preferDefinitionImpl<FunctionDecl>(D);
- D = preferDefinitionImpl<TagDecl>(D);
- D = preferDefinitionImpl<ObjCMethodDecl>(D);
- assert(D);
- return D;
-}
-
//===----------------------------------------------------------------------===//
// Path-sensitive checking.
//===----------------------------------------------------------------------===//
@@ -818,12 +790,13 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D,
// FIXME: Inter-procedural analysis will need to handle invalid CFGs.
if (!Mgr->getCFG(D))
return;
-
+ auto *DeclContext = Mgr->getAnalysisDeclContext(D);
// See if the LiveVariables analysis scales.
- if (!Mgr->getAnalysisDeclContext(D)->getAnalysis<RelaxedLiveVariables>())
+ if (!DeclContext->getAnalysis<RelaxedLiveVariables>())
return;
- const Decl *DefDecl = getDefinitionOrCanonicalDecl(D);
+ // DeclContext declaration is the redeclaration of D that has a body.
+ const Decl *DefDecl = DeclContext->getDecl();
// 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
More information about the cfe-commits
mailing list