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
