https://github.com/a-nogikh updated 
https://github.com/llvm/llvm-project/pull/165433

>From cb8dba47bb92b013e92b9a9fc6fc434e5f20ce92 Mon Sep 17 00:00:00 2001
From: Aleksandr Nogikh <[email protected]>
Date: Fri, 17 Oct 2025 18:05:36 +0200
Subject: [PATCH 1/4] [Clang] Allow malloc and alloc_size attributes for
 functions returning structs

These attributes restrict the possible function signatures to the ones
returning a pointer, which is not the case for some non-standard
allocation functions 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.

Relax the mentioned restriction and allow "malloc" and "alloc_size"
attributes to be applied not just to the functions returning pointer
types, but also to the functions returning records whose first member is
a pointer (which would be 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.

As the previous restriction was dictated by the need to set the noalias
attribute, adjust the code generation logic to only assign the attribute
when the returned value type is a pointer type.

In future commits, codegen can be improved to recognize the
noalias-ness of the pointer returned inside a span-like struct.

This change also 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/include/clang/AST/TypeBase.h |  1 +
 clang/lib/AST/Type.cpp             | 20 ++++++++++++++++++++
 clang/lib/CodeGen/CGCall.cpp       |  3 ++-
 clang/lib/Sema/SemaDeclAttr.cpp    |  5 +++--
 clang/test/CodeGen/attr-malloc.c   |  9 ++++++++-
 clang/test/Sema/alloc-size.c       |  8 ++++++++
 clang/test/Sema/attr-malloc.c      | 26 ++++++++++++++++++++++++++
 7 files changed, 68 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index f07861f50fe8c..3ff66ddbf7edc 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -2601,6 +2601,7 @@ class alignas(TypeAlignment) Type : public 
ExtQualsTypeCommonBase {
   bool isFunctionProtoType() const { return getAs<FunctionProtoType>(); }
   bool isPointerType() const;
   bool isPointerOrReferenceType() const;
+  bool isSpanLikeType() const;
   bool isSignableType(const ASTContext &Ctx) const;
   bool isSignablePointerType() const;
   bool isSignableIntegerType(const ASTContext &Ctx) const;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 4548af17e37f2..9c59640c6ca49 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5753,3 +5753,23 @@ StringRef PredefinedSugarType::getName(Kind KD) {
   }
   llvm_unreachable("unexpected kind");
 }
+
+bool Type::isSpanLikeType() const {
+  // 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.
+  const RecordDecl *RD = 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();
+}
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 465f3f4e670c2..6c3aab528dc47 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2507,7 +2507,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
       FuncAttrs.addAttribute(llvm::Attribute::NoUnwind);
     }
     if (const auto *RA = TargetDecl->getAttr<RestrictAttr>();
-        RA && RA->getDeallocator() == nullptr)
+        RA && RA->getDeallocator() == nullptr &&
+        FI.getReturnType()->getAs<PointerType>())
       RetAttrs.addAttribute(llvm::Attribute::NoAlias);
     if (TargetDecl->hasAttr<ReturnsNonNullAttr>() &&
         !CodeGenOpts.NullPointerIsValid)
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index a9e7b44ac9d73..f8d4c481890e0 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -571,7 +571,7 @@ static void handleAllocSizeAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   assert(isFuncOrMethodForAttrSubject(D) && hasFunctionProto(D));
 
   QualType RetTy = getFunctionOrMethodResultType(D);
-  if (!RetTy->isPointerType()) {
+  if (!RetTy->isPointerType() && !RetTy->isSpanLikeType()) {
     S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only) << AL;
     return;
   }
@@ -1750,7 +1750,8 @@ static void handleTLSModelAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
 
 static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   QualType ResultType = getFunctionOrMethodResultType(D);
-  if (!ResultType->isAnyPointerType() && !ResultType->isBlockPointerType()) {
+  if (!ResultType->isAnyPointerType() && !ResultType->isBlockPointerType() &&
+      !ResultType->isSpanLikeType()) {
     S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
         << AL << getFunctionOrMethodResultSourceRange(D);
     return;
diff --git a/clang/test/CodeGen/attr-malloc.c b/clang/test/CodeGen/attr-malloc.c
index e69f8bce55f3b..238ca95a13be1 100644
--- a/clang/test/CodeGen/attr-malloc.c
+++ b/clang/test/CodeGen/attr-malloc.c
@@ -3,10 +3,17 @@
 int *Mem;
 void dealloc(int*);
 
+typedef struct {
+  void *p;
+  int size;
+} sized_ptr;
+
 __attribute__((malloc)) int *MallocFunc(){ return Mem;}
 // CHECK: define[[BEFORE:.*]] noalias[[AFTER:.*]]@MallocFunc
-// Ensure these two do not generate noalias here.
+// Ensure these three do not generate noalias here.
 __attribute__((malloc(dealloc))) int *MallocFunc2(){ return Mem;}
 // CHECK: define[[BEFORE]][[AFTER]]@MallocFunc2
 __attribute__((malloc(dealloc, 1))) int *MallocFunc3(){ return Mem;}
 // CHECK: define[[BEFORE]][[AFTER]]@MallocFunc3
+__attribute__((malloc)) sized_ptr MallocFunc4(){ return (sized_ptr){ .p = Mem 
};}
+// CHECK: define[[BEFORE]] { ptr, i32 } @MallocFunc4
diff --git a/clang/test/Sema/alloc-size.c b/clang/test/Sema/alloc-size.c
index 93714894a630a..29681fd483f41 100644
--- a/clang/test/Sema/alloc-size.c
+++ b/clang/test/Sema/alloc-size.c
@@ -30,6 +30,14 @@ void *KR() __attribute__((alloc_size(1))); 
//expected-warning{{'alloc_size' attr
 void *(__attribute__((alloc_size(1))) * func_ptr1)(int);
 void *(__attribute__((alloc_size(1, 2))) func_ptr2)(int, int);
 
+// Applying alloc_size to functions returning a struct with a pointer as a 
first field should work.
+typedef struct {
+  void* p;
+  int n;
+} sized_ptr;
+
+sized_ptr sized_ptr_alloc(int len) __attribute__((alloc_size(1)));
+
 // TODO: according to GCC documentation the following should actually be the 
type
 // “pointer to pointer to alloc_size attributed function returning void*” and 
should
 // therefore be supported
diff --git a/clang/test/Sema/attr-malloc.c b/clang/test/Sema/attr-malloc.c
index a431aa43969d7..e65787bbc3f6a 100644
--- a/clang/test/Sema/attr-malloc.c
+++ b/clang/test/Sema/attr-malloc.c
@@ -13,6 +13,32 @@ int   returns_int   (void) __attribute((malloc)); // 
expected-warning {{attribut
 int * returns_intptr(void) __attribute((malloc)); // no-warning
 typedef int * iptr;
 iptr  returns_iptr  (void) __attribute((malloc)); // no-warning
+typedef struct {
+  void *ptr;
+  size_t n;
+} sized_ptr;
+sized_ptr  returns_sized_ptr  (void) __attribute((malloc)); // 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)); // 
expected-warning {{attribute only applies to return values that are pointers}}
+
+typedef struct {
+  void *ptr;
+  void *ptr2;
+} invalid_span2;
+invalid_span2  returns_non_std_span2  (void) __attribute((malloc)); // 
expected-warning {{attribute only applies to return values that are pointers}}
+
+typedef struct {
+  void *ptr;
+  size_t n;
+  size_t n2;
+} invalid_span3;
+invalid_span3  returns_non_std_span3  (void) __attribute((malloc)); // 
expected-warning {{attribute only applies to return values that are pointers}}
 
 __attribute((malloc)) void *(*f)(void); //  expected-warning{{attribute only 
applies to functions}}
 __attribute((malloc)) int (*g)(void); // expected-warning{{attribute only 
applies to functions}}

>From 0b443157807dabc7eb3f66dffe4425f6407cf33f Mon Sep 17 00:00:00 2001
From: Aleksandr Nogikh <[email protected]>
Date: Thu, 6 Nov 2025 11:49:45 +0000
Subject: [PATCH 2/4] fixup: move isSpanLikeType() to SemaDeclAttr.cpp

Also leave a comment about possible false positives.
---
 clang/include/clang/AST/TypeBase.h |  1 -
 clang/lib/AST/Type.cpp             | 20 --------------------
 clang/lib/Sema/SemaDeclAttr.cpp    | 27 +++++++++++++++++++++++++--
 3 files changed, 25 insertions(+), 23 deletions(-)

diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index 3ff66ddbf7edc..f07861f50fe8c 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -2601,7 +2601,6 @@ class alignas(TypeAlignment) Type : public 
ExtQualsTypeCommonBase {
   bool isFunctionProtoType() const { return getAs<FunctionProtoType>(); }
   bool isPointerType() const;
   bool isPointerOrReferenceType() const;
-  bool isSpanLikeType() const;
   bool isSignableType(const ASTContext &Ctx) const;
   bool isSignablePointerType() const;
   bool isSignableIntegerType(const ASTContext &Ctx) const;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 9c59640c6ca49..4548af17e37f2 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5753,23 +5753,3 @@ StringRef PredefinedSugarType::getName(Kind KD) {
   }
   llvm_unreachable("unexpected kind");
 }
-
-bool Type::isSpanLikeType() const {
-  // 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.
-  const RecordDecl *RD = 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();
-}
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index f8d4c481890e0..75e933c81406b 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -564,6 +564,29 @@ static bool checkParamIsIntegerType(Sema &S, const Decl 
*D, const AttrInfo &AI,
   return true;
 }
 
+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 std::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 handleAllocSizeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   if (!AL.checkAtLeastNumArgs(S, 1) || !AL.checkAtMostNumArgs(S, 2))
     return;
@@ -571,7 +594,7 @@ static void handleAllocSizeAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   assert(isFuncOrMethodForAttrSubject(D) && hasFunctionProto(D));
 
   QualType RetTy = getFunctionOrMethodResultType(D);
-  if (!RetTy->isPointerType() && !RetTy->isSpanLikeType()) {
+  if (!RetTy->isPointerType() && !isSpanLikeType(RetTy)) {
     S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only) << AL;
     return;
   }
@@ -1751,7 +1774,7 @@ static void handleTLSModelAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
 static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   QualType ResultType = getFunctionOrMethodResultType(D);
   if (!ResultType->isAnyPointerType() && !ResultType->isBlockPointerType() &&
-      !ResultType->isSpanLikeType()) {
+      !isSpanLikeType(ResultType)) {
     S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
         << AL << getFunctionOrMethodResultSourceRange(D);
     return;

>From 63c5c5856f27ecde3e1d4e98364ecc8e2871345f Mon Sep 17 00:00:00 2001
From: Aleksandr Nogikh <[email protected]>
Date: Thu, 6 Nov 2025 13:52:24 +0000
Subject: [PATCH 3/4] fixup: drop alloc_size attribute changes

Extending the range of functions alloc_size applies to makes no sense
without the __builtin_object_size-side support.

For now, just the malloc attribute changes should be enough.
---
 clang/lib/Sema/SemaDeclAttr.cpp | 48 ++++++++++++++++-----------------
 clang/test/Sema/alloc-size.c    |  8 ------
 2 files changed, 24 insertions(+), 32 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 75e933c81406b..0ac1dbbe94180 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -564,29 +564,6 @@ static bool checkParamIsIntegerType(Sema &S, const Decl 
*D, const AttrInfo &AI,
   return true;
 }
 
-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 std::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 handleAllocSizeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   if (!AL.checkAtLeastNumArgs(S, 1) || !AL.checkAtMostNumArgs(S, 2))
     return;
@@ -594,7 +571,7 @@ static void handleAllocSizeAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   assert(isFuncOrMethodForAttrSubject(D) && hasFunctionProto(D));
 
   QualType RetTy = getFunctionOrMethodResultType(D);
-  if (!RetTy->isPointerType() && !isSpanLikeType(RetTy)) {
+  if (!RetTy->isPointerType()) {
     S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only) << AL;
     return;
   }
@@ -1771,6 +1748,29 @@ static void handleTLSModelAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   D->addAttr(::new (S.Context) TLSModelAttr(S.Context, AL, Model));
 }
 
+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 std::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 handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   QualType ResultType = getFunctionOrMethodResultType(D);
   if (!ResultType->isAnyPointerType() && !ResultType->isBlockPointerType() &&
diff --git a/clang/test/Sema/alloc-size.c b/clang/test/Sema/alloc-size.c
index 29681fd483f41..93714894a630a 100644
--- a/clang/test/Sema/alloc-size.c
+++ b/clang/test/Sema/alloc-size.c
@@ -30,14 +30,6 @@ void *KR() __attribute__((alloc_size(1))); 
//expected-warning{{'alloc_size' attr
 void *(__attribute__((alloc_size(1))) * func_ptr1)(int);
 void *(__attribute__((alloc_size(1, 2))) func_ptr2)(int, int);
 
-// Applying alloc_size to functions returning a struct with a pointer as a 
first field should work.
-typedef struct {
-  void* p;
-  int n;
-} sized_ptr;
-
-sized_ptr sized_ptr_alloc(int len) __attribute__((alloc_size(1)));
-
 // TODO: according to GCC documentation the following should actually be the 
type
 // “pointer to pointer to alloc_size attributed function returning void*” and 
should
 // therefore be supported

>From 63d9cc8421922c2cbdfdefa7a87de2443de6bfcf Mon Sep 17 00:00:00 2001
From: Aleksandr Nogikh <[email protected]>
Date: Thu, 6 Nov 2025 13:31:11 +0000
Subject: [PATCH 4/4] fixup: update the documentation

Mention the change in the release notes and extend the attribute
descriptions.
---
 clang/docs/ReleaseNotes.rst           | 4 ++++
 clang/include/clang/Basic/AttrDocs.td | 5 +++++
 2 files changed, 9 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ae21c69b2d3c5..946ff40417753 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)
 
+- The malloc attribute can now be applied to functions returning span-like 
structures (structs
+  containing a pointer field and a size integer field). Currently, this is 
primarily used for
+  Allocation Token instrumentation.
+
 Improvements to Clang's diagnostics
 -----------------------------------
 - Diagnostics messages now refer to ``structured binding`` instead of 
``decomposition``,
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 1be9a96aa44de..d256a93860544 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -5244,6 +5244,11 @@ deallocation function. When this form is used, it 
enables the compiler to
 diagnose when the incorrect deallocation function is used with this variable.
 However the associated warning, spelled `-Wmismatched-dealloc` in GCC, is not
 yet implemented in clang.
+
+It's also possible to place the ``malloc`` attribute on the functions returning
+span-like structures, that is, the structs having a pointer as the first field
+and an integer with the size of the actually allocated memory as the second 
field.
+Note that extended semantics is clang-specific and not currently supported in 
GCC.
   }];
 }
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to