Skip to content

Commit eb65517

Browse files
authored
[Clang] Introduce malloc_span attribute (#167010)
The "malloc" attribute restricts the possible function signatures to the ones returning a pointer, which is not the case for some non-standard allocation function variants. For example, P0901R11 proposed ::operator new overloads that return a return_size_t result - a struct that contains a pointer to the allocated memory as well as the actual size of the allocated memory. Another example is __size_returning_new. Introduce a new "malloc_span" attribute that exhibits similar semantics, but applies to functions returning records where one member is a pointer (assumed to point to the allocated memory) and another is an integer (assumed to be the size of the allocated memory). This is the case for return_size_t as well as std::span, should it be returned from such an annotated function. An alternative approach would be to relax the restrictions of the existing "malloc" attribute to be applied to both functions returning pointers and functions returning span-like structs. However, it would complicate the user-space code by requiring specific Clang version checks. In contrast, the presence of a new attribute can be straightforwardly verified via the __has_attribute macro. Introducing a new attribute also avoids concerns about the potential incompatibility with GCC's "malloc" semantics. In future commits, codegen can be improved to recognize the noalias-ness of the pointer returned inside a span-like struct. This change helps unlock the alloc token instrumentation for such non-standard allocation functions: https://clang.llvm.org/docs/AllocToken.html#instrumenting-non-standard-allocation-functions
1 parent 2f6f045 commit eb65517

File tree

10 files changed

+345
-0
lines changed

10 files changed

+345
-0
lines changed

clang/docs/ReleaseNotes.rst

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

356+
- New function attribute `malloc_span` is added. It has semantics similar to that of the `malloc`
357+
attribute, but `malloc_span` applies not to functions returning pointers, but to functions returning
358+
span-like structures (i.e. those that contain a pointer field and a size integer field or two pointers).
359+
356360
Improvements to Clang's diagnostics
357361
-----------------------------------
358362
- 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
@@ -2072,6 +2072,12 @@ def Restrict : InheritableAttr {
20722072
let Documentation = [RestrictDocs];
20732073
}
20742074

2075+
def MallocSpan : InheritableAttr {
2076+
let Spellings = [Clang<"malloc_span">];
2077+
let Subjects = SubjectList<[Function]>;
2078+
let Documentation = [MallocSpanDocs];
2079+
}
2080+
20752081
def LayoutVersion : InheritableAttr, TargetSpecificAttr<TargetMicrosoftRecordLayout> {
20762082
let Spellings = [Declspec<"layout_version">];
20772083
let Args = [UnsignedArgument<"Version">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5247,6 +5247,23 @@ 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 two non-static data
5260+
members, one of which is a pointer to the start of the allocated memory and
5261+
the other one is either an integer type containing the size of the actually
5262+
allocated memory or a pointer to the end of the allocated region. Note, static
5263+
data members do not impact whether a type is span-like or not.
5264+
}];
5265+
}
5266+
52505267
def ReturnsNonNullDocs : Documentation {
52515268
let Category = NullabilityDocs;
52525269
let Content = [{

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3449,6 +3449,24 @@ 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 functions that return span-like "
3454+
"structures">,
3455+
InGroup<IgnoredAttributes>;
3456+
def note_returned_not_struct : Note<"returned type is not a struct type">;
3457+
def note_returned_incomplete_type : Note<"returned type is incomplete">;
3458+
def note_returned_not_two_field_struct
3459+
: Note<"returned struct has %0 fields, expected 2">;
3460+
def note_returned_not_span_struct
3461+
: Note<"returned struct fields are not a supported combination for a "
3462+
"span-like type (expected pointer/integer or pointer/pointer)">;
3463+
def note_returned_not_integer_field
3464+
: Note<"%ordinal0 field is expected to be an integer">;
3465+
def note_returned_not_wide_enough_field
3466+
: Note<"%ordinal0 field of span-like type is not a wide enough integer "
3467+
"(minimum width: %1)">;
3468+
def note_type_inherits_from_base
3469+
: Note<"returned type inherits from a base class">;
34523470
def warn_attribute_return_pointers_refs_only : Warning<
34533471
"%0 attribute only applies to return values that are pointers or references">,
34543472
InGroup<IgnoredAttributes>;

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5111,6 +5111,11 @@ class Sema final : public SemaBase {
51115111
/// Essentially, this just moves them to the current pool.
51125112
void redelayDiagnostics(sema::DelayedDiagnosticPool &pool);
51135113

5114+
/// Check that the type is a plain record with one field being a pointer
5115+
/// type and the other field being an integer. This matches the common
5116+
/// implementation of std::span or sized_allocation_t in P0901R11.
5117+
bool CheckSpanLikeType(const AttributeCommonInfo &CI, const QualType &Ty);
5118+
51145119
/// Check if IdxExpr is a valid parameter index for a function or
51155120
/// instance method D. May output an error.
51165121
///

clang/lib/Sema/SemaDeclAttr.cpp

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

1842+
bool Sema::CheckSpanLikeType(const AttributeCommonInfo &CI,
1843+
const QualType &Ty) {
1844+
// Note that there may also be numerous cases of pointer + integer /
1845+
// pointer + pointer / integer + pointer structures not actually exhibiting
1846+
// a span-like semantics, so sometimes these heuristics expectedly
1847+
// lead to false positive results.
1848+
auto emitWarning = [this, &CI](unsigned NoteDiagID) {
1849+
Diag(CI.getLoc(), diag::warn_attribute_return_span_only) << CI;
1850+
return Diag(CI.getLoc(), NoteDiagID);
1851+
};
1852+
if (Ty->isDependentType())
1853+
return false;
1854+
// isCompleteType is used to force template class instantiation.
1855+
if (!isCompleteType(CI.getLoc(), Ty))
1856+
return emitWarning(diag::note_returned_incomplete_type);
1857+
const RecordDecl *RD = Ty->getAsRecordDecl();
1858+
if (!RD || RD->isUnion())
1859+
return emitWarning(diag::note_returned_not_struct);
1860+
if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
1861+
if (CXXRD->getNumBases() > 0) {
1862+
return emitWarning(diag::note_type_inherits_from_base);
1863+
}
1864+
}
1865+
auto FieldsBegin = RD->field_begin();
1866+
auto FieldsCount = std::distance(FieldsBegin, RD->field_end());
1867+
if (FieldsCount != 2)
1868+
return emitWarning(diag::note_returned_not_two_field_struct) << FieldsCount;
1869+
QualType FirstFieldType = FieldsBegin->getType();
1870+
QualType SecondFieldType = std::next(FieldsBegin)->getType();
1871+
auto validatePointerType = [](const QualType &T) {
1872+
// It must not point to functions.
1873+
return T->isPointerType() && !T->isFunctionPointerType();
1874+
};
1875+
auto checkIntegerType = [this, emitWarning](const QualType &T,
1876+
const int FieldNo) -> bool {
1877+
const auto *BT = dyn_cast<BuiltinType>(T.getCanonicalType());
1878+
if (!BT || !BT->isInteger())
1879+
return emitWarning(diag::note_returned_not_integer_field) << FieldNo;
1880+
auto IntSize = Context.getTypeSize(Context.IntTy);
1881+
if (Context.getTypeSize(BT) < IntSize)
1882+
return emitWarning(diag::note_returned_not_wide_enough_field)
1883+
<< FieldNo << IntSize;
1884+
return false;
1885+
};
1886+
if (validatePointerType(FirstFieldType) &&
1887+
validatePointerType(SecondFieldType)) {
1888+
// Pointer + pointer.
1889+
return false;
1890+
} else if (validatePointerType(FirstFieldType)) {
1891+
// Pointer + integer?
1892+
return checkIntegerType(SecondFieldType, 2);
1893+
} else if (validatePointerType(SecondFieldType)) {
1894+
// Integer + pointer?
1895+
return checkIntegerType(FirstFieldType, 1);
1896+
}
1897+
return emitWarning(diag::note_returned_not_span_struct);
1898+
}
1899+
1900+
static void handleMallocSpanAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
1901+
QualType ResultType = getFunctionOrMethodResultType(D);
1902+
if (!S.CheckSpanLikeType(AL, ResultType))
1903+
D->addAttr(::new (S.Context) MallocSpanAttr(S.Context, AL));
1904+
}
1905+
18421906
static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
18431907
// Ensure we don't combine these with themselves, since that causes some
18441908
// confusing behavior.
@@ -7276,6 +7340,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
72767340
case ParsedAttr::AT_Restrict:
72777341
handleRestrictAttr(S, D, AL);
72787342
break;
7343+
case ParsedAttr::AT_MallocSpan:
7344+
handleMallocSpanAttr(S, D, AL);
7345+
break;
72797346
case ParsedAttr::AT_Mode:
72807347
handleModeAttr(S, D, AL);
72817348
break;

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,14 @@ static void instantiateDependentHLSLParamModifierAttr(
796796
"out or inout parameter type must be a reference and restrict qualified");
797797
}
798798

799+
static void instantiateDependentMallocSpanAttr(Sema &S,
800+
const MallocSpanAttr *Attr,
801+
Decl *New) {
802+
QualType RT = getFunctionOrMethodResultType(New);
803+
if (!S.CheckSpanLikeType(*Attr, RT))
804+
New->addAttr(Attr->clone(S.getASTContext()));
805+
}
806+
799807
void Sema::InstantiateAttrsForDecl(
800808
const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl,
801809
Decl *New, LateInstantiatedAttrVec *LateAttrs,
@@ -1007,6 +1015,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
10071015
continue;
10081016
}
10091017

1018+
if (auto *A = dyn_cast<MallocSpanAttr>(TmplAttr)) {
1019+
instantiateDependentMallocSpanAttr(*this, A, New);
1020+
continue;
1021+
}
1022+
10101023
if (auto *A = dyn_cast<CleanupAttr>(TmplAttr)) {
10111024
if (!New->hasAttr<CleanupAttr>()) {
10121025
auto *NewAttr = A->clone(Context);

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: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// RUN: %clang_cc1 -verify -fsyntax-only %s
2+
3+
typedef __SIZE_TYPE__ size_t;
4+
5+
typedef struct {
6+
void *ptr;
7+
size_t n;
8+
} span;
9+
span returns_span (void) __attribute((malloc_span)); // no-warning
10+
11+
typedef struct {
12+
size_t n;
13+
void *ptr;
14+
} span2;
15+
span2 returns_span2 (void) __attribute((malloc_span)); // no-warning
16+
17+
typedef struct {
18+
void *ptr;
19+
void *ptr2;
20+
} span3;
21+
span3 returns_span3 (void) __attribute((malloc_span)); // no-warning
22+
23+
typedef struct {
24+
void *ptr;
25+
int n;
26+
} span4;
27+
span4 returns_span4 (void) __attribute((malloc_span)); // no-warning
28+
29+
typedef struct incomplete_span incomplete_span;
30+
// expected-warning@+2 {{attribute only applies to functions that return span-like structures}}
31+
// expected-note@+1 {{returned type is incomplete}}
32+
incomplete_span returns_incomplete_span (void) __attribute((malloc_span));
33+
34+
// expected-warning@+2 {{attribute only applies to functions that return span-like structures}}
35+
// expected-note@+1 {{returned type is not a struct type}}
36+
int *returns_int_ptr (void) __attribute((malloc_span));
37+
38+
typedef struct {
39+
void *ptr;
40+
size_t n;
41+
size_t n2;
42+
} too_long_span;
43+
// expected-warning@+2 {{attribute only applies to functions that return span-like structures}}
44+
// expected-note@+1 {{returned struct has 3 fields, expected 2}}
45+
too_long_span returns_too_long_span (void) __attribute((malloc_span));
46+
47+
// Function pointers are not allowed.
48+
typedef struct {
49+
int (*func_ptr)(void);
50+
size_t n;
51+
} func_span;
52+
// expected-warning@+2 {{attribute only applies to functions that return span-like structures}}
53+
// expected-note@+1 {{returned struct fields are not a supported combination}}
54+
func_span returns_func_span (void) __attribute((malloc_span));
55+
56+
// Integer should not be an enum.
57+
enum some_enum { some_value, other_value };
58+
typedef struct {
59+
void *ptr;
60+
enum some_enum field;
61+
} enum_span;
62+
// expected-warning@+2 {{attribute only applies to functions that return span-like structures}}
63+
// expected-note@+1 {{2nd field is expected to be an integer}}
64+
enum_span returns_enum_span (void) __attribute((malloc_span));
65+
66+
// Bit integers are also not supported.
67+
typedef struct {
68+
void *ptr;
69+
_BitInt(16) n;
70+
} bit_span;
71+
// expected-warning@+2 {{attribute only applies to functions that return span-like structures}}
72+
// expected-note@+1 {{2nd field is expected to be an integer}}
73+
bit_span returns_bit_span (void) __attribute((malloc_span));
74+
75+
// Integer must be at least as big as int.
76+
typedef struct {
77+
void *ptr;
78+
short n;
79+
} short_span;
80+
// expected-warning@+2 {{attribute only applies to functions that return span-like structures}}
81+
// expected-note@+1 {{2nd field of span-like type is not a wide enough integer (minimum width: 32)}}
82+
short_span returns_short_span (void) __attribute((malloc_span));

0 commit comments

Comments
 (0)