Skip to content

Commit f7c3e7b

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 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. Introduce a new "malloc_span" attribute that exhibits similar semantics, but can be applied 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. An alternative approach would be to relax the restrictions for 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 that would now need to check for the specific Clang version while the presence of a new attribute is straightforward to check via __has_attribute. Another concern would have been the compatibility between "malloc" semantics between Clang and GCC, which is not the case for a new separate "malloc_span" attribute. 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 f7c3e7b

File tree

9 files changed

+112
-1
lines changed

9 files changed

+112
-1
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. It has the `malloc` semantics, but must be applied
331+
not to functions returning pointers, but to functions returning span-like structures (i.e. those
332+
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5247,6 +5247,20 @@ 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`` marks that a function acts like a system memory allocation
5255+
function returning a span-like structure does not alias storage from any other
5256+
object accessible to the caller.
5257+
5258+
In this context, a span-like structure is assumed to have a pointer as its first
5259+
field and an integer with the size of the actually allocated memory as the
5260+
second field.
5261+
}];
5262+
}
5263+
52505264
def ReturnsNonNullDocs : Documentation {
52515265
let Category = NullabilityDocs;
52525266
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/CodeGen/CGExpr.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6642,7 +6642,8 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
66426642
CalleeDecl);
66436643
}
66446644
if (CalleeDecl->hasAttr<RestrictAttr>() ||
6645-
CalleeDecl->hasAttr<AllocSizeAttr>()) {
6645+
CalleeDecl->hasAttr<AllocSizeAttr>() ||
6646+
CalleeDecl->hasAttr<MallocSpanAttr>()) {
66466647
// Function has 'malloc' (aka. 'restrict') or 'alloc_size' attribute.
66476648
if (SanOpts.has(SanitizerKind::AllocToken)) {
66486649
// Set !alloc_token metadata.

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,41 @@ 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.
1845+
// This matches the common implementation of std::span or sized_allocation_t
1846+
// in P0901R11.
1847+
// Note that there may also be numerous cases of pointer+integer structures
1848+
// not actually exhibiting a std::span-like semantics, so sometimes
1849+
// this heuristic expectedly leads to false positive results.
1850+
const RecordDecl *RD = Ty->getAsRecordDecl();
1851+
if (!RD || RD->isUnion())
1852+
return false;
1853+
const RecordDecl *Def = RD->getDefinition();
1854+
if (!Def)
1855+
return false; // This is an incomplete type.
1856+
auto FieldsBegin = Def->field_begin();
1857+
if (std::distance(FieldsBegin, Def->field_end()) != 2)
1858+
return false;
1859+
const FieldDecl *FirstField = *FieldsBegin;
1860+
const FieldDecl *SecondField = *std::next(FieldsBegin);
1861+
return FirstField->getType()->isAnyPointerType() &&
1862+
SecondField->getType()->isIntegerType();
1863+
}
1864+
1865+
static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
1866+
QualType ResultType = getFunctionOrMethodResultType(D);
1867+
if (!isSpanLikeType(ResultType)) {
1868+
S.Diag(AL.getLoc(), diag::warn_attribute_return_span_only)
1869+
<< AL << getFunctionOrMethodResultSourceRange(D);
1870+
return;
1871+
}
1872+
// TODO: do we need a special check for the number of args to be 0?
1873+
// Or is it auto-generated?
1874+
D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL));
1875+
}
1876+
18421877
static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
18431878
// Ensure we don't combine these with themselves, since that causes some
18441879
// confusing behavior.
@@ -7278,6 +7313,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
72787313
case ParsedAttr::AT_Restrict:
72797314
handleRestrictAttr(S, D, AL);
72807315
break;
7316+
case ParsedAttr::AT_MallocSpan:
7317+
handleMallocSpanAttr(S, D, AL);
7318+
break;
72817319
case ParsedAttr::AT_Mode:
72827320
handleModeAttr(S, D, AL);
72837321
break;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s
2+
3+
int *Mem;
4+
5+
typedef struct {
6+
void *p;
7+
int size;
8+
} sized_ptr;
9+
10+
// It should not set the no alias attribute (for now).
11+
__attribute__((malloc_span)) sized_ptr MallocSpan(){ return (sized_ptr){ .p = Mem };}
12+
// CHECK: define dso_local { ptr, i32 } @MallocSpan

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)