Author: Balázs Benics
Date: 2026-03-17T15:08:47Z
New Revision: 6d107523b14bacad87755315a8a2829e10544176

URL: 
https://github.com/llvm/llvm-project/commit/6d107523b14bacad87755315a8a2829e10544176
DIFF: 
https://github.com/llvm/llvm-project/commit/6d107523b14bacad87755315a8a2829e10544176.diff

LOG: [analyzer] Fix [[clang::suppress]] for nested templates (#183727)

For nested templates, we might need to walk the member template chain to
get to the primary template. This can be an arbitrary long chain, of the
partial specializations.

Previously, we hit the assertion `This class template must have a redecl
that is a definition` because we only walked the redecls of the given
template. However, that redecl-chain might not have a redecl that is the
definition.
Sometimes (in case of member templates) you also need to walk the member
specialization and continue walking the redecls of that one.

(Don't ask me more, because I have no clue how these nested templates
and their redecls are implemented. It just works™️)

Fixes #182659
Assisted-by: claude

Added: 
    clang/test/Analysis/clang-suppress/nested-templates.cpp

Modified: 
    clang/lib/StaticAnalyzer/Core/BugSuppression.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp 
b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
index f3e9b0929f9c4..0e7b5b77a7e54 100644
--- a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
@@ -9,6 +9,7 @@
 #include "clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h"
 #include "clang/AST/DynamicRecursiveASTVisitor.h"
 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/TimeProfiler.h"
 
@@ -165,6 +166,55 @@ bool BugSuppression::isSuppressed(const BugReport &R) {
          isSuppressed(UniqueingLocation, DeclWithIssue, {});
 }
 
+static const ClassTemplateDecl *
+walkInstantiatedFromChain(const ClassTemplateDecl *Tmpl) {
+  // For nested member templates (e.g., S2 inside S1<T>), getInstantiatedFrom
+  // may return the member template as instantiated within an outer
+  // specialization (e.g., S2 as it appears in S1<int>).  That instantiated
+  // member template has no definition redeclaration itself; we need to walk
+  // up the member template chain to reach the primary template definition.
+  // \code
+  //   template <class> struct S1 {
+  //     template <class> struct S2 {
+  //       int i;
+  //       template <class T> int m(const S2<T>& s2) {
+  //         return s2.i;
+  //       }
+  //     };
+  //   }
+  // /code
+  const ClassTemplateDecl *MemberTmpl;
+  while ((MemberTmpl = Tmpl->getInstantiatedFromMemberTemplate())) {
+    if (Tmpl->isMemberSpecialization())
+      break;
+    Tmpl = MemberTmpl;
+  }
+  return Tmpl;
+}
+
+static const ClassTemplatePartialSpecializationDecl *walkInstantiatedFromChain(
+    const ClassTemplatePartialSpecializationDecl *PartialSpec) {
+  const ClassTemplatePartialSpecializationDecl *MemberPS;
+  while ((MemberPS = PartialSpec->getInstantiatedFromMember())) {
+    if (PartialSpec->isMemberSpecialization())
+      break;
+    PartialSpec = MemberPS;
+  }
+  return PartialSpec;
+}
+
+template <class T> static const T *chooseDefinitionRedecl(const T *Tmpl) {
+  static_assert(llvm::is_one_of<T, ClassTemplateDecl,
+                                
ClassTemplatePartialSpecializationDecl>::value);
+  for (const auto *Redecl : Tmpl->redecls()) {
+    if (const T *D = cast<T>(Redecl); D->isThisDeclarationADefinition()) {
+      return D;
+    }
+  }
+  assert(false && "This template must have a redecl that is a definition");
+  return Tmpl;
+}
+
 // For template specializations, returns the primary template definition or
 // partial specialization that was used to instantiate the specialization.
 // This ensures suppression attributes on templates apply to their
@@ -178,12 +228,8 @@ bool BugSuppression::isSuppressed(const BugReport &R) {
 // back to the primary template definition, allowing us to find the suppression
 // attribute.
 //
-// The function handles two cases:
-// 1. Instantiation from a class template - searches redeclarations to find
-//    the definition (not just a forward declaration).
-// 2. Instantiation from a partial specialization - returns it directly.
-//
-// For non-template-specialization decls, returns the input unchanged.
+// The function handles specializations (and partial specializations) of
+// class templates. For any other decl, it returns the input unchagned.
 static const Decl *
 preferTemplateDefinitionForTemplateSpecializations(const Decl *D) {
   const auto *SpecializationDecl = 
dyn_cast<ClassTemplateSpecializationDecl>(D);
@@ -194,27 +240,13 @@ preferTemplateDefinitionForTemplateSpecializations(const 
Decl *D) {
   if (!InstantiatedFrom)
     return D;
 
-  // This might be a class template.
   if (const auto *Tmpl = InstantiatedFrom.dyn_cast<ClassTemplateDecl *>()) {
     // Interestingly, the source template might be a forward declaration, so we
     // need to find the definition redeclaration.
-    for (const auto *Redecl : Tmpl->redecls()) {
-      if (cast<ClassTemplateDecl>(Redecl)->isThisDeclarationADefinition()) {
-        return Redecl;
-      }
-    }
-    assert(false &&
-           "This class template must have a redecl that is a definition");
-    return D;
+    return chooseDefinitionRedecl(walkInstantiatedFromChain(Tmpl));
   }
-
-  // It might be a partial specialization.
-  const auto *PartialSpecialization =
-      InstantiatedFrom.dyn_cast<ClassTemplatePartialSpecializationDecl *>();
-
-  // The partial specialization should be a definition.
-  assert(PartialSpecialization->isThisDeclarationADefinition());
-  return PartialSpecialization;
+  return chooseDefinitionRedecl(walkInstantiatedFromChain(
+      cast<ClassTemplatePartialSpecializationDecl *>(InstantiatedFrom)));
 }
 
 bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,

diff  --git a/clang/test/Analysis/clang-suppress/nested-templates.cpp 
b/clang/test/Analysis/clang-suppress/nested-templates.cpp
new file mode 100644
index 0000000000000..0b5d1d5d98878
--- /dev/null
+++ b/clang/test/Analysis/clang-suppress/nested-templates.cpp
@@ -0,0 +1,340 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify 
%s
+
+void clang_analyzer_warnIfReached();
+
+// Placeholder types for triggering instantiations.
+// - Type{A,B,C,D} should match an unconstrained template type parameter.
+struct TypeA{};
+struct TypeB{};
+struct TypeC{};
+struct TypeD{};
+
+// ============================================================================
+// Group A: 2-level nesting — attribute on outer
+// ============================================================================
+
+template <typename A>
+struct [[clang::suppress]] TwoLevel_AttrOuter {
+  template <typename B>
+  struct Inner {
+    void inline_method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+    void outline_method();
+  };
+  void outer_inline() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+  void outer_outline();
+};
+
+template <typename A>
+template <typename B>
+void TwoLevel_AttrOuter<A>::Inner<B>::outline_method() {
+  // Out-of-line: lexical context is namespace, not the class.
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename A>
+void TwoLevel_AttrOuter<A>::outer_outline() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void test_two_level_outer() {
+  TwoLevel_AttrOuter<TypeA>::Inner<TypeB>().inline_method();
+  TwoLevel_AttrOuter<TypeA>::Inner<TypeB>().outline_method();
+  TwoLevel_AttrOuter<TypeA>().outer_inline();
+  TwoLevel_AttrOuter<TypeA>().outer_outline();
+}
+
+// ============================================================================
+// Group B: 2-level nesting — attribute on inner
+// ============================================================================
+
+template <typename A>
+struct TwoLevel_AttrInner {
+  template <typename B>
+  struct [[clang::suppress]] Inner {
+    void inline_method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+    void outline_method();
+  };
+  void outer_inline() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+template <typename A>
+template <typename B>
+void TwoLevel_AttrInner<A>::Inner<B>::outline_method() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void test_two_level_inner() {
+  TwoLevel_AttrInner<TypeA>::Inner<TypeB>().inline_method();
+  TwoLevel_AttrInner<TypeA>::Inner<TypeB>().outline_method();
+  TwoLevel_AttrInner<TypeA>().outer_inline();
+}
+
+// ============================================================================
+// Group C: 3-level nesting — attribute at each level
+// ============================================================================
+
+// --- C1: attribute on outermost ---
+
+template <typename A>
+struct [[clang::suppress]] ThreeLevel_AttrOuter {
+  template <typename B>
+  struct Mid {
+    template <typename C>
+    struct Inner {
+      void method() {
+        clang_analyzer_warnIfReached(); // no-warning
+      }
+    };
+    void mid_method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+};
+
+void test_three_level_outer() {
+  ThreeLevel_AttrOuter<TypeA>::Mid<TypeB>::Inner<TypeC>().method();
+  ThreeLevel_AttrOuter<TypeA>::Mid<TypeB>().mid_method();
+}
+
+// --- C2: attribute on middle ---
+
+template <typename A>
+struct ThreeLevel_AttrMid {
+  template <typename B>
+  struct [[clang::suppress]] Mid {
+    template <typename C>
+    struct Inner {
+      void method() {
+        clang_analyzer_warnIfReached(); // no-warning
+      }
+    };
+    void mid_method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+  void outer_method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+void test_three_level_mid() {
+  ThreeLevel_AttrMid<TypeA>::Mid<TypeB>::Inner<TypeC>().method();
+  ThreeLevel_AttrMid<TypeA>::Mid<TypeB>().mid_method();
+  ThreeLevel_AttrMid<TypeA>().outer_method();
+}
+
+// --- C3: attribute on innermost ---
+
+template <typename A>
+struct ThreeLevel_AttrInner {
+  template <typename B>
+  struct Mid {
+    template <typename C>
+    struct [[clang::suppress]] Inner {
+      void method() {
+        clang_analyzer_warnIfReached(); // no-warning
+      }
+    };
+    void mid_method() {
+      clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    }
+  };
+  void outer_method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+void test_three_level_inner() {
+  ThreeLevel_AttrInner<TypeA>::Mid<TypeB>::Inner<TypeC>().method();
+  ThreeLevel_AttrInner<TypeA>::Mid<TypeB>().mid_method();
+  ThreeLevel_AttrInner<TypeA>().outer_method();
+}
+
+// --- C4: no attribute at any level (negative test) ---
+
+template <typename A>
+struct ThreeLevel_NoAttr {
+  template <typename B>
+  struct Mid {
+    template <typename C>
+    struct Inner {
+      void method() {
+        clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+      }
+    };
+    void mid_method() {
+      clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    }
+  };
+  void outer_method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+void test_three_level_none() {
+  ThreeLevel_NoAttr<TypeA>::Mid<TypeB>::Inner<TypeC>().method();
+  ThreeLevel_NoAttr<TypeA>::Mid<TypeB>().mid_method();
+  ThreeLevel_NoAttr<TypeA>().outer_method();
+}
+
+// ============================================================================
+// Group D: Mixed template / non-template nesting
+// ============================================================================
+
+// --- D1: non-template outer, template inner ---
+
+struct [[clang::suppress]] NonTmplOuter_TmplInner {
+  template <typename T>
+  struct Inner {
+    void method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+};
+
+struct NonTmplOuter_TmplInner_NoAttr {
+  template <typename T>
+  struct Inner {
+    void method() {
+      clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    }
+  };
+};
+
+void test_mixed_nontmpl_outer() {
+  NonTmplOuter_TmplInner::Inner<TypeA>().method();
+  NonTmplOuter_TmplInner_NoAttr::Inner<TypeA>().method();
+}
+
+// --- D2: template outer, non-template inner ---
+
+template <typename T>
+struct [[clang::suppress]] TmplOuter_NonTmplInner {
+  struct Inner {
+    void method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+};
+
+template <typename T>
+struct TmplOuter_NonTmplInner_NoAttr {
+  struct Inner {
+    void method() {
+      clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    }
+  };
+};
+
+void test_mixed_tmpl_outer() {
+  TmplOuter_NonTmplInner<TypeA>::Inner().method();
+  TmplOuter_NonTmplInner_NoAttr<TypeA>::Inner().method();
+}
+
+// ============================================================================
+// Group E: Multiple instantiations of the same nested template
+// ============================================================================
+
+// Ensure suppression applies across 
diff erent instantiation parameters.
+
+template <typename A>
+struct [[clang::suppress]] MultiInst {
+  template <typename B>
+  struct Inner {
+    void method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+};
+
+void test_multi_inst() {
+  MultiInst<TypeA>::Inner<TypeA>().method();
+  MultiInst<TypeA>::Inner<TypeB>().method();
+  MultiInst<TypeB>::Inner<TypeA>().method();
+  MultiInst<TypeC>::Inner<TypeD>().method();
+}
+
+// ============================================================================
+// Group F: Nested template with methods that have their own template params
+// ============================================================================
+
+template <typename A>
+struct [[clang::suppress]] NestedWithTemplateMethods {
+  template <typename B>
+  struct Inner {
+    template <typename C>
+    void tmpl_method(C) {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+};
+
+template <typename A>
+struct NestedWithTemplateMethods_NoAttr {
+  template <typename B>
+  struct Inner {
+    template <typename C>
+    void tmpl_method(C) {
+      clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+    }
+  };
+};
+
+void test_nested_tmpl_methods() {
+  NestedWithTemplateMethods<TypeA>::Inner<TypeB>().tmpl_method(TypeC{});
+  NestedWithTemplateMethods<TypeA>::Inner<TypeB>().tmpl_method(TypeD{});
+  NestedWithTemplateMethods_NoAttr<TypeA>::Inner<TypeB>().tmpl_method(TypeC{});
+}
+
+// ============================================================================
+// Group G: Attribute on both outer and inner (redundant but should work)
+// ============================================================================
+
+template <typename A>
+struct [[clang::suppress]] BothSuppressed {
+  template <typename B>
+  struct [[clang::suppress]] Inner {
+    void method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+  void outer_method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+};
+
+void test_both_suppressed() {
+  BothSuppressed<TypeA>::Inner<TypeB>().method();
+  BothSuppressed<TypeA>().outer_method();
+}
+
+// ============================================================================
+// Regression test for gh#182659
+// ============================================================================
+
+// Nested template structs where the inner method accesses a member of a
+// 
diff erent specialization — verifies that the suppression mechanism does not
+// accidentally suppress legitimate warnings when walking instantiation chains.
+
+template <class> struct gh_182659_s1 {
+  template <class> struct gh_182659_s2 {
+    int i;
+    template <class T> int m(const gh_182659_s2<T>& s2) {
+      return s2.i; // expected-warning{{Undefined or garbage value returned to 
caller}}
+    }
+  };
+};
+
+void gh_182659() {
+  gh_182659_s1<TypeA>::gh_182659_s2<TypeA> s1;
+  gh_182659_s1<TypeA>::gh_182659_s2<TypeB> s2;
+  s1.m(s2);
+}


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

Reply via email to