https://github.com/a-nogikh updated https://github.com/llvm/llvm-project/pull/167010
>From e0fa07d42042a391fc92c71e6e2e378dd58a775b Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Fri, 17 Oct 2025 18:05:36 +0200 Subject: [PATCH 01/13] [Clang] Introduce malloc_span attribute 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 whose first member is a pointer (assumed to point to 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 --- clang/docs/ReleaseNotes.rst | 4 +++ clang/include/clang/Basic/Attr.td | 6 ++++ clang/include/clang/Basic/AttrDocs.td | 15 ++++++++ .../clang/Basic/DiagnosticSemaKinds.td | 4 +++ clang/lib/Sema/SemaDeclAttr.cpp | 35 +++++++++++++++++++ ...a-attribute-supported-attributes-list.test | 1 + clang/test/Sema/attr-malloc_span.c | 31 ++++++++++++++++ 7 files changed, 96 insertions(+) create mode 100644 clang/test/Sema/attr-malloc_span.c diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index e8339fa13ffba..ed23af353494a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -327,6 +327,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. Its semantics is 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). + 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 1013bfc575747..27987b4da3cc9 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2068,6 +2068,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 1be9a96aa44de..a3556790ff07b 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5247,6 +5247,21 @@ 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 a pointer to the +allocated memory as its first field and any integer type containing the size +of the actually allocated memory as the second field. + }]; +} + 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 04f2e8d654fd5..7687eb5ca0ec6 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3449,6 +3449,10 @@ 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 return values that are span-like " + "structures">, + InGroup<IgnoredAttributes>; 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/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index a9e7b44ac9d73..4fdfc9c13c05b 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1839,6 +1839,38 @@ static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) { RestrictAttr(S.Context, AL, DeallocE, DeallocPtrIdx)); } +static bool isSpanLikeType(const QualType &Ty) { + // Check that the type is a plain record with the first field being a pointer + // type and the second field being an integer. This matches the common + // implementation of std::span or sized_allocation_t in P0901R11. + // Note that there may also be numerous cases of pointer+integer structures + // not actually exhibiting a span-like semantics, so sometimes + // this heuristic expectedly leads to false positive results. + const RecordDecl *RD = Ty->getAsRecordDecl(); + if (!RD || RD->isUnion()) + return false; + const RecordDecl *Def = RD->getDefinition(); + if (!Def) + return false; // This is an incomplete type. + auto FieldsBegin = Def->field_begin(); + if (std::distance(FieldsBegin, Def->field_end()) != 2) + return false; + const FieldDecl *FirstField = *FieldsBegin; + const FieldDecl *SecondField = *std::next(FieldsBegin); + return FirstField->getType()->isAnyPointerType() && + SecondField->getType()->isIntegerType(); +} + +static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + QualType ResultType = getFunctionOrMethodResultType(D); + if (!isSpanLikeType(ResultType)) { + S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only) + << AL << getFunctionOrMethodResultSourceRange(D); + return; + } + 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. @@ -7278,6 +7310,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/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..05f29ccf6dd83 --- /dev/null +++ b/clang/test/Sema/attr-malloc_span.c @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %s +// RUN: %clang_cc1 -emit-llvm -o %t %s + +#include <stddef.h> + +typedef struct { + void *ptr; + size_t n; +} sized_ptr; +sized_ptr returns_sized_ptr (void) __attribute((malloc_span)); // no-warning + +// The first struct field must be pointer and the second must be an integer. +// Check the possible ways to violate it. +typedef struct { + size_t n; + void *ptr; +} invalid_span1; +invalid_span1 returns_non_std_span1 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} + +typedef struct { + void *ptr; + void *ptr2; +} invalid_span2; +invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} + +typedef struct { + void *ptr; + size_t n; + size_t n2; +} invalid_span3; +invalid_span3 returns_non_std_span3 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} >From 770e017c6cc14c380167c50fbda35508809a6622 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Tue, 11 Nov 2025 08:48:27 +0000 Subject: [PATCH 02/13] fixup: support different span struct field orderings Do not demand that the pointer should be the first field and the integer type the second field. --- clang/include/clang/Basic/AttrDocs.td | 6 +++--- clang/lib/Sema/SemaDeclAttr.cpp | 15 +++++++++------ clang/test/Sema/attr-malloc_span.c | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index a3556790ff07b..778e84321d9fd 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5256,9 +5256,9 @@ 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 a pointer to the -allocated memory as its first field and any integer type containing the size -of the actually allocated memory as the second field. +In this context, a span-like structure is assumed to have two fields, one of +which is a pointer to the allocated memory and another one is an integer type +containing the size of the actually allocated memory. }]; } diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 4fdfc9c13c05b..dfe2ed88e216f 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1840,8 +1840,8 @@ static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) { } static bool isSpanLikeType(const QualType &Ty) { - // Check that the type is a plain record with the first field being a pointer - // type and the second field being an integer. This matches the common + // 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. // Note that there may also be numerous cases of pointer+integer structures // not actually exhibiting a span-like semantics, so sometimes @@ -1855,10 +1855,13 @@ static bool isSpanLikeType(const QualType &Ty) { auto FieldsBegin = Def->field_begin(); if (std::distance(FieldsBegin, Def->field_end()) != 2) return false; - const FieldDecl *FirstField = *FieldsBegin; - const FieldDecl *SecondField = *std::next(FieldsBegin); - return FirstField->getType()->isAnyPointerType() && - SecondField->getType()->isIntegerType(); + const QualType FirstFieldType = FieldsBegin->getType(); + const QualType SecondFieldType = std::next(FieldsBegin)->getType(); + // Verify two possible orderings. + return (FirstFieldType->isAnyPointerType() && + SecondFieldType->isIntegerType()) || + (FirstFieldType->isIntegerType() && + SecondFieldType->isAnyPointerType()); } static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { diff --git a/clang/test/Sema/attr-malloc_span.c b/clang/test/Sema/attr-malloc_span.c index 05f29ccf6dd83..245de9d1af4ad 100644 --- a/clang/test/Sema/attr-malloc_span.c +++ b/clang/test/Sema/attr-malloc_span.c @@ -6,26 +6,26 @@ typedef struct { void *ptr; size_t n; -} sized_ptr; -sized_ptr returns_sized_ptr (void) __attribute((malloc_span)); // no-warning +} span; +span returns_span (void) __attribute((malloc_span)); // no-warning -// The first struct field must be pointer and the second must be an integer. -// Check the possible ways to violate it. +// Try out a different field ordering. typedef struct { size_t n; void *ptr; -} invalid_span1; -invalid_span1 returns_non_std_span1 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} +} span2; +span2 returns_span2 (void) __attribute((malloc_span)); // no-warning +// Ensure that a warning is produced on malloc_span precondition violation. typedef struct { void *ptr; void *ptr2; -} invalid_span2; -invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} +} invalid_span1; +invalid_span1 returns_non_std_span1 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} typedef struct { void *ptr; size_t n; size_t n2; -} invalid_span3; -invalid_span3 returns_non_std_span3 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} +} invalid_span2; +invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} >From b3b230955d22f5dc14ecf720aa90581a0c7001a0 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Thu, 13 Nov 2025 10:50:42 +0000 Subject: [PATCH 03/13] fixup: minor update to release notes --- clang/docs/ReleaseNotes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index ed23af353494a..7cccfe1225416 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -327,7 +327,7 @@ 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. Its semantics is similar to that of the `malloc` +- 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). >From 0a4cffcd4d67295dad52f2b740cf204a1e7c9808 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Thu, 13 Nov 2025 10:51:09 +0000 Subject: [PATCH 04/13] fixup: expand the diagnostic message --- clang/include/clang/Basic/DiagnosticSemaKinds.td | 5 +++-- clang/test/Sema/attr-malloc_span.c | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 7687eb5ca0ec6..df8b7fed9bafa 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3450,8 +3450,9 @@ 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 return values that are span-like " - "structures">, + : Warning<"%0 attribute only applies to functions that return span-like structures: " + "one field is a pointer to the allocated memory and another field is an integer with " + "the size of the allocated memory">, InGroup<IgnoredAttributes>; def warn_attribute_return_pointers_refs_only : Warning< "%0 attribute only applies to return values that are pointers or references">, diff --git a/clang/test/Sema/attr-malloc_span.c b/clang/test/Sema/attr-malloc_span.c index 245de9d1af4ad..65b60bd03e49e 100644 --- a/clang/test/Sema/attr-malloc_span.c +++ b/clang/test/Sema/attr-malloc_span.c @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -verify -fsyntax-only %s // RUN: %clang_cc1 -emit-llvm -o %t %s -#include <stddef.h> +typedef __SIZE_TYPE__ size_t; typedef struct { void *ptr; @@ -21,11 +21,11 @@ typedef struct { void *ptr; void *ptr2; } invalid_span1; -invalid_span1 returns_non_std_span1 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} +invalid_span1 returns_non_std_span1 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to functions that return span-like structures}} typedef struct { void *ptr; size_t n; size_t n2; } invalid_span2; -invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}} +invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to functions that return span-like structures}} >From 79b0bea2412432c7eee781b5ef671786468eacee Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Thu, 13 Nov 2025 10:51:43 +0000 Subject: [PATCH 05/13] fixup: isSpanLikeType -> checkSpanLikeType --- clang/lib/Sema/SemaDeclAttr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index dfe2ed88e216f..ae54f283a1eb3 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1839,7 +1839,7 @@ static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) { RestrictAttr(S.Context, AL, DeallocE, DeallocPtrIdx)); } -static bool isSpanLikeType(const QualType &Ty) { +static bool checkSpanLikeType(const QualType &Ty) { // 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. @@ -1866,7 +1866,7 @@ static bool isSpanLikeType(const QualType &Ty) { static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { QualType ResultType = getFunctionOrMethodResultType(D); - if (!isSpanLikeType(ResultType)) { + if (!checkSpanLikeType(ResultType)) { S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only) << AL << getFunctionOrMethodResultSourceRange(D); return; >From 749f34b89b2f0975553d1ecfecc8161de0d73cef Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Thu, 13 Nov 2025 14:14:25 +0000 Subject: [PATCH 06/13] fixup: more restrictions on the pointer type Disallow function pointers. Add tests to verify that member/data member pointers also fail. --- clang/lib/Sema/SemaDeclAttr.cpp | 8 ++++++-- clang/test/Sema/attr-malloc_span.c | 7 +++++++ clang/test/SemaCXX/attr-malloc_span.cpp | 27 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 clang/test/SemaCXX/attr-malloc_span.cpp diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index ae54f283a1eb3..44b10e360fe10 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1857,11 +1857,15 @@ static bool checkSpanLikeType(const QualType &Ty) { return false; const QualType FirstFieldType = FieldsBegin->getType(); const QualType SecondFieldType = std::next(FieldsBegin)->getType(); + auto validatePointerType = [](const QualType &T) { + // It must not point to functions. + return T->isPointerType() && !T->isFunctionPointerType(); + }; // Verify two possible orderings. - return (FirstFieldType->isAnyPointerType() && + return (validatePointerType(FirstFieldType) && SecondFieldType->isIntegerType()) || (FirstFieldType->isIntegerType() && - SecondFieldType->isAnyPointerType()); + validatePointerType(SecondFieldType)); } static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { diff --git a/clang/test/Sema/attr-malloc_span.c b/clang/test/Sema/attr-malloc_span.c index 65b60bd03e49e..c1a8e9e08a77d 100644 --- a/clang/test/Sema/attr-malloc_span.c +++ b/clang/test/Sema/attr-malloc_span.c @@ -29,3 +29,10 @@ typedef struct { size_t n2; } invalid_span2; invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to functions that return span-like structures}} + +// Function pointers are not allowed. +typedef struct { + int (*func_ptr)(void); + size_t n; +} func_span; +func_span returns_func_span (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to functions that return span-like structures}} diff --git a/clang/test/SemaCXX/attr-malloc_span.cpp b/clang/test/SemaCXX/attr-malloc_span.cpp new file mode 100644 index 0000000000000..c70a8c3a1a755 --- /dev/null +++ b/clang/test/SemaCXX/attr-malloc_span.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s + +class SomeClass { +public: + int Data; +}; + +// Returning pointers to data members is not allowed. +struct DataMemberSpan { + int SomeClass::* member_ptr; + int n; +}; + +DataMemberSpan returns_data_member_span(void) __attribute((malloc_span)) { // expected-warning {{attribute only applies to functions that return span-like structures}} + return DataMemberSpan{}; +} + +// Returning pointers to member functions is not allowed. +struct MemberFuncSpan { + void (SomeClass::*member_func_ptr)(); + int n; +}; + +MemberFuncSpan returns_member_func_span(void) __attribute((malloc_span)) { // expected-warning {{attribute only applies to functions that return span-like structures}} + return MemberFuncSpan{}; +} + >From 45c1a77ca1cf6d9cb6866e137df75d198952ca55 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Thu, 13 Nov 2025 17:59:12 +0000 Subject: [PATCH 07/13] fixup: rework malloc_span validation and error reporting --- .../clang/Basic/DiagnosticSemaKinds.td | 13 +++- clang/lib/Sema/SemaDeclAttr.cpp | 63 ++++++++++++++----- clang/test/Sema/attr-malloc_span.c | 46 ++++++++++++-- clang/test/SemaCXX/attr-malloc_span.cpp | 8 ++- 4 files changed, 102 insertions(+), 28 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index df8b7fed9bafa..3078d8e797305 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3450,10 +3450,17 @@ 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: " - "one field is a pointer to the allocated memory and another field is an integer with " - "the size of the allocated memory">, + : Warning<"%0 attribute only applies to functions that return span-like " + "structures">, InGroup<IgnoredAttributes>; +def note_span_must_be_struct : Note<"span-like type must be a struct">; +def note_span_must_be_complete : Note<"span-like type must be complete">; +def note_wrong_span_field_count : Note<"span-like type must have 2 fields">; +def note_wrong_span_field_types + : Note<"span-like type must have a pointer and an integer field or two " + "pointer fields">; +def note_span_invalid_integer : Note<"the integer field must be an actual " + "integer that is at least as big as int">; 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/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 44b10e360fe10..e3666baf75918 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1839,43 +1839,72 @@ static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) { RestrictAttr(S.Context, AL, DeallocE, DeallocPtrIdx)); } -static bool checkSpanLikeType(const QualType &Ty) { +static bool checkSpanLikeType(Sema &S, const ParsedAttr &AL, + const QualType &Ty) { // 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. - // Note that there may also be numerous cases of pointer+integer structures - // not actually exhibiting a span-like semantics, so sometimes - // this heuristic expectedly leads to false positive results. + // 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 = [&S, &AL](unsigned NoteDiagID) { + S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only) << AL; + S.Diag(AL.getLoc(), NoteDiagID); + }; const RecordDecl *RD = Ty->getAsRecordDecl(); - if (!RD || RD->isUnion()) + if (!RD || RD->isUnion()) { + emitWarning(diag::note_span_must_be_struct); return false; + } const RecordDecl *Def = RD->getDefinition(); - if (!Def) + if (!Def) { + emitWarning(diag::note_span_must_be_complete); return false; // This is an incomplete type. + } auto FieldsBegin = Def->field_begin(); - if (std::distance(FieldsBegin, Def->field_end()) != 2) + if (std::distance(FieldsBegin, Def->field_end()) != 2) { + emitWarning(diag::note_wrong_span_field_count); return false; + } const QualType FirstFieldType = FieldsBegin->getType(); const QualType SecondFieldType = std::next(FieldsBegin)->getType(); auto validatePointerType = [](const QualType &T) { // It must not point to functions. return T->isPointerType() && !T->isFunctionPointerType(); }; - // Verify two possible orderings. - return (validatePointerType(FirstFieldType) && - SecondFieldType->isIntegerType()) || - (FirstFieldType->isIntegerType() && - validatePointerType(SecondFieldType)); + auto checkIntegerType = [&S, emitWarning](const QualType &T) { + bool valid = false; + // Must be an actual integer and at least as bit as int. + if (const auto *BT = dyn_cast<BuiltinType>(T.getCanonicalType())) { + const auto IntSize = S.Context.getTypeSize(S.Context.IntTy); + valid = BT->isInteger() && S.Context.getTypeSize(BT) >= IntSize; + } + if (!valid) { + emitWarning(diag::note_span_invalid_integer); + } + return valid; + }; + if (validatePointerType(FirstFieldType) && + validatePointerType(SecondFieldType)) { + // Pointer + pointer. + return true; + } else if (validatePointerType(FirstFieldType)) { + // Pointer + integer? + return checkIntegerType(SecondFieldType); + } else if (validatePointerType(SecondFieldType)) { + // Integer + pointer? + return checkIntegerType(FirstFieldType); + } + emitWarning(diag::note_wrong_span_field_types); + return false; } static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { QualType ResultType = getFunctionOrMethodResultType(D); - if (!checkSpanLikeType(ResultType)) { - S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only) - << AL << getFunctionOrMethodResultSourceRange(D); - return; + if (checkSpanLikeType(S, AL, ResultType)) { + D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL)); } - D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL)); } static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) { diff --git a/clang/test/Sema/attr-malloc_span.c b/clang/test/Sema/attr-malloc_span.c index c1a8e9e08a77d..8203e9df4d330 100644 --- a/clang/test/Sema/attr-malloc_span.c +++ b/clang/test/Sema/attr-malloc_span.c @@ -16,23 +16,57 @@ typedef struct { } span2; span2 returns_span2 (void) __attribute((malloc_span)); // no-warning -// Ensure that a warning is produced on malloc_span precondition violation. typedef struct { void *ptr; void *ptr2; -} invalid_span1; -invalid_span1 returns_non_std_span1 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to functions that return span-like structures}} +} span3; +span3 returns_span3 (void) __attribute((malloc_span)); // no-warning +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{span-like type must be a struct}} +int *returns_int_ptr (void) __attribute((malloc_span)); typedef struct { void *ptr; size_t n; size_t n2; -} invalid_span2; -invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to functions that return span-like structures}} +} too_long_span; +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{span-like type must have 2 fields}} +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; -func_span returns_func_span (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to functions that return span-like structures}} +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{span-like type must have a pointer and an integer field or two pointer fields}} +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 {{the integer field must be an actual 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 {{the integer field must be an actual 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 {{the integer field must be an actual integer}} +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 index c70a8c3a1a755..8748038f8d133 100644 --- a/clang/test/SemaCXX/attr-malloc_span.cpp +++ b/clang/test/SemaCXX/attr-malloc_span.cpp @@ -11,7 +11,9 @@ struct DataMemberSpan { int n; }; -DataMemberSpan returns_data_member_span(void) __attribute((malloc_span)) { // expected-warning {{attribute only applies to functions that return span-like structures}} +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{span-like type must have a pointer and an integer field or two pointer fields}} +DataMemberSpan returns_data_member_span(void) __attribute((malloc_span)) { return DataMemberSpan{}; } @@ -21,7 +23,9 @@ struct MemberFuncSpan { int n; }; -MemberFuncSpan returns_member_func_span(void) __attribute((malloc_span)) { // expected-warning {{attribute only applies to functions that return span-like structures}} +// expected-warning@+2 {{attribute only applies to functions that return span-like structures}} +// expected-note@+1 {{span-like type must have a pointer and an integer field or two pointer fields}} +MemberFuncSpan returns_member_func_span(void) __attribute((malloc_span)) { return MemberFuncSpan{}; } >From 585f214958489a9e6ec82c753811f55a19708cf0 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Thu, 13 Nov 2025 18:50:27 +0000 Subject: [PATCH 08/13] fixup: update documentation Reflect that pointer+pointer is also possible now. --- clang/docs/ReleaseNotes.rst | 2 +- clang/include/clang/Basic/AttrDocs.td | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 7cccfe1225416..c17e9121d1f83 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -329,7 +329,7 @@ Attribute Changes in Clang - 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). + span-like structures (i.e. those that contain a pointer field and a size integer field or two pointers). Improvements to Clang's diagnostics ----------------------------------- diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 778e84321d9fd..5702ecc051899 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5257,8 +5257,9 @@ 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 fields, one of -which is a pointer to the allocated memory and another one is an integer type -containing the size of the actually allocated memory. +which is a pointer to the start of theallocated memory and another one is +either an integer type containing the size of the actually allocated memory +or a pointer the end of the allocated region. }]; } >From 6ab5f3e333d62e8262aec87f2db637e84d98d5d7 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Fri, 14 Nov 2025 11:20:40 +0000 Subject: [PATCH 09/13] fixup: refactor semantic checks Change error descriptions and restructure the checks code. --- .../clang/Basic/DiagnosticSemaKinds.td | 20 ++++--- clang/lib/Sema/SemaDeclAttr.cpp | 56 ++++++++----------- clang/test/Sema/attr-malloc_span.c | 25 ++++++--- clang/test/SemaCXX/attr-malloc_span.cpp | 4 +- 4 files changed, 56 insertions(+), 49 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3078d8e797305..565f170200c21 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3453,14 +3453,18 @@ def warn_attribute_return_span_only : Warning<"%0 attribute only applies to functions that return span-like " "structures">, InGroup<IgnoredAttributes>; -def note_span_must_be_struct : Note<"span-like type must be a struct">; -def note_span_must_be_complete : Note<"span-like type must be complete">; -def note_wrong_span_field_count : Note<"span-like type must have 2 fields">; -def note_wrong_span_field_types - : Note<"span-like type must have a pointer and an integer field or two " - "pointer fields">; -def note_span_invalid_integer : Note<"the integer field must be an actual " - "integer that is at least as big as int">; +def note_returned_not_struct : Note<"returned type is not a struct/class type">; +def note_returned_incomlete_type : Note<"returned type is incomplete">; +def note_returned_not_two_field_struct + : Note<"returned struct/class has %0 fields, expected 2">; +def note_returned_not_span_struct + : Note<"returned struct/class fields are not a supported combination for a " + "span-like type (expected pointer/integer or pointer/pointer)">; +def note_returned_not_integer_field + : Note<"field #%0 expected to be an integer">; +def note_returned_not_wide_enough_field + : Note<"integer field #%0 of span-like type is not wide enough (minimum " + "width: %1)">; 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/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index e3666baf75918..6015584e9fdcf 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1850,59 +1850,51 @@ static bool checkSpanLikeType(Sema &S, const ParsedAttr &AL, // lead to false positive results. auto emitWarning = [&S, &AL](unsigned NoteDiagID) { S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only) << AL; - S.Diag(AL.getLoc(), NoteDiagID); + return S.Diag(AL.getLoc(), NoteDiagID); }; + if (Ty->isIncompleteType()) + return emitWarning(diag::note_returned_incomlete_type); const RecordDecl *RD = Ty->getAsRecordDecl(); - if (!RD || RD->isUnion()) { - emitWarning(diag::note_span_must_be_struct); - return false; - } - const RecordDecl *Def = RD->getDefinition(); - if (!Def) { - emitWarning(diag::note_span_must_be_complete); - return false; // This is an incomplete type. - } - auto FieldsBegin = Def->field_begin(); - if (std::distance(FieldsBegin, Def->field_end()) != 2) { - emitWarning(diag::note_wrong_span_field_count); - return false; - } + if (!RD || RD->isUnion()) + return emitWarning(diag::note_returned_not_struct); + auto FieldsBegin = RD->field_begin(); + const auto FieldsCount = std::distance(FieldsBegin, RD->field_end()); + if (FieldsCount != 2) + return emitWarning(diag::note_returned_not_two_field_struct) << FieldsCount; const QualType FirstFieldType = FieldsBegin->getType(); const QualType SecondFieldType = std::next(FieldsBegin)->getType(); auto validatePointerType = [](const QualType &T) { // It must not point to functions. return T->isPointerType() && !T->isFunctionPointerType(); }; - auto checkIntegerType = [&S, emitWarning](const QualType &T) { - bool valid = false; - // Must be an actual integer and at least as bit as int. - if (const auto *BT = dyn_cast<BuiltinType>(T.getCanonicalType())) { - const auto IntSize = S.Context.getTypeSize(S.Context.IntTy); - valid = BT->isInteger() && S.Context.getTypeSize(BT) >= IntSize; - } - if (!valid) { - emitWarning(diag::note_span_invalid_integer); - } - return valid; + auto checkIntegerType = [&S, 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; + const auto IntSize = S.Context.getTypeSize(S.Context.IntTy); + if (S.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 true; + return false; } else if (validatePointerType(FirstFieldType)) { // Pointer + integer? - return checkIntegerType(SecondFieldType); + return checkIntegerType(SecondFieldType, 2); } else if (validatePointerType(SecondFieldType)) { // Integer + pointer? - return checkIntegerType(FirstFieldType); + return checkIntegerType(FirstFieldType, 1); } - emitWarning(diag::note_wrong_span_field_types); - return false; + return emitWarning(diag::note_returned_not_span_struct); } static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { QualType ResultType = getFunctionOrMethodResultType(D); - if (checkSpanLikeType(S, AL, ResultType)) { + if (!checkSpanLikeType(S, AL, ResultType)) { D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL)); } } diff --git a/clang/test/Sema/attr-malloc_span.c b/clang/test/Sema/attr-malloc_span.c index 8203e9df4d330..6dc94ed3797f3 100644 --- a/clang/test/Sema/attr-malloc_span.c +++ b/clang/test/Sema/attr-malloc_span.c @@ -9,7 +9,6 @@ typedef struct { } span; span returns_span (void) __attribute((malloc_span)); // no-warning -// Try out a different field ordering. typedef struct { size_t n; void *ptr; @@ -21,8 +20,20 @@ typedef struct { 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 {{span-like type must be a struct}} +// expected-note@+1 {{returned type is not a struct/class type}} int *returns_int_ptr (void) __attribute((malloc_span)); typedef struct { @@ -31,7 +42,7 @@ typedef struct { size_t n2; } too_long_span; // expected-warning@+2 {{attribute only applies to functions that return span-like structures}} -// expected-note@+1 {{span-like type must have 2 fields}} +// expected-note@+1 {{returned struct/class has 3 fields, expected 2}} too_long_span returns_too_long_span (void) __attribute((malloc_span)); // Function pointers are not allowed. @@ -40,7 +51,7 @@ typedef struct { size_t n; } func_span; // expected-warning@+2 {{attribute only applies to functions that return span-like structures}} -// expected-note@+1 {{span-like type must have a pointer and an integer field or two pointer fields}} +// expected-note@+1 {{returned struct/class fields are not a supported combination}} func_span returns_func_span (void) __attribute((malloc_span)); // Integer should not be an enum. @@ -50,7 +61,7 @@ typedef struct { enum some_enum field; } enum_span; // expected-warning@+2 {{attribute only applies to functions that return span-like structures}} -// expected-note@+1 {{the integer field must be an actual integer}} +// expected-note@+1 {{field #2 expected to be an integer}} enum_span returns_enum_span (void) __attribute((malloc_span)); // Bit integers are also not supported. @@ -59,7 +70,7 @@ typedef struct { _BitInt(16) n; } bit_span; // expected-warning@+2 {{attribute only applies to functions that return span-like structures}} -// expected-note@+1 {{the integer field must be an actual integer}} +// expected-note@+1 {{field #2 expected to be an integer}} bit_span returns_bit_span (void) __attribute((malloc_span)); // Integer must be at least as big as int. @@ -68,5 +79,5 @@ typedef struct { short n; } short_span; // expected-warning@+2 {{attribute only applies to functions that return span-like structures}} -// expected-note@+1 {{the integer field must be an actual integer}} +// expected-note@+1 {{integer field #2 of span-like type is not wide enough (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 index 8748038f8d133..747216b637c5f 100644 --- a/clang/test/SemaCXX/attr-malloc_span.cpp +++ b/clang/test/SemaCXX/attr-malloc_span.cpp @@ -12,7 +12,7 @@ struct DataMemberSpan { }; // expected-warning@+2 {{attribute only applies to functions that return span-like structures}} -// expected-note@+1 {{span-like type must have a pointer and an integer field or two pointer fields}} +// expected-note@+1 {{returned struct/class fields are not a supported combination}} DataMemberSpan returns_data_member_span(void) __attribute((malloc_span)) { return DataMemberSpan{}; } @@ -24,7 +24,7 @@ struct MemberFuncSpan { }; // expected-warning@+2 {{attribute only applies to functions that return span-like structures}} -// expected-note@+1 {{span-like type must have a pointer and an integer field or two pointer fields}} +// expected-note@+1 {{returned struct/class fields are not a supported combination}} MemberFuncSpan returns_member_func_span(void) __attribute((malloc_span)) { return MemberFuncSpan{}; } >From 2faecf253c228a17fb8325473bc0d2470ca6b38c Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Fri, 14 Nov 2025 18:42:03 +0000 Subject: [PATCH 10/13] fixup: remove typos Fix typos in docs and diagnostic messages. Remove unnecessary line in a test file. --- clang/include/clang/Basic/AttrDocs.td | 2 +- clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/lib/Sema/SemaDeclAttr.cpp | 2 +- clang/test/Sema/attr-malloc_span.c | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 5702ecc051899..44f747e5a8877 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5257,7 +5257,7 @@ 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 fields, one of -which is a pointer to the start of theallocated memory and another one is +which is a pointer to the start of the allocated memory and another one is either an integer type containing the size of the actually allocated memory or a pointer the end of the allocated region. }]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 565f170200c21..f6a5df9f76d51 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3454,7 +3454,7 @@ def warn_attribute_return_span_only "structures">, InGroup<IgnoredAttributes>; def note_returned_not_struct : Note<"returned type is not a struct/class type">; -def note_returned_incomlete_type : Note<"returned type is incomplete">; +def note_returned_incomplete_type : Note<"returned type is incomplete">; def note_returned_not_two_field_struct : Note<"returned struct/class has %0 fields, expected 2">; def note_returned_not_span_struct diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 6015584e9fdcf..573ba07531e82 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1853,7 +1853,7 @@ static bool checkSpanLikeType(Sema &S, const ParsedAttr &AL, return S.Diag(AL.getLoc(), NoteDiagID); }; if (Ty->isIncompleteType()) - return emitWarning(diag::note_returned_incomlete_type); + return emitWarning(diag::note_returned_incomplete_type); const RecordDecl *RD = Ty->getAsRecordDecl(); if (!RD || RD->isUnion()) return emitWarning(diag::note_returned_not_struct); diff --git a/clang/test/Sema/attr-malloc_span.c b/clang/test/Sema/attr-malloc_span.c index 6dc94ed3797f3..bcfb4e6a14225 100644 --- a/clang/test/Sema/attr-malloc_span.c +++ b/clang/test/Sema/attr-malloc_span.c @@ -1,5 +1,4 @@ // RUN: %clang_cc1 -verify -fsyntax-only %s -// RUN: %clang_cc1 -emit-llvm -o %t %s typedef __SIZE_TYPE__ size_t; >From d8708e0f6764d01d265eb8ded562380617739dc1 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Fri, 14 Nov 2025 18:23:58 +0000 Subject: [PATCH 11/13] [experimental] Support templates Support returned dependent types and class tempate instantiations. --- clang/include/clang/Sema/Sema.h | 5 +++ clang/lib/Sema/SemaDeclAttr.cpp | 33 ++++++++------ .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 19 ++++++++ clang/test/SemaCXX/attr-malloc_span.cpp | 43 +++++++++++++++++++ 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 0470645a9e7ad..b1cbd3ca894dd 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5105,6 +5105,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 573ba07531e82..a2315a44cbae8 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1839,19 +1839,24 @@ static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) { RestrictAttr(S.Context, AL, DeallocE, DeallocPtrIdx)); } -static bool checkSpanLikeType(Sema &S, const ParsedAttr &AL, - const QualType &Ty) { - // 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 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 = [&S, &AL](unsigned NoteDiagID) { - S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only) << AL; - return S.Diag(AL.getLoc(), NoteDiagID); + auto emitWarning = [this, &CI](unsigned NoteDiagID) { + Diag(CI.getLoc(), diag::warn_attribute_return_span_only) << CI; + return Diag(CI.getLoc(), NoteDiagID); }; + if (!Ty->isDependentType()) { + // If the type is a class template specialization, it may not be + // instantiated at this stage. We must force it to be complete to examine + // its fields. + // The returned value is discarded since the code below emits a warning + // if the type keeps being incomplete. + (void)isCompleteType(CI.getLoc(), Ty); + } if (Ty->isIncompleteType()) return emitWarning(diag::note_returned_incomplete_type); const RecordDecl *RD = Ty->getAsRecordDecl(); @@ -1867,13 +1872,13 @@ static bool checkSpanLikeType(Sema &S, const ParsedAttr &AL, // It must not point to functions. return T->isPointerType() && !T->isFunctionPointerType(); }; - auto checkIntegerType = [&S, emitWarning](const QualType &T, - const int FieldNo) -> bool { + 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; - const auto IntSize = S.Context.getTypeSize(S.Context.IntTy); - if (S.Context.getTypeSize(BT) < IntSize) + const auto IntSize = Context.getTypeSize(Context.IntTy); + if (Context.getTypeSize(BT) < IntSize) return emitWarning(diag::note_returned_not_wide_enough_field) << FieldNo << IntSize; return false; @@ -1894,7 +1899,9 @@ static bool checkSpanLikeType(Sema &S, const ParsedAttr &AL, static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { QualType ResultType = getFunctionOrMethodResultType(D); - if (!checkSpanLikeType(S, AL, ResultType)) { + if (ResultType->isDependentType() || !S.CheckSpanLikeType(AL, ResultType)) { + // If it's a dependent type, the attribute will be re-checked upon + // instantiation. D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL)); } } diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 4d58f00168298..2a2f3b52bcfaa 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -796,6 +796,20 @@ 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 (RT->isDependentType()) { + // The type is still dependent. + // Clone the attribute, it will be checked later. + New->addAttr(Attr->clone(S.getASTContext())); + } else if (!S.CheckSpanLikeType(*Attr, RT)) { + // The conditions have been successfully validated. + New->addAttr(Attr->clone(S.getASTContext())); + } +} + void Sema::InstantiateAttrsForDecl( const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl, Decl *New, LateInstantiatedAttrVec *LateAttrs, @@ -1007,6 +1021,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, continue; } + if (auto *A = dyn_cast<MallocSpanAttr>(TmplAttr)) { + instantiateDependentMallocSpanAttr(*this, A, New); + continue; + } + assert(!TmplAttr->isPackExpansion()); if (TmplAttr->isLateParsed() && LateAttrs) { // Late parsed attributes must be instantiated and attached after the diff --git a/clang/test/SemaCXX/attr-malloc_span.cpp b/clang/test/SemaCXX/attr-malloc_span.cpp index 747216b637c5f..be54050c4ee7a 100644 --- a/clang/test/SemaCXX/attr-malloc_span.cpp +++ b/clang/test/SemaCXX/attr-malloc_span.cpp @@ -29,3 +29,46 @@ 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/class 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/class 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>(); +} >From a901d4a9a5fdcb8632036fb18b291c8e65d90b83 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Mon, 17 Nov 2025 13:09:36 +0000 Subject: [PATCH 12/13] fixup: add trailing return type tests --- clang/test/SemaCXX/attr-malloc_span.cpp | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/clang/test/SemaCXX/attr-malloc_span.cpp b/clang/test/SemaCXX/attr-malloc_span.cpp index be54050c4ee7a..b16d3075344d0 100644 --- a/clang/test/SemaCXX/attr-malloc_span.cpp +++ b/clang/test/SemaCXX/attr-malloc_span.cpp @@ -72,3 +72,31 @@ void TestGoodBadSpan() { // 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 trailling_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/class has 1 fields, expected 2}} +__attribute__((malloc_span)) auto templated_trailling_return_type() -> T { // no-warning + return T{}; +} + +void TestGoodBadTrailingReturnType() { + templated_trailling_return_type<GoodSpan>(); // no-warnings + // expected-note@+1 {{in instantiation of function template specialization 'templated_trailling_return_type<BadSpan>' requested here}} + templated_trailling_return_type<BadSpan>(); +} + +__attribute((malloc_span)) auto trailling_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/class fields are not a supported combination for a span-like type}} +__attribute((malloc_span)) auto trailling_return_temmplate_bad(void) -> Pair<int, int> { + return Pair<int, int>{}; +} >From 6991fe6274e80a4c0cb010334b93c959ca548ef8 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh <[email protected]> Date: Mon, 17 Nov 2025 14:09:55 +0000 Subject: [PATCH 13/13] fixup: simplify dependent type processing code --- clang/lib/Sema/SemaDeclAttr.cpp | 18 +++++------------- clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 8 +------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index a2315a44cbae8..7ebd60b474a5c 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1849,15 +1849,10 @@ bool Sema::CheckSpanLikeType(const AttributeCommonInfo &CI, Diag(CI.getLoc(), diag::warn_attribute_return_span_only) << CI; return Diag(CI.getLoc(), NoteDiagID); }; - if (!Ty->isDependentType()) { - // If the type is a class template specialization, it may not be - // instantiated at this stage. We must force it to be complete to examine - // its fields. - // The returned value is discarded since the code below emits a warning - // if the type keeps being incomplete. - (void)isCompleteType(CI.getLoc(), Ty); - } - if (Ty->isIncompleteType()) + 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()) @@ -1899,11 +1894,8 @@ bool Sema::CheckSpanLikeType(const AttributeCommonInfo &CI, static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) { QualType ResultType = getFunctionOrMethodResultType(D); - if (ResultType->isDependentType() || !S.CheckSpanLikeType(AL, ResultType)) { - // If it's a dependent type, the attribute will be re-checked upon - // instantiation. + if (!S.CheckSpanLikeType(AL, ResultType)) D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL)); - } } static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) { diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 2a2f3b52bcfaa..dfd3f2b60f102 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -800,14 +800,8 @@ static void instantiateDependentMallocSpanAttr(Sema &S, const MallocSpanAttr *Attr, Decl *New) { QualType RT = getFunctionOrMethodResultType(New); - if (RT->isDependentType()) { - // The type is still dependent. - // Clone the attribute, it will be checked later. + if (!S.CheckSpanLikeType(*Attr, RT)) New->addAttr(Attr->clone(S.getASTContext())); - } else if (!S.CheckSpanLikeType(*Attr, RT)) { - // The conditions have been successfully validated. - New->addAttr(Attr->clone(S.getASTContext())); - } } void Sema::InstantiateAttrsForDecl( _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
