https://github.com/steakhal created 
https://github.com/llvm/llvm-project/pull/187043

When a friend function template is defined inline inside a 
[[clang::suppress]]-annotated class but was forward-declared at namespace 
scope, the instantiation's lexical DeclContext was the namespace (from the 
forward-declaration), not the class.
The lexical parent chain walk in BugSuppression::isSuppressed therefore never 
reached the class and suppression did not apply.

Fix by extending preferTemplateDefinitionForTemplateSpecializations to handle 
FunctionDecl instances: calling getTemplateInstantiationPattern() that maps the 
instantiation back to the primary template FunctionDecl, whose lexical DC is 
the class where the friend was defined inline.

So the existing parent-chain walk then finds the suppression attribute.

Assisted-By: claude

From 9f200c664e945484eba98645c9435a9e48cd3d96 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Fri, 13 Mar 2026 12:53:23 +0000
Subject: [PATCH] [analyzer] Fix [[clang::suppress]] for friend function
 templates with namespace-scope forward-declarations

When a friend function template is defined inline inside a
[[clang::suppress]]-annotated class but was forward-declared at namespace
scope, the instantiation's lexical DeclContext was the namespace (from
the forward-declaration), not the class.
The lexical parent chain walk in BugSuppression::isSuppressed therefore
never reached the class and suppression did not apply.

Fix by extending preferTemplateDefinitionForTemplateSpecializations to
handle FunctionDecl instances: calling getTemplateInstantiationPattern()
that maps the instantiation back to the primary template FunctionDecl,
whose lexical DC is the class where the friend was defined inline.

So the existing parent-chain walk then finds the suppression attribute.

Assisted-By: claude
---
 clang/lib/StaticAnalyzer/Core/BugSuppression.cpp | 16 ++++++++++++++++
 clang/test/Analysis/clang-suppress/friends.cpp   | 12 ++++--------
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp 
b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
index 0e7b5b77a7e54..9e13c0169e879 100644
--- a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
@@ -232,6 +232,22 @@ template <class T> static const T 
*chooseDefinitionRedecl(const T *Tmpl) {
 // class templates. For any other decl, it returns the input unchagned.
 static const Decl *
 preferTemplateDefinitionForTemplateSpecializations(const Decl *D) {
+  // For function template specializations (including instantiated friend
+  // function templates), map back to the primary template's FunctionDecl so
+  // that the lexical parent chain walk reaches the class where the template
+  // was defined inline.
+  //
+  // This handles the case where a friend function template is defined inline
+  // inside a [[clang::suppress]]-annotated class but was pre-declared at
+  // namespace scope.  In that case the instantiation's lexical DC is the
+  // namespace (from the pre-declaration), not the class.  Walking back to the
+  // primary template FunctionDecl — whose lexical DC IS the class — lets the
+  // existing parent-chain walk find the suppression attribute.
+  if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
+    if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern())
+      return Pattern;
+  }
+
   const auto *SpecializationDecl = 
dyn_cast<ClassTemplateSpecializationDecl>(D);
   if (!SpecializationDecl)
     return D;
diff --git a/clang/test/Analysis/clang-suppress/friends.cpp 
b/clang/test/Analysis/clang-suppress/friends.cpp
index acd3193eca0e2..1f89567d399bd 100644
--- a/clang/test/Analysis/clang-suppress/friends.cpp
+++ b/clang/test/Analysis/clang-suppress/friends.cpp
@@ -153,8 +153,7 @@ extern void b3_suppressed(T);
 struct [[clang::suppress]] B3_Suppressed {
   template <typename T>
   friend void b3_suppressed(T) {
-    // FIXME: This should be suppressed.
-    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    clang_analyzer_warnIfReached(); // no-warning
   }
 };
 template <typename T>
@@ -280,8 +279,7 @@ template <typename T> void e3_multi_redecl(T);
 struct [[clang::suppress]] E3_Suppressed {
   template <typename T>
   friend void e3_multi_redecl(T) {
-    // FIXME: This should be suppressed.
-    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    clang_analyzer_warnIfReached(); // no-warning
   }
 };
 void test_E3() {
@@ -325,8 +323,7 @@ template <typename U>
 struct [[clang::suppress]] E6_SuppressedTmpl {
   template <typename T>
   friend void e6_combined(T) {
-    // FIXME: This should be suppressed.
-    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    clang_analyzer_warnIfReached(); // no-warning
   }
 };
 void test_E6() {
@@ -356,8 +353,7 @@ template <typename T> void e8_fwd_multi(T);
 struct [[clang::suppress]] E8_Suppressed {
   template <typename T>
   friend void e8_fwd_multi(T) {
-    // FIXME: This should be suppressed.
-    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    clang_analyzer_warnIfReached(); // no-warning
   }
 };
 void test_E8() {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to