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 11 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 @@ -425,6 +425,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
20 changes: 16 additions & 4 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 Expand Up @@ -4637,7 +4649,7 @@ def HLSLResourceBinding: InheritableAttr {
let AdditionalMembers = [{
public:
enum class RegisterType : unsigned { SRV, UAV, CBuffer, Sampler, C, I };

private:
RegisterType RegType;
unsigned SlotNumber;
Expand Down Expand Up @@ -4707,7 +4719,7 @@ def HLSLResource : InheritableAttr {
let Spellings = [];
let Subjects = SubjectList<[Struct]>;
let LangOpts = [HLSL];
let Args = [
let Args = [
EnumArgument<
"ResourceKind", "llvm::hlsl::ResourceKind",
/*is_string=*/0,
Expand All @@ -4732,7 +4744,7 @@ def HLSLResource : InheritableAttr {

def HLSLROV : TypeAttr {
let Spellings = [CXX11<"hlsl", "is_rov">];
let LangOpts = [HLSL];
let LangOpts = [HLSL];
let Documentation = [InternalOnly];
}

Expand All @@ -4757,7 +4769,7 @@ def HLSLContainedType : TypeAttr {

def HLSLRawBuffer : TypeAttr {
let Spellings = [CXX11<"hlsl", "raw_buffer">];
let LangOpts = [HLSL];
let LangOpts = [HLSL];
let Documentation = [InternalOnly];
}

Expand Down
27 changes: 18 additions & 9 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 Expand Up @@ -7133,8 +7142,8 @@ the field it is attached to, and it may also lead to emission of automatic fix-i
hints which would help the user replace the use of unsafe functions(/fields) with safe
alternatives, though the attribute can be used even when the fix can't be automated.

* Attribute attached to functions: The attribute does not suppress
``-Wunsafe-buffer-usage`` inside the function to which it is attached.
* Attribute attached to functions: The attribute does not suppress
``-Wunsafe-buffer-usage`` inside the function to which it is attached.
These warnings still need to be addressed.

The attribute is warranted even if the only way a function can overflow
Expand Down Expand Up @@ -7197,10 +7206,10 @@ alternatives, though the attribute can be used even when the fix can't be automa
and then use the attribute on the original ``baz()`` to help the users
update their code to use the new function.

* Attribute attached to fields: The attribute should only be attached to
struct fields, if the fields can not be updated to a safe type with bounds
check, such as std::span. In other words, the buffers prone to unsafe accesses
should always be updated to use safe containers/views and attaching the attribute
* Attribute attached to fields: The attribute should only be attached to
struct fields, if the fields can not be updated to a safe type with bounds
check, such as std::span. In other words, the buffers prone to unsafe accesses
should always be updated to use safe containers/views and attaching the attribute
must be last resort when such an update is infeasible.

The attribute can be placed on individual fields or a set of them as shown below.
Expand All @@ -7218,7 +7227,7 @@ alternatives, though the attribute can be used even when the fix can't be automa
size_t sz;
};

Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning
Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning
that the field has been explcitly marked as unsafe due to unsafe-buffer operations.

}];
Expand Down Expand Up @@ -7773,10 +7782,10 @@ def HLSLLoopHintDocs : Documentation {
let Content = [{
The ``[loop]`` directive allows loop optimization hints to be
specified for the subsequent loop. The directive allows unrolling to
be disabled and is not compatible with [unroll(x)].
be disabled and is not compatible with [unroll(x)].

Specifying the parameter, ``[loop]``, directs the
unroller to not unroll the loop.
unroller to not unroll the loop.

.. code-block:: hlsl

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ def ExpansionToDefined : DiagGroup<"expansion-to-defined">;
def FlagEnum : DiagGroup<"flag-enum">;
def IncrementBool : DiagGroup<"increment-bool", [DeprecatedIncrementBool]>;
def InfiniteRecursion : DiagGroup<"infinite-recursion">;
def InvalidSpecialization : DiagGroup<"invalid-specialization">;
def PureVirtualCallFromCtorDtor: DiagGroup<"call-to-pure-virtual-from-ctor-dtor">;
def GNUImaginaryConstant : DiagGroup<"gnu-imaginary-constant">;
def IgnoredGCH : DiagGroup<"ignored-gch">;
Expand Down
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<InvalidSpecialization>;
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}}