llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

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

Author: Balázs Benics (steakhal)

<details>
<summary>Changes</summary>

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.

Assisted-by: claude

---

Patch is 20.62 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/183727.diff


2 Files Affected:

- (modified) clang/lib/StaticAnalyzer/Core/BugSuppression.cpp (+53-20) 
- (modified) clang/test/Analysis/suppression-attr.cpp (+547) 


``````````diff
diff --git a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp 
b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
index f3e9b0929f9c4..06386a0414b96 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,53 @@ 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
+  while (auto *MemberTmpl = Tmpl->getInstantiatedFromMemberTemplate()) {
+    if (Tmpl->isMemberSpecialization())
+      break;
+    Tmpl = MemberTmpl;
+  }
+  return Tmpl;
+}
+
+static const ClassTemplatePartialSpecializationDecl *walkInstantiatedFromChain(
+    const ClassTemplatePartialSpecializationDecl *PartialSpec) {
+  while (auto *MemberPS = PartialSpec->getInstantiatedFromMember()) {
+    if (PartialSpec->isMemberSpecialization())
+      break;
+    PartialSpec = MemberPS;
+  }
+  return PartialSpec;
+}
+
+template <class T> static const auto *chooseDefinitionRedecl(const T *Tmpl) {
+  static_assert(llvm::is_one_of<T, ClassTemplateDecl,
+                                
ClassTemplatePartialSpecializationDecl>::value);
+  for (const auto *Redecl : Tmpl->redecls()) {
+    if (const auto *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
@@ -179,9 +227,8 @@ bool BugSuppression::isSuppressed(const BugReport &R) {
 // 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.
+// 1. Class template specializations.
+// 2. Class template partial specializations.
 //
 // For non-template-specialization decls, returns the input unchanged.
 static const Decl *
@@ -194,27 +241,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/suppression-attr.cpp 
b/clang/test/Analysis/suppression-attr.cpp
index 9ba56d976fddb..f789304e353a1 100644
--- a/clang/test/Analysis/suppression-attr.cpp
+++ b/clang/test/Analysis/suppression-attr.cpp
@@ -89,3 +89,550 @@ int SuppressedMethodClass::bar2() {
   int *x = 0;
   return *x; // no-warning
 }
+
+
+template <class> struct S1 {
+  template <class> struct S2 {
+    int i;
+    template <class T> int m(const S2<T>& s2) {
+      return s2.i; // expected-warning{{Undefined or garbage value returned to 
caller}}
+    }
+  };
+};
+
+void gh_182659() {
+  S1<int>::S2<int> s1;
+  S1<int>::S2<char> s2;
+  s1.m(s2);
+}
+
+template <typename T>
+class [[clang::suppress]] ClassTemplateAttrOnClass {
+public:
+  void inline_method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+  void out_of_line_method();
+};
+
+template <typename T>
+void ClassTemplateAttrOnClass<T>::out_of_line_method() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename T>
+struct ClassTemplateAttrOnOutOfLineDef {
+  void method();
+};
+
+template <typename T>
+[[clang::suppress]]
+void ClassTemplateAttrOnOutOfLineDef<T>::method() {
+  clang_analyzer_warnIfReached(); // no-warning
+}
+
+template <typename T>
+struct ClassTemplateAttrOnDecl {
+  [[clang::suppress]] void method();
+};
+
+template <typename T>
+void ClassTemplateAttrOnDecl<T>::method() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void instantiate_template_class() {
+  ClassTemplateAttrOnClass<int>().inline_method();
+  ClassTemplateAttrOnClass<int>().out_of_line_method();
+  ClassTemplateAttrOnOutOfLineDef<int>().method();
+  ClassTemplateAttrOnDecl<int>().method();
+}
+
+// Just the declaration.
+template <typename T> [[clang::suppress]] void FunctionTemplateSuppressed(T);
+template <typename T>
+void FunctionTemplateSuppressed(T) {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename T>
+void FunctionTemplateUnsuppressed(T) {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void instantiate_function_templates() {
+  FunctionTemplateSuppressed(0);
+  FunctionTemplateUnsuppressed(0);
+}
+
+// Only the <int*> specialization carries the attribute.
+template <typename T>
+struct ExplicitFullClassSpecializationAttrOnSpec {
+  void method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+template <>
+class [[clang::suppress]] ExplicitFullClassSpecializationAttrOnSpec<int *> {
+public:
+  void method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+};
+
+// Only the primary template carries the attribute.  The explicit
+// specialization is a completely independent class and is NOT suppressed.
+template <typename T>
+class [[clang::suppress]] ExplicitFullClassSpecializationAttrOnPrimary {
+public:
+  void method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+};
+
+template <>
+struct ExplicitFullClassSpecializationAttrOnPrimary<int *> {
+  void method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+void instantiate_full_spec_class() {
+  ExplicitFullClassSpecializationAttrOnSpec<long>().method();   // warns 
(primary)
+  ExplicitFullClassSpecializationAttrOnSpec<int *>().method();  // suppressed 
(explicit specialization)
+
+  ExplicitFullClassSpecializationAttrOnPrimary<long>().method();   // 
suppressed (primary)
+  ExplicitFullClassSpecializationAttrOnPrimary<int *>().method();  // warns 
(explicit specialization)
+}
+
+// Only the <int *> specialization is suppressed.
+template <typename T>
+void ExplicitFullFunctionSpecializationAttrOnSpec(T) {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <>
+[[clang::suppress]] void ExplicitFullFunctionSpecializationAttrOnSpec(int *) {
+  clang_analyzer_warnIfReached(); // no-warning
+}
+
+// Only the primary template is suppressed.
+template <typename T>
+[[clang::suppress]] void ExplicitFullFunctionSpecializationAttrOnPrimary(T) {
+  clang_analyzer_warnIfReached(); // no-warning
+}
+
+template <>
+void ExplicitFullFunctionSpecializationAttrOnPrimary(int *) {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void instantiate_full_spec_function() {
+  ExplicitFullFunctionSpecializationAttrOnSpec(0L);      // warns (primary)
+  ExplicitFullFunctionSpecializationAttrOnSpec((int *)nullptr); // suppressed 
(explicit specialization)
+
+  ExplicitFullFunctionSpecializationAttrOnPrimary(0L);      // suppressed 
(primary)
+  ExplicitFullFunctionSpecializationAttrOnPrimary((int *)nullptr); // warns 
(explicit specialization)
+}
+
+// Only the <T, int *> partial specialization carries the attribute.
+template <typename T, typename U>
+struct PartialClassSpecializationAttrOnPartial {
+  void method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+template <typename T>
+class [[clang::suppress]] PartialClassSpecializationAttrOnPartial<T, int *> {
+public:
+  void method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+};
+
+// Only the primary template carries the attribute; partial spec is separate.
+template <typename T, typename U>
+class [[clang::suppress]] PartialClassSpecializationAttrOnPrimary {
+public:
+  void method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+};
+
+template <typename T>
+struct PartialClassSpecializationAttrOnPrimary<T, int *> {
+  void method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+void instantiate_partial_spec() {
+  PartialClassSpecializationAttrOnPartial<long, long>().method();    // warns 
(primary)
+  PartialClassSpecializationAttrOnPartial<long, int *>().method();   // 
suppressed (partial spec)
+
+  PartialClassSpecializationAttrOnPrimary<long, long>().method();    // 
suppressed (primary)
+  PartialClassSpecializationAttrOnPrimary<long, int *>().method();   // warns 
(partial spec)
+}
+
+// Attribute on outer -> suppresses both outer and inner inline methods.
+template <typename T>
+class [[clang::suppress]] NestedTemplateClassAttrOnOuter {
+public:
+  void outer_method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+
+  template <typename U>
+  struct Inner {
+    void method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+};
+
+// Attribute on inner only -> outer method NOT suppressed.
+template <typename T>
+struct NestedTemplateClassAttrOnInner {
+  void outer_method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+
+  template <typename U>
+  class [[clang::suppress]] Inner {
+  public:
+    void method() {
+      clang_analyzer_warnIfReached(); // no-warning
+    }
+  };
+};
+
+void instantiate_nested() {
+  NestedTemplateClassAttrOnOuter<int>().outer_method();
+  NestedTemplateClassAttrOnOuter<int>::Inner<long>().method();
+
+  NestedTemplateClassAttrOnInner<int>().outer_method();
+  NestedTemplateClassAttrOnInner<int>::Inner<long>().method();
+}
+
+struct NonTemplateClassWithTemplatedMethod {
+  template <typename T>
+  [[clang::suppress]] void suppressed(T) {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+
+  template <typename T>
+  void unsuppressed(T) {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+void instantiate_nontpl_templated_method() {
+  NonTemplateClassWithTemplatedMethod obj;
+  obj.suppressed(0);
+  obj.unsuppressed(0);
+}
+
+template <typename T>
+struct TemplateClassWithTemplateMethod {
+  template <typename U>
+  [[clang::suppress]] void suppressed(U) {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+
+  template <typename U>
+  void unsuppressed(U) {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+
+  template <typename U>
+  [[clang::suppress]] void suppress_at_decl_outline(U);
+
+  template <typename U>
+  void suppress_at_def_outline(U);
+};
+
+template <typename T>
+template <typename U>
+void TemplateClassWithTemplateMethod<T>::suppress_at_decl_outline(U) {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename T>
+template <typename U>
+[[clang::suppress]] void 
TemplateClassWithTemplateMethod<T>::suppress_at_def_outline(U) {
+  clang_analyzer_warnIfReached(); // no-warning
+}
+
+void instantiate_tpl_class_tpl_method() {
+  TemplateClassWithTemplateMethod<int> obj;
+  obj.suppressed(0L);
+  obj.unsuppressed(0L);
+  obj.suppress_at_decl_outline(0L);
+  obj.suppress_at_def_outline(0L);
+}
+
+// A simple "box" template used as a template-template argument.
+template <typename T>
+struct Box {
+  void get() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+// A version of Box that suppresses its own methods.
+template <typename T>
+class [[clang::suppress]] SuppressedBox {
+public:
+  void get() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+};
+
+// Adaptor whose own methods are suppressed; the contained Box's methods are 
not.
+template <typename T, template <typename> class Container>
+class [[clang::suppress]] SuppressedAdaptor {
+public:
+  Container<T> data;
+
+  void adaptor_method() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+};
+
+// Adaptor with no suppression; Box's own suppression is independent.
+template <typename T, template <typename> class Container>
+struct UnsuppressedAdaptor {
+  Container<T> data;
+
+  void adaptor_method() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+void instantiate_template_template() {
+  // SuppressedAdaptor<Box>: adaptor method suppressed; Box::get not affected.
+  SuppressedAdaptor<int, Box> sa;
+  sa.adaptor_method();  // suppressed by adaptor's attr
+  sa.data.get();        // warns — Box has no attr, different lexical context
+
+  // UnsuppressedAdaptor<SuppressedBox>: adaptor warns; SuppressedBox::get 
suppressed.
+  UnsuppressedAdaptor<int, SuppressedBox> ua;
+  ua.adaptor_method();  // warns — adaptor has no attr
+  ua.data.get();        // suppressed by SuppressedBox's attr
+}
+
+template <typename... Args>
+[[clang::suppress]] void Variadic_Suppressed(Args...) {
+  clang_analyzer_warnIfReached(); // no-warning
+}
+
+// Variadic template function specialization.
+template <>
+void Variadic_Suppressed(int, long) {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void instantiate_variadic() {
+  Variadic_Suppressed();
+  Variadic_Suppressed(0);
+  Variadic_Suppressed(0, 0L);
+}
+
+// 3 levels of nesting:
+// The suppression mechanism walks the member-template-instantiation chain.
+// Verify it reaches the primary template definition at depth 3.
+// Similar to gh_182659.
+
+template <typename A>
+struct [[clang::suppress]] ThreeLevels_AttrOnOuter {
+  template <typename B>
+  struct Mid {
+    template <typename C>
+    struct Inner {
+      void inline_defined() {
+        clang_analyzer_warnIfReached(); // no-warning
+      }
+      void outline_defined();
+    };
+  };
+};
+
+template <typename A>
+template <typename B>
+template <typename C>
+void ThreeLevels_AttrOnOuter<A>::Mid<B>::Inner<C>::outline_defined() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename A>
+struct ThreeLevels_AttrOnInner {
+  template <typename B>
+  struct Mid {
+    template <typename C>
+    struct [[clang::suppress]] Inner {
+      void inline_defined() {
+        clang_analyzer_warnIfReached(); // no-warning
+      }
+      void outline_defined();
+    };
+  };
+};
+
+template <typename A>
+template <typename B>
+template <typename C>
+void ThreeLevels_AttrOnInner<A>::Mid<B>::Inner<C>::outline_defined() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename A>
+struct ThreeLevels_NoAttr {
+  template <typename B>
+  struct Mid {
+    template <typename C>
+    struct Inner {
+      void inline_defined() {
+        clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+      }
+      void outline_defined();
+    };
+  };
+};
+
+template <typename A>
+template <typename B>
+template <typename C>
+void ThreeLevels_NoAttr<A>::Mid<B>::Inner<C>::outline_defined() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void instantiate_three_levels() {
+  ThreeLevels_AttrOnOuter<int>::Mid<long>::Inner<short>().inline_defined();
+  ThreeLevels_AttrOnOuter<int>::Mid<long>::Inner<short>().outline_defined();
+
+  ThreeLevels_AttrOnInner<int>::Mid<long>::Inner<short>().inline_defined();
+  ThreeLevels_AttrOnInner<int>::Mid<long>::Inner<short>().outline_defined();
+
+  ThreeLevels_NoAttr<int>::Mid<long>::Inner<short>().inline_defined();
+  ThreeLevels_NoAttr<int>::Mid<long>::Inner<short>().outline_defined();
+}
+
+template <typename T>
+class [[clang::suppress]] ClassTemplateStaticMethod {
+public:
+  static void static_method_inline() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+  static void static_method_outline();
+};
+
+template <typename T>
+void ClassTemplateStaticMethod<T>::static_method_outline() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename T>
+struct ClassTemplateStaticMethod_NoAttr {
+  static void static_method_inline() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+  static void static_method_outline();
+};
+
+template <typename T>
+void ClassTemplateStaticMethod_NoAttr<T>::static_method_outline() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+void instantiate_static_methods() {
+  ClassTemplateStaticMethod<int>::static_method_inline();
+  ClassTemplateStaticMethod<int>::static_method_outline();
+  ClassTemplateStaticMethod_NoAttr<int>::static_method_inline();
+  ClassTemplateStaticMethod_NoAttr<int>::static_method_outline();
+}
+
+// Forward declarations for the friend declarations so that they can be called 
without ADL.
+extern void friend_inline_in_suppressed_class();
+extern void friend_ool_in_suppressed_class();
+struct [[clang::suppress]] Friend_SuppressedClass {
+  friend void friend_inline_in_suppressed_class() {
+    clang_analyzer_warnIfReached(); // no-warning
+  }
+  friend void friend_ool_in_suppressed_class();
+};
+
+void friend_ool_in_suppressed_class() {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+extern void friend_inline_in_unsuppressed_class();
+struct Friend_UnsuppressedClass {
+  friend void friend_inline_in_unsuppressed_class() {
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+};
+
+// Out-of-line definition with the attribute placed on the definition itself.
+extern void friend_attr_on_ool_def();
+struct Friend_AttrOnDef {
+  friend void friend_attr_on_ool_def();
+};
+[[clang::suppress]]
+void friend_attr_on_ool_def() {
+  clang_analyzer_warnIfReached(); // no-warning
+}
+
+// Friend function template defined inline in a suppressed class.
+template <typename T>
+extern void friend_template_in_suppressed_class(T);
+template <typename T>
+extern void friend_template_ool_in_suppressed_class(T);
+struct [[clang::suppress]] Friend_SuppressedClassWithTemplate {
+  template <typename T>
+  friend void friend_template_in_suppressed_class(T) {
+    // FIXME: This should be suppressed.
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+
+  template <typename T>
+  friend void friend_template_ool_in_suppressed_class(T);
+};
+
+template <typename T>
+extern void friend_template_ool_in_suppressed_class(T) {
+  clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+}
+
+template <typename T>
+extern void friend_template_in_unsuppressed_class(T);
+template <typename T>
+extern void friend_template_ool_in_unsuppressed_class(T);
+struct Friend_UnsuppressedClassWithTemplate {
+  template <typename T>
+  friend void friend_template_in_unsuppressed_class(T) {
+    clang_analyzer_warnIfReached(); // expected-warning{...
[truncated]

``````````

</details>


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

Reply via email to