Skip to content

[Clang] Add [[clang::no_specializations]] #101469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,11 @@ Attribute Changes in Clang
- The ``hybrid_patchable`` attribute is now supported on ARM64EC targets. It can be used to specify
that a function requires an additional x86-64 thunk, which may be patched at runtime.

- The attribute ``[[clang::no_specializations]]`` has been added to warn
users that a specific template shouldn't be specialized. This is useful for
e.g. standard library type traits, where adding a specialization results in
undefined behaviour.

- ``[[clang::lifetimebound]]`` is now explicitly disallowed on explicit object member functions
where they were previously silently ignored.

Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ def NonParmVar : SubsetSubject<Var,
def NonLocalVar : SubsetSubject<Var,
[{!S->hasLocalStorage()}],
"variables with non-local storage">;
def VarTmpl : SubsetSubject<Var, [{S->getDescribedVarTemplate()}],
"variable templates">;

def NonBitField : SubsetSubject<Field,
[{!S->isBitField()}],
"non-bit-field non-static data members">;
Expand Down Expand Up @@ -3428,6 +3431,15 @@ def DiagnoseIf : InheritableAttr {
let Documentation = [DiagnoseIfDocs];
}

def NoSpecializations : InheritableAttr {
let Spellings = [Clang<"no_specializations", /*AllowInC*/0>];
let Args = [StringArgument<"Message", 1>];
let Subjects = SubjectList<[ClassTmpl, FunctionTmpl, VarTmpl]>;
let Documentation = [NoSpecializationsDocs];
let MeaningfulToClassTemplateDefinition = 1;
let TemplateDependent = 1;
}

def ArcWeakrefUnavailable : InheritableAttr {
let Spellings = [Clang<"objc_arc_weak_reference_unavailable">];
let Subjects = SubjectList<[ObjCInterface], ErrorDiag>;
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,15 @@ Query for this feature with ``__has_attribute(diagnose_if)``.
}];
}

def NoSpecializationsDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
``[[clang::no_specializations]]`` can be applied to function, class, or variable
templates which should not be explicitly specialized by users. This is primarily
used to diagnose user specializations of standard library type traits.
}];
}

def PassObjectSizeDocs : Documentation {
let Category = DocCatVariable; // Technically it's a parameter doc, but eh.
let Heading = "pass_object_size, pass_dynamic_object_size";
Expand Down
1 change: 0 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -1589,4 +1589,3 @@ def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-stor

// A warning for options that enable a feature that is not yet complete
def ExperimentalOption : DiagGroup<"experimental-option">;

4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -5445,6 +5445,10 @@ def note_dependent_function_template_spec_discard_reason : Note<
"candidate ignored: %select{not a function template|"
"not a member of the enclosing %select{class template|"
"namespace; did you mean to explicitly qualify the specialization?}1}0">;
def warn_invalid_specialization : Warning<
"%0 cannot be specialized%select{|: %2}1">,
DefaultError, InGroup<DiagGroup<"invalid-specialization">>;
def note_marked_here : Note<"marked %0 here">;

// C++ class template specializations and out-of-line definitions
def err_template_spec_needs_header : Error<
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,14 @@ static void handlePreferredName(Sema &S, Decl *D, const ParsedAttr &AL) {
<< TT->getDecl();
}

static void handleNoSpecializations(Sema &S, Decl *D, const ParsedAttr &AL) {
StringRef Message;
if (AL.getNumArgs() != 0)
S.checkStringLiteralArgumentAttr(AL, 0, Message);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static analysis flags this b/c you are not checking the return value of checkStringLiteralArgumentAttr and AFAICT every other use is checking the value.

Please confirm the intent and please fix to check the return value or please modify to purposely ignore it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure what the correct thing to do is. I don't think there's harm in adding the attribute without a string if the arguments are incorrect, since they don't affect what the attribute does in a significant way. OTOH there isn't that much benefit in adding it, since the program is ill-formed anyways. @AaronBallman any thoughts on this?

P.S. why isn't checkStringLiteralArgumentAttr marked [[nodiscard]]?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. why isn't checkStringLiteralArgumentAttr marked [[nodiscard]]?

Pretty sure this function, or its origin predate [[nodiscard]] by a decade or so :) Also, the use you have is completely reasonable here. The Message argument is left zero-initalized, which is the behavior you want.

I don't see any good reason to change the code here, I think the SA tool isn't really adding value here, and I don't see anything I'd change.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It stands out as completely inconsistent with the rest of the file, literally everywhere else it is checked and they return early. I am guessing if we return early here we should just get the proper diagnostic, right? Is this case somehow different from all the two dozen or so other uses?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is inconsistent, but not really an issue. About 1/2 the uses have some additional processing that needs to happen, so the early return is sensible, though perhaps they could better reflect the code in that case.

But the other 1/2 could very well have their return removed with no negative effects (and likely, an improvement, as the error-AST would better reflect the syntax).

The only reason NOT to do a mass-changeover is that we're not sure which were written in a way to not properly expect an empty string here in codegen/etc.

That said, a bug report marked 'good-first-issue' for someone to go through the uses and ensure we are tolerant of them would be a good idea. @shafik: mind filing one?

D->getDescribedTemplate()->addAttr(
NoSpecializationsAttr::Create(S.Context, Message, AL));
}

