Skip to content

Conversation

@necto
Copy link
Contributor

@necto necto commented Oct 14, 2025

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.

Note that some of the entry points that were analyzed by syntax-only rules may be missing in the CSV export if they were never analyzed by path-sensitive rules. Conversely, if a function is analyzed with path-sensitive analysis but not syntax-only analysis, its SyntaxRunningTime will be empty.

--

CPP-7099

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
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Oct 14, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 14, 2025

@llvm/pr-subscribers-clang-static-analyzer-1

@llvm/pr-subscribers-clang

Author: Arseniy Zaostrovnykh (necto)

Changes

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


Full diff: https://github.com/llvm/llvm-project/pull/163341.diff

3 Files Affected:

  • (modified) clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h (+8)
  • (modified) clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp (+43)
  • (modified) clang/test/Analysis/analyzer-stats/entry-point-stats.cpp (+2)
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]+}}",

@necto
Copy link
Contributor Author

necto commented Oct 14, 2025

This is the final PR out of the triple replacing #162089.

Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, I don't see any issues.

@necto necto requested a review from steakhal October 14, 2025 09:45
Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Have you checked that now the findSummary would get hits when it's using the DefDecl?

@necto
Copy link
Contributor Author

necto commented Oct 20, 2025

LGTM. Have you checked that now the findSummary would get hits when it's using the DefDecl?

I have checked now. in the context of RunPathSensitiveChecks analysisContext->getDecl() is equivalent for function decls (but it is different than getDefinitionOrCanonicalDecl for some ObjcMethodDecls, and it is more correct), so I will keep it

@necto necto enabled auto-merge (squash) October 20, 2025 15:02
@necto necto merged commit 38372df into llvm:main Oct 20, 2025
9 of 10 checks passed
@necto necto deleted the az/time-syntax branch October 20, 2025 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:static analyzer clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants