Skip to content

Commit 520daa2

Browse files
committed
[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
1 parent cdc3cb2 commit 520daa2

File tree

7 files changed

+96
-0
lines changed

7 files changed

+96
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ Attribute Changes in Clang
327327
- New format attributes ``gnu_printf``, ``gnu_scanf``, ``gnu_strftime`` and ``gnu_strfmon`` are added
328328
as aliases for ``printf``, ``scanf``, ``strftime`` and ``strfmon``. (#GH16219)
329329

330+
- New function attribute `malloc_span` is added. Its semantics is similar to that of the `malloc`
331+
attribute, but `malloc_span` applies not to functions returning pointers, but to functions returning
332+
span-like structures (i.e. those that contain a pointer field and a size integer field).
333+
330334
Improvements to Clang's diagnostics
331335
-----------------------------------
332336
- Diagnostics messages now refer to ``structured binding`` instead of ``decomposition``,

clang/include/clang/Basic/Attr.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,6 +2068,12 @@ def Restrict : InheritableAttr {
20682068
let Documentation = [RestrictDocs];
20692069
}
20702070

2071+
def MallocSpan : InheritableAttr {
2072+
let Spellings = [Clang<"malloc_span">];
2073+
let Subjects = SubjectList<[Function]>;
2074+
let Documentation = [MallocSpanDocs];
2075+
}
2076+
20712077
def LayoutVersion : InheritableAttr, TargetSpecificAttr<TargetMicrosoftRecordLayout> {
20722078
let Spellings = [Declspec<"layout_version">];
20732079
let Args = [UnsignedArgument<"Version">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5247,6 +5247,21 @@ yet implemented in clang.
52475247
}];
52485248
}
52495249

5250+
def MallocSpanDocs : Documentation {
5251+
let Category = DocCatFunction;
5252+
let Heading = "malloc_span";
5253+
let Content = [{
5254+
The ``malloc_span`` attribute can be used to mark that a function which acts
5255+
like a system memory allocation function and returns a span-like structure
5256+
where the returned memory range does not alias storage from any other object
5257+
accessible to the caller.
5258+
5259+
In this context, a span-like structure is assumed to have a pointer to the
5260+
allocated memory as its first field and any integer type containing the size
5261+
of the actually allocated memory as the second field.
5262+
}];
5263+
}
5264+
52505265
def ReturnsNonNullDocs : Documentation {
52515266
let Category = NullabilityDocs;
52525267
let Content = [{

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3449,6 +3449,10 @@ def err_attribute_integers_only : Error<
34493449
def warn_attribute_return_pointers_only : Warning<
34503450
"%0 attribute only applies to return values that are pointers">,
34513451
InGroup<IgnoredAttributes>;
3452+
def warn_attribute_return_span_only
3453+
: Warning<"%0 attribute only applies to return values that are span-like "
3454+
"structures">,
3455+
InGroup<IgnoredAttributes>;
34523456
def warn_attribute_return_pointers_refs_only : Warning<
34533457
"%0 attribute only applies to return values that are pointers or references">,
34543458
InGroup<IgnoredAttributes>;

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,38 @@ static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
18391839
RestrictAttr(S.Context, AL, DeallocE, DeallocPtrIdx));
18401840
}
18411841

1842+
static bool isSpanLikeType(const QualType &Ty) {
1843+
// Check that the type is a plain record with the first field being a pointer
1844+
// type and the second field being an integer. This matches the common
1845+
// implementation of std::span or sized_allocation_t in P0901R11.
1846+
// Note that there may also be numerous cases of pointer+integer structures
1847+
// not actually exhibiting a span-like semantics, so sometimes
1848+
// this heuristic expectedly leads to false positive results.
1849+
const RecordDecl *RD = Ty->getAsRecordDecl();
1850+
if (!RD || RD->isUnion())
1851+
return false;
1852+
const RecordDecl *Def = RD->getDefinition();
1853+
if (!Def)
1854+
return false; // This is an incomplete type.
1855+
auto FieldsBegin = Def->field_begin();
1856+
if (std::distance(FieldsBegin, Def->field_end()) != 2)
1857+
return false;
1858+
const FieldDecl *FirstField = *FieldsBegin;
1859+
const FieldDecl *SecondField = *std::next(FieldsBegin);
1860+
return FirstField->getType()->isAnyPointerType() &&
1861+
SecondField->getType()->isIntegerType();
1862+
}
1863+
1864+
static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
1865+
QualType ResultType = getFunctionOrMethodResultType(D);
1866+
if (!isSpanLikeType(ResultType)) {
1867+
S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only)
1868+
<< AL << getFunctionOrMethodResultSourceRange(D);
1869+
return;
1870+
}
1871+
D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL));
1872+
}
1873+
18421874
static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
18431875
// Ensure we don't combine these with themselves, since that causes some
18441876
// confusing behavior.
@@ -7278,6 +7310,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
72787310
case ParsedAttr::AT_Restrict:
72797311
handleRestrictAttr(S, D, AL);
72807312
break;
7313+
case ParsedAttr::AT_MallocSpan:
7314+
handleMallocSpanAttr(S, D, AL);
7315+
break;
72817316
case ParsedAttr::AT_Mode:
72827317
handleModeAttr(S, D, AL);
72837318
break;

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
// CHECK-NEXT: MIGServerRoutine (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_block)
103103
// CHECK-NEXT: MSConstexpr (SubjectMatchRule_function)
104104
// CHECK-NEXT: MSStruct (SubjectMatchRule_record)
105+
// CHECK-NEXT: MallocSpan (SubjectMatchRule_function)
105106
// CHECK-NEXT: MaybeUndef (SubjectMatchRule_variable_is_parameter)
106107
// CHECK-NEXT: MicroMips (SubjectMatchRule_function)
107108
// CHECK-NEXT: MinSize (SubjectMatchRule_function, SubjectMatchRule_objc_method)

clang/test/Sema/attr-malloc_span.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %clang_cc1 -verify -fsyntax-only %s
2+
// RUN: %clang_cc1 -emit-llvm -o %t %s
3+
4+
#include <stddef.h>
5+
6+
typedef struct {
7+
void *ptr;
8+
size_t n;
9+
} sized_ptr;
10+
sized_ptr returns_sized_ptr (void) __attribute((malloc_span)); // no-warning
11+
12+
// The first struct field must be pointer and the second must be an integer.
13+
// Check the possible ways to violate it.
14+
typedef struct {
15+
size_t n;
16+
void *ptr;
17+
} invalid_span1;
18+
invalid_span1 returns_non_std_span1 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}}
19+
20+
typedef struct {
21+
void *ptr;
22+
void *ptr2;
23+
} invalid_span2;
24+
invalid_span2 returns_non_std_span2 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}}
25+
26+
typedef struct {
27+
void *ptr;
28+
size_t n;
29+
size_t n2;
30+
} invalid_span3;
31+
invalid_span3 returns_non_std_span3 (void) __attribute((malloc_span)); // expected-warning {{attribute only applies to return values that are span-like structures}}

0 commit comments

Comments
 (0)