bool Sema::isValidPointerAttrType(QualType T, bool RefOkay) {
if (T->isDependentType())
return true;
Expand Down Expand Up @@ -6913,6 +6921,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_PreferredName:
handlePreferredName(S, D, AL);
break;
case ParsedAttr::AT_NoSpecializations:
handleNoSpecializations(S, D, AL);
break;
case ParsedAttr::AT_Section:
handleSectionAttr(S, D, AL);
break;
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4157,6 +4157,13 @@ DeclResult Sema::ActOnVarTemplateSpecialization(
<< IsPartialSpecialization;
}

if (const auto *DSA = VarTemplate->getAttr<NoSpecializationsAttr>()) {
auto Message = DSA->getMessage();
Diag(TemplateNameLoc, diag::warn_invalid_specialization)
<< VarTemplate << !Message.empty() << Message;
Diag(DSA->getLoc(), diag::note_marked_here) << DSA;
}

// Check for unexpanded parameter packs in any of the template arguments.
for (unsigned I = 0, N = TemplateArgs.size(); I != N; ++I)
if (DiagnoseUnexpandedParameterPack(TemplateArgs[I],
Expand Down Expand Up @@ -8291,6 +8298,13 @@ DeclResult Sema::ActOnClassTemplateSpecialization(
return true;
}

if (const auto *DSA = ClassTemplate->getAttr<NoSpecializationsAttr>()) {
auto Message = DSA->getMessage();
Diag(TemplateNameLoc, diag::warn_invalid_specialization)
<< ClassTemplate << !Message.empty() << Message;
Diag(DSA->getLoc(), diag::note_marked_here) << DSA;
}

if (S->isTemplateParamScope())
EnterTemplatedContext(S, ClassTemplate->getTemplatedDecl());

Expand Down Expand Up @@ -9175,6 +9189,14 @@ bool Sema::CheckFunctionTemplateSpecialization(
// Ignore access information; it doesn't figure into redeclaration checking.
FunctionDecl *Specialization = cast<FunctionDecl>(*Result);

if (const auto *PT = Specialization->getPrimaryTemplate();
const auto *DSA = PT->getAttr<NoSpecializationsAttr>()) {
auto Message = DSA->getMessage();
Diag(FD->getLocation(), diag::warn_invalid_specialization)
<< PT << !Message.empty() << Message;
Diag(DSA->getLoc(), diag::note_marked_here) << DSA;
}

// C++23 [except.spec]p13:
// An exception specification is considered to be needed when:
// - [...]
Expand Down
62 changes: 62 additions & 0 deletions clang/test/SemaCXX/attr-no-specializations.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// RUN: %clang_cc1 %s -verify

#if !__has_cpp_attribute(clang::no_specializations)
# error
#endif

struct [[clang::no_specializations]] S {}; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}}

template <class T, class U>
struct [[clang::no_specializations]] is_same { // expected-note 2 {{marked 'no_specializations' here}}
static constexpr bool value = __is_same(T, U);
};

template <class T>
using alias [[clang::no_specializations]] = T; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}}

template <>
struct is_same<int, char> {}; // expected-error {{'is_same' cannot be specialized}}

template <class>
struct Template {};

template <class T>
struct is_same<Template<T>, Template <T>> {}; // expected-error {{'is_same' cannot be specialized}}

bool test_instantiation1 = is_same<int, int>::value;

template <class T, class U>
[[clang::no_specializations]] inline constexpr bool is_same_v = __is_same(T, U); // expected-note 2 {{marked 'no_specializations' here}}

template <>
inline constexpr bool is_same_v<int, char> = false; // expected-error {{'is_same_v' cannot be specialized}}

template <class T>
inline constexpr bool is_same_v<Template <T>, Template <T>> = true; // expected-error {{'is_same_v' cannot be specialized}}

bool test_instantiation2 = is_same_v<int, int>;

template <class T>
struct [[clang::no_specializations("specializing type traits results in undefined behaviour")]] is_trivial { // expected-note {{marked 'no_specializations' here}}
static constexpr bool value = __is_trivial(T);
};

template <>
struct is_trivial<int> {}; // expected-error {{'is_trivial' cannot be specialized: specializing type traits results in undefined behaviour}}

template <class T>
[[clang::no_specializations("specializing type traits results in undefined behaviour")]] inline constexpr bool is_trivial_v = __is_trivial(T); // expected-note {{marked 'no_specializations' here}}

template <>
inline constexpr bool is_trivial_v<int> = false; // expected-error {{'is_trivial_v' cannot be specialized: specializing type traits results in undefined behaviour}}

template <class T>
struct Partial {};

template <class T>
struct [[clang::no_specializations]] Partial<Template <T>> {}; // expected-warning {{'no_specializations' attribute only applies to class templates, function templates, and variable templates}}

template <class T>
[[clang::no_specializations]] void func(); // expected-note {{marked 'no_specializations' here}}

template <> void func<int>(); // expected-error {{'func' cannot be specialized}}