Skip to content

Commit bfb8b0b

Browse files
committed
[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
1 parent 624d4f6 commit bfb8b0b

File tree

7 files changed

+68
-4
lines changed

7 files changed

+68
-4
lines changed

clang/include/clang/AST/TypeBase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,6 +2601,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
26012601
bool isFunctionProtoType() const { return getAs<FunctionProtoType>(); }
26022602
bool isPointerType() const;
26032603
bool isPointerOrReferenceType() const;
2604+
bool isSpanLikeType() const;
26042605
bool isSignableType(const ASTContext &Ctx) const;
26052606
bool isSignablePointerType() const;
26062607
bool isSignableIntegerType(const ASTContext &Ctx) const;

clang/lib/AST/Type.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5753,3 +5753,23 @@ StringRef PredefinedSugarType::getName(Kind KD) {
57535753
}
57545754
llvm_unreachable("unexpected kind");
57555755
}
5756+
5757+
bool Type::isSpanLikeType() const {
5758+
// Check that the type is a plain record with the first field being a pointer
5759+
// type and the second field being an integer.
5760+
// This matches the common implementation of std::span or sized_allocation_t
5761+
// in P0901R11.
5762+
const RecordDecl *RD = getAsRecordDecl();
5763+
if (!RD || RD->isUnion())
5764+
return false;
5765+
const RecordDecl *Def = RD->getDefinition();
5766+
if (!Def)
5767+
return false; // This is an incomplete type.
5768+
auto FieldsBegin = Def->field_begin();
5769+
if (std::distance(FieldsBegin, Def->field_end()) != 2)
5770+
return false;
5771+
const FieldDecl *FirstField = *FieldsBegin;
5772+
const FieldDecl *SecondField = *std::next(FieldsBegin);
5773+
return FirstField->getType()->isAnyPointerType() &&
5774+
SecondField->getType()->isIntegerType();
5775+
}

clang/lib/CodeGen/CGCall.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2507,7 +2507,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
25072507
FuncAttrs.addAttribute(llvm::Attribute::NoUnwind);
25082508
}
25092509
if (const auto *RA = TargetDecl->getAttr<RestrictAttr>();
2510-
RA && RA->getDeallocator() == nullptr)
2510+
RA && RA->getDeallocator() == nullptr &&
2511+
FI.getReturnType()->getAs<PointerType>())
25112512
RetAttrs.addAttribute(llvm::Attribute::NoAlias);
25122513
if (TargetDecl->hasAttr<ReturnsNonNullAttr>() &&
25132514
!CodeGenOpts.NullPointerIsValid)

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ static void handleAllocSizeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
571571
assert(isFuncOrMethodForAttrSubject(D) && hasFunctionProto(D));
572572

573573
QualType RetTy = getFunctionOrMethodResultType(D);
574-
if (!RetTy->isPointerType()) {
574+
if (!RetTy->isPointerType() && !RetTy->isSpanLikeType()) {
575575
S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only) << AL;
576576
return;
577577
}
@@ -1750,7 +1750,8 @@ static void handleTLSModelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
17501750

17511751
static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
17521752
QualType ResultType = getFunctionOrMethodResultType(D);
1753-
if (!ResultType->isAnyPointerType() && !ResultType->isBlockPointerType()) {
1753+
if (!ResultType->isAnyPointerType() && !ResultType->isBlockPointerType() &&
1754+
!ResultType->isSpanLikeType()) {
17541755
S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
17551756
<< AL << getFunctionOrMethodResultSourceRange(D);
17561757
return;

clang/test/CodeGen/attr-malloc.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
int *Mem;
44
void dealloc(int*);
55

6+
typedef struct {
7+
void *p;
8+
int size;
9+
} sized_ptr;
10+
611
__attribute__((malloc)) int *MallocFunc(){ return Mem;}
712
// CHECK: define[[BEFORE:.*]] noalias[[AFTER:.*]]@MallocFunc
8-
// Ensure these two do not generate noalias here.
13+
// Ensure these three do not generate noalias here.
914
__attribute__((malloc(dealloc))) int *MallocFunc2(){ return Mem;}
1015
// CHECK: define[[BEFORE]][[AFTER]]@MallocFunc2
1116
__attribute__((malloc(dealloc, 1))) int *MallocFunc3(){ return Mem;}
1217
// CHECK: define[[BEFORE]][[AFTER]]@MallocFunc3
18+
__attribute__((malloc)) sized_ptr MallocFunc4(){ return (sized_ptr){ .p = Mem };}
19+
// CHECK: define[[BEFORE]] { ptr, i32 } @MallocFunc4

clang/test/Sema/alloc-size.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ void *KR() __attribute__((alloc_size(1))); //expected-warning{{'alloc_size' attr
3030
void *(__attribute__((alloc_size(1))) * func_ptr1)(int);
3131
void *(__attribute__((alloc_size(1, 2))) func_ptr2)(int, int);
3232

33+
// Applying alloc_size to functions returning a struct with a pointer as a first field should work.
34+
typedef struct {
35+
void* p;
36+
int n;
37+
} sized_ptr;
38+
39+
sized_ptr sized_ptr_alloc(int len) __attribute__((alloc_size(1)));
40+
3341
// TODO: according to GCC documentation the following should actually be the type
3442
// “pointer to pointer to alloc_size attributed function returning void*” and should
3543
// therefore be supported

clang/test/Sema/attr-malloc.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,32 @@ int returns_int (void) __attribute((malloc)); // expected-warning {{attribut
1313
int * returns_intptr(void) __attribute((malloc)); // no-warning
1414
typedef int * iptr;
1515
iptr returns_iptr (void) __attribute((malloc)); // no-warning
16+
typedef struct {
17+
void *ptr;
18+
size_t n;
19+
} sized_ptr;
20+
sized_ptr returns_sized_ptr (void) __attribute((malloc)); // no-warning
21+
22+
// The first struct field must be pointer and the second must be an integer.
23+
// Check the possible ways to violate it.
24+
typedef struct {
25+
size_t n;
26+
void *ptr;
27+
} invalid_span1;
28+
invalid_span1 returns_non_std_span1 (void) __attribute((malloc)); // expected-warning {{attribute only applies to return values that are pointers}}
29+
30+
typedef struct {
31+
void *ptr;
32+
void *ptr2;
33+
} invalid_span2;
34+
invalid_span2 returns_non_std_span2 (void) __attribute((malloc)); // expected-warning {{attribute only applies to return values that are pointers}}
35+
36+
typedef struct {
37+
void *ptr;
38+
size_t n;
39+
size_t n2;
40+
} invalid_span3;
41+
invalid_span3 returns_non_std_span3 (void) __attribute((malloc)); // expected-warning {{attribute only applies to return values that are pointers}}
1642

1743
__attribute((malloc)) void *(*f)(void); // expected-warning{{attribute only applies to functions}}
1844
__attribute((malloc)) int (*g)(void); // expected-warning{{attribute only applies to functions}}

0 commit comments

Comments
 (0)