diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index f8a8fc3c0f450..7e9119b5f2b46 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -353,6 +353,10 @@ Attribute Changes in Clang - New format attributes ``gnu_printf``, ``gnu_scanf``, ``gnu_strftime`` and ``gnu_strfmon`` are added as aliases for ``printf``, ``scanf``, ``strftime`` and ``strfmon``. (#GH16219) +- New function attribute `malloc_span` is added. It has semantics similar to that of the `malloc` + attribute, but `malloc_span` applies not to functions returning pointers, but to functions returning + span-like structures (i.e. those that contain a pointer field and a size integer field or two pointers). + Improvements to Clang's diagnostics ----------------------------------- - Diagnostics messages now refer to ``structured binding`` instead of ``decomposition``, diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 0097476bc0d8d..8e5f7ef0bb82d 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2072,6 +2072,12 @@ def Restrict : InheritableAttr { let Documentation = [RestrictDocs]; } +def MallocSpan : InheritableAttr { + let Spellings = [Clang<"malloc_span">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [MallocSpanDocs]; +} + def LayoutVersion : InheritableAttr, TargetSpecificAttr { let Spellings = [Declspec<"layout_version">]; let Args = [UnsignedArgument<"Version">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 4813191d2d602..c1b1510f363d4 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5247,6 +5247,23 @@ yet implemented in clang. }]; } +def MallocSpanDocs : Documentation { + let Category = DocCatFunction; + let Heading = "malloc_span"; + let Content = [{ +The ``malloc_span`` attribute can be used to mark that a function which acts +like a system memory allocation function and returns a span-like structure, +where the returned memory range does not alias storage from any other object +accessible to the caller. + +In this context, a span-like structure is assumed to have two non-static data +members, one of which is a pointer to the start of the allocated memory and +the other one is either an integer type containing the size of the actually +allocated memory or a pointer to the end of the allocated region. Note, static +data members do not impact whether a type is span-like or not. + }]; +} + def ReturnsNonNullDocs : Documentation { let Category = NullabilityDocs; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index c535fe2b9f241..533bebca2fe17 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3449,6 +3449,24 @@ def err_attribute_integers_only : Error< def warn_attribute_return_pointers_only : Warning< "%0 attribute only applies to return values that are pointers">, InGroup; +def warn_attribute_return_span_only + : Warning<"%0 attribute only applies to functions that return span-like " + "structures">, + InGroup; +def note_returned_not_struct : Note<"returned type is not a struct type">; +def note_returned_incomplete_type : Note<"returned type is incomplete">; +def note_returned_not_two_field_struct + : Note<"returned struct has %0 fields, expected 2">; +def note_returned_not_span_struct + : Note<"returned struct fields are not a supported combination for a " + "span-like type (expected pointer/integer or pointer/pointer)">; +def note_returned_not_integer_field + : Note<"%ordinal0 field is expected to be an integer">; +def note_returned_not_wide_enough_field + : Note<"%ordinal0 field of span-like type is not a wide enough integer " + "(minimum width: %1)">; +def note_type_inherits_from_base + : Note<"returned type inherits from a base class">; def warn_attribute_return_pointers_refs_only : Warning< "%0 attribute only applies to return values that are pointers or references">, InGroup; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index fd2a2469142e4..cbfcc9bc0ea99 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5111,6 +5111,11 @@ class Sema final : public SemaBase { /// Essentially, this just moves them to the current pool. void redelayDiagnostics(sema::DelayedDiagnosticPool &pool); + /// Check that the type is a plain record with one field being a pointer + /// type and the other field being an integer. This matches the common + /// implementation of std::span or sized_allocation_t in P0901R11. + bool CheckSpanLikeType(const AttributeCommonInfo &CI, const QualType &Ty); + /// Check if IdxExpr is a valid parameter index for a function or /// instance method D. May output an error. /// diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index c093052cf4035..e3af5023c74d0 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1839,6 +1839,70 @@ static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) { RestrictAttr(S.Context, AL, DeallocE, DeallocPtrIdx)); } +bool Sema::CheckSpanLikeType(const AttributeCommonInfo &CI, + const QualType &Ty) { + // Note that there may also be numerous cases of pointer + integer / + // pointer + pointer / integer + pointer structures not actually exhibiting + // a span-like semantics, so sometimes these heuristics expectedly + // lead to false positive results. + auto emitWarning = [this, &CI](unsigned NoteDiagID) { + Diag(CI.getLoc(), diag::warn_attribute_return_span_only) << CI; + return Diag(CI.getLoc(), NoteDiagID); + }; + if (Ty->isDependentType()) + return false; + // isCompleteType is used to force template class instantiation. + if (!isCompleteType(CI.getLoc(), Ty)) + return emitWarning(diag::note_returned_incomplete_type); + const RecordDecl *RD = Ty->getAsRecordDecl(); + if (!RD || RD->isUnion()) + return emitWarning(diag::note_returned_not_struct); + if (const auto *CXXRD = dyn_cast(RD)) { + if (CXXRD->getNumBases() > 0) { + return emitWarning(diag::note_type_inherits_from_base); + } + } + auto FieldsBegin = RD->field_begin(); + auto FieldsCount = std::distance(FieldsBegin, RD->field_end()); + if (FieldsCount != 2) + return emitWarning(diag::note_returned_not_two_field_struct) << FieldsCount; + QualType FirstFieldType = FieldsBegin->getType(); + QualType SecondFieldType = std::next(FieldsBegin)->getType(); + auto validatePointerType = [](const QualType &T) { + // It must not point to functions. + return T->isPointerType() && !T->isFunctionPointerType(); + }; + auto checkIntegerType = [this, emitWarning](const QualType &T, + const int FieldNo) -> bool { + const auto *BT = dyn_cast(T.getCanonicalType()); + if (!BT || !BT->isInteger()) + return emitWarning(diag::note_returned_not_integer_field) << FieldNo; + auto IntSize = Context.getTypeSize(Context.IntTy); + if (Context.getTypeSize(BT) < IntSize) + return emitWarning(diag::note_returned_not_wide_enough_field) + << FieldNo << IntSize; + return false; + }; + if (validatePointerType(FirstFieldType) && + validatePointerType(SecondFieldType)) { + // Pointer + pointer. + return false; + } else if (validatePointerType(FirstFieldType)) { + // Pointer + integer? + return checkIntegerType(SecondFieldType, 2); + } else if (validatePointerType(SecondFieldType)) { + // Integer + pointer? + return checkIntegerType(FirstFieldType, 1); + } + return emitWarning(diag::note_returned_not_span_struct); +} + +static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + QualType ResultType = getFunctionOrMethodResultType(D); + if (!S.CheckSpanLikeType(AL, ResultType)) + D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL)); +} + static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) { // Ensure we don't combine these with themselves, since that causes some // confusing behavior. @@ -7276,6 +7340,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_Restrict: handleRestrictAttr(S, D, AL); break; + case ParsedAttr::AT_MallocSpan: + handleMallocSpanAttr(S, D, AL); + break; case ParsedAttr::AT_Mode: handleModeAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 3a4b2ccc74350..26693514bb278 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -796,6 +796,14 @@ static void instantiateDependentHLSLParamModifierAttr( "out or inout parameter type must be a reference and restrict qualified"); } +static void instantiateDependentMallocSpanAttr(Sema &S, + const MallocSpanAttr *Attr, + Decl *New) { + QualType RT = getFunctionOrMethodResultType(New); + if (!S.CheckSpanLikeType(*Attr, RT)) + New->addAttr(Attr->clone(S.getASTContext())); +} + void Sema::InstantiateAttrsForDecl( const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl, Decl *New, LateInstantiatedAttrVec *LateAttrs, @@ -1007,6 +1015,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, continue; } + if (auto *A = dyn_cast(TmplAttr)) { + instantiateDependentMallocSpanAttr(*this, A, New); + continue; + } + if (auto *A = dyn_cast(TmplAttr)) { if (!New->hasAttr()) { auto *NewAttr = A->clone(Context); diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index ab4153a64f028..747eb17446c87 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -102,6 +102,7 @@ // CHECK-NEXT: MIGServerRoutine (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_block) // CHECK-NEXT: MSConstexpr (SubjectMatchRule_function) // CHECK-NEXT: MSStruct (SubjectMatchRule_record) +// CHECK-NEXT: MallocSpan (SubjectMatchRule_function) // CHECK-NEXT: MaybeUndef (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: MicroMips (SubjectMatchRule_function) // CHECK-NEXT: MinSize (SubjectMatchRule_function, SubjectMatchRule_objc_method) diff --git a/clang/test/Sema/attr-malloc_span.c b/clang/test/Sema/attr-malloc_span.c new file mode 100644 index 0000000000000..9238b601c5f0d --- /dev/null +++ b/clang/test/Sema/attr-malloc_span.c @@ -0,0 +1,82 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %s + +typedef __SIZE_TYPE__ size_t; + +typedef struct { + void *ptr; + size_t n; +} span; +span returns_span (void) __attribute((malloc_span)); // no-warning + +typedef struct { + size_t n; + void *ptr; +} span2; +span2 returns_span2 (void) __attribute((malloc_span)); // no-warning + +typedef struct { + void *ptr; + void *ptr2; +} span3; +span3 returns_span3 (void) __attribute((malloc_span)); // no-warning + +typedef struct { + void *ptr; + int n; +} span4; +span4 returns_span4 (void) __attribute((malloc_span)); // no-warning + +typedef struct incomplete_span incomplete_span; +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned type is incomplete}} +incomplete_span returns_incomplete_span (void) __attribute((malloc_span)); + +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned type is not a struct type}} +int *returns_int_ptr (void) __attribute((malloc_span)); + +typedef struct { + void *ptr; + size_t n; + size_t n2; +} too_long_span; +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct has 3 fields, expected 2}} +too_long_span returns_too_long_span (void) __attribute((malloc_span)); + +// Function pointers are not allowed. +typedef struct { + int (*func_ptr)(void); + size_t n; +} func_span; +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct fields are not a supported combination}} +func_span returns_func_span (void) __attribute((malloc_span)); + +// Integer should not be an enum. +enum some_enum { some_value, other_value }; +typedef struct { + void *ptr; + enum some_enum field; +} enum_span; +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{2nd field is expected to be an integer}} +enum_span returns_enum_span (void) __attribute((malloc_span)); + +// Bit integers are also not supported. +typedef struct { + void *ptr; + _BitInt(16) n; +} bit_span; +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{2nd field is expected to be an integer}} +bit_span returns_bit_span (void) __attribute((malloc_span)); + +// Integer must be at least as big as int. +typedef struct { + void *ptr; + short n; +} short_span; +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{2nd field of span-like type is not a wide enough integer (minimum width: 32)}} +short_span returns_short_span (void) __attribute((malloc_span)); diff --git a/clang/test/SemaCXX/attr-malloc_span.cpp b/clang/test/SemaCXX/attr-malloc_span.cpp new file mode 100644 index 0000000000000..86622f6c154ea --- /dev/null +++ b/clang/test/SemaCXX/attr-malloc_span.cpp @@ -0,0 +1,132 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s + +struct span_with_static { + void *ptr; + int n; + static int static_field; +}; + +span_with_static returns_span_with_static (void) __attribute((malloc_span)); // no-warning + +class SomeClass { +public: + int Data; +}; + +// Returning pointers to data members is not allowed. +struct DataMemberSpan { + int SomeClass::* member_ptr; + int n; +}; + +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct fields are not a supported combination}} +DataMemberSpan returns_data_member_span(void) __attribute((malloc_span)) { + return DataMemberSpan{}; +} + +// Returning pointers to member functions is not allowed. +struct MemberFuncSpan { + void (SomeClass::*member_func_ptr)(); + int n; +}; + +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct fields are not a supported combination}} +MemberFuncSpan returns_member_func_span(void) __attribute((malloc_span)) { + return MemberFuncSpan{}; +} + +template +struct Pair { + FirstType first; + SecondType second; +}; + +Pair returns_templated_span1(void) __attribute((malloc_span)) { // no-warning + return Pair{}; +} + +Pair returns_templated_span2(void) __attribute((malloc_span)) { // no-warning + return Pair{}; +} + +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct fields are not a supported combination for a span-like type}} +Pair returns_templated_span3(void) __attribute((malloc_span)) { + return Pair{}; +} + +// Verify that semantic checks are done on dependent types. + +struct GoodSpan { + void *ptr; + int n; +}; + +struct BadSpan { + int n; +}; + +template +// expected-warning@+2 {{'malloc_span' attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct has 1 fields, expected 2}} +T produce_span() __attribute((malloc_span)) { + return T{}; +} + +void TestGoodBadSpan() { + produce_span(); // no-warnings + // expected-note@+1 {{in instantiation of function template specialization 'produce_span' requested here}} + produce_span(); +} + +// Ensure that trailing return types are also supported. +__attribute__((malloc_span)) auto trailing_return_type(int size) -> GoodSpan { // no-warning + return GoodSpan{}; +} + +template +// expected-warning@+2 {{'malloc_span' attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct has 1 fields, expected 2}} +__attribute__((malloc_span)) auto templated_trailing_return_type() -> T { + return T{}; +} + +void TestGoodBadTrailingReturnType() { + templated_trailing_return_type(); // no-warnings + // expected-note@+1 {{in instantiation of function template specialization 'templated_trailing_return_type' requested here}} + templated_trailing_return_type(); +} + +__attribute((malloc_span)) auto trailing_return_temmplate_good(void) -> Pair { // no-warning + return Pair{}; +} + +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned struct fields are not a supported combination for a span-like type}} +__attribute((malloc_span)) auto trailing_return_temmplate_bad(void) -> Pair { + return Pair{}; +} + +struct Base { + void *other_p; +}; + +struct ChildSpan : Base { + void *p; + int n; +}; + +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned type inherits from a base class}} +__attribute((malloc_span)) ChildSpan return_child_span(void); + +class VirtualBaseSpan : public virtual Base { + void *p; + int n; +}; + +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{returned type inherits from a base class}} +__attribute((malloc_span)) VirtualBaseSpan return_virtual_base_span(void);