llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang 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
