Author: Aleksandr Nogikh Date: 2025-11-19T12:03:22-05:00 New Revision: eb65517c76c131de7a3f772beea02347279ab6a3
URL: https://github.com/llvm/llvm-project/commit/eb65517c76c131de7a3f772beea02347279ab6a3 DIFF: https://github.com/llvm/llvm-project/commit/eb65517c76c131de7a3f772beea02347279ab6a3.diff LOG: [Clang] Introduce malloc_span attribute (#167010) The "malloc" attribute restricts the possible function signatures to the ones returning a pointer, which is not the case for some non-standard allocation function variants. For example, P0901R11 proposed ::operator new overloads that return a return_size_t result - a struct that contains a pointer to the allocated memory as well as the actual size of the allocated memory. Another example is __size_returning_new. Introduce a new "malloc_span" attribute that exhibits similar semantics, but applies to functions returning records where one member is a pointer (assumed to point to the allocated memory) and another is an integer (assumed to be the size of the allocated memory). This is the case for return_size_t as well as std::span, should it be returned from such an annotated function. An alternative approach would be to relax the restrictions of the existing "malloc" attribute to be applied to both functions returning pointers and functions returning span-like structs. However, it would complicate the user-space code by requiring specific Clang version checks. In contrast, the presence of a new attribute can be straightforwardly verified via the __has_attribute macro. Introducing a new attribute also avoids concerns about the potential incompatibility with GCC's "malloc" semantics. In future commits, codegen can be improved to recognize the noalias-ness of the pointer returned inside a span-like struct. This change helps unlock the alloc token instrumentation for such non-standard allocation functions: https://clang.llvm.org/docs/AllocToken.html#instrumenting-non-standard-allocation-functions Added: clang/test/Sema/attr-malloc_span.c clang/test/SemaCXX/attr-malloc_span.cpp Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Basic/Attr.td clang/include/clang/Basic/AttrDocs.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Sema.h clang/lib/Sema/SemaDeclAttr.cpp clang/lib/Sema/SemaTemplateInstantiateDecl.cpp clang/test/Misc/pragma-attribute-supported-attributes-list.test Removed: ################################################################################ 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<TargetMicrosoftRecordLayout> { 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<IgnoredAttributes>; +def warn_attribute_return_span_only + : Warning<"%0 attribute only applies to functions that return span-like " + "structures">, + InGroup<IgnoredAttributes>; +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<IgnoredAttributes>; 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<CXXRecordDecl>(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<BuiltinType>(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<MallocSpanAttr>(TmplAttr)) { + instantiateDependentMallocSpanAttr(*this, A, New); + continue; + } + if (auto *A = dyn_cast<CleanupAttr>(TmplAttr)) { if (!New->hasAttr<CleanupAttr>()) { 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<typename FirstType, typename SecondType> +struct Pair { + FirstType first; + SecondType second; +}; + +Pair<int*, int> returns_templated_span1(void) __attribute((malloc_span)) { // no-warning + return Pair<int*, int>{}; +} + +Pair<int*, int*> returns_templated_span2(void) __attribute((malloc_span)) { // no-warning + return Pair<int*, int*>{}; +} + +// 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<int, int> returns_templated_span3(void) __attribute((malloc_span)) { + return Pair<int, int>{}; +} + +// Verify that semantic checks are done on dependent types. + +struct GoodSpan { + void *ptr; + int n; +}; + +struct BadSpan { + int n; +}; + +template <typename T> +// 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<GoodSpan>(); // no-warnings + // expected-note@+1 {{in instantiation of function template specialization 'produce_span<BadSpan>' requested here}} + produce_span<BadSpan>(); +} + +// Ensure that trailing return types are also supported. +__attribute__((malloc_span)) auto trailing_return_type(int size) -> GoodSpan { // no-warning + return GoodSpan{}; +} + +template<typename T> +// 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<GoodSpan>(); // no-warnings + // expected-note@+1 {{in instantiation of function template specialization 'templated_trailing_return_type<BadSpan>' requested here}} + templated_trailing_return_type<BadSpan>(); +} + +__attribute((malloc_span)) auto trailing_return_temmplate_good(void) -> Pair<int*, int> { // no-warning + return Pair<int*, int>{}; +} + +// 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<int, int> { + return Pair<int, int>{}; +} + +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); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
