Skip to content

Commit e465c25

Browse files
author
Gabor Horvath
committed
[Clang] Permit noescape on non-pointer types
In modern C++ we often use span, string_view or other view objects instead of raw pointers. This PR opens the door to mark those noescape. This can be useful to document the API contracts, for interop with memory safe languages like Swift or Rust, and possibly in the future to implement take this into account in codegen. The PR also adds a feature test macro. The goal is to help avoiding warnings when the code is compiler by earlier versions of clang that does not permit this attribute on non-pointer types. rdar://140196481
1 parent 9f6c3d7 commit e465c25

File tree

14 files changed

+87
-46
lines changed

14 files changed

+87
-46
lines changed

clang/include/clang/AST/Attr.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB,
380380
DB.AddTaggedVal(reinterpret_cast<uint64_t>(At), DiagnosticsEngine::ak_attr);
381381
return DB;
382382
}
383+
384+
/// Determine if type T is a valid subject for a nonnull and similar
385+
/// attributes. Dependent types are considered valid so they can be checked
386+
/// during instantiation time. By default, we look through references (the
387+
/// behavior used by nonnull), but if the second parameter is true, then we
388+
/// treat a reference type as valid.
389+
bool isValidPointerAttrType(QualType T, bool RefOkay = false);
383390
} // end namespace clang
384391

385392
#endif

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3355,6 +3355,9 @@ def warn_attribute_return_pointers_refs_only : Warning<
33553355
def warn_attribute_pointer_or_reference_only : Warning<
33563356
"%0 attribute only applies to a pointer or reference (%1 is invalid)">,
33573357
InGroup<IgnoredAttributes>;
3358+
def warn_attribute_pointer_or_reference_or_record_only : Warning<
3359+
"%0 attribute only applies to a pointer, reference, class, struct, or union (%1 is invalid)">,
3360+
InGroup<IgnoredAttributes>;
33583361
def err_attribute_no_member_pointers : Error<
33593362
"%0 attribute cannot be used with pointers to members">;
33603363
def err_attribute_invalid_implicit_this_argument : Error<

clang/include/clang/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ FEATURE(attribute_overloadable, true)
8686
FEATURE(attribute_unavailable_with_message, true)
8787
FEATURE(attribute_unused_on_fields, true)
8888
FEATURE(attribute_diagnose_if_objc, true)
89+
FEATURE(attribute_noescape_nonpointer, true)
8990
FEATURE(blocks, LangOpts.Blocks)
9091
FEATURE(c_thread_safety_attributes, true)
9192
FEATURE(cxx_exceptions, LangOpts.CXXExceptions)

clang/include/clang/Sema/Sema.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4663,12 +4663,6 @@ class Sema final : public SemaBase {
46634663
StringRef &Str,
46644664
SourceLocation *ArgLocation = nullptr);
46654665

4666-
/// Determine if type T is a valid subject for a nonnull and similar
4667-
/// attributes. By default, we look through references (the behavior used by
4668-
/// nonnull), but if the second parameter is true, then we treat a reference
4669-
/// type as valid.
4670-
bool isValidPointerAttrType(QualType T, bool RefOkay = false);
4671-
46724666
/// AddAssumeAlignedAttr - Adds an assume_aligned attribute to a particular
46734667
/// declaration.
46744668
void AddAssumeAlignedAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,

clang/lib/AST/AttrImpl.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,30 @@ unsigned AlignedAttr::getAlignment(ASTContext &Ctx) const {
270270
return Ctx.getTargetDefaultAlignForAttributeAligned();
271271
}
272272

273+
bool clang::isValidPointerAttrType(QualType T, bool RefOkay) {
274+
if (T->isDependentType())
275+
return true;
276+
if (RefOkay) {
277+
if (T->isReferenceType())
278+
return true;
279+
} else {
280+
T = T.getNonReferenceType();
281+
}
282+
283+
// The nonnull attribute, and other similar attributes, can be applied to a
284+
// transparent union that contains a pointer type.
285+
if (const RecordType *UT = T->getAsUnionType()) {
286+
if (UT && UT->getDecl()->hasAttr<TransparentUnionAttr>()) {
287+
RecordDecl *UD = UT->getDecl();
288+
for (const auto *I : UD->fields()) {
289+
QualType QT = I->getType();
290+
if (QT->isAnyPointerType() || QT->isBlockPointerType())
291+
return true;
292+
}
293+
}
294+
}
295+
296+
return T->isAnyPointerType() || T->isBlockPointerType();
297+
}
298+
273299
#include "clang/AST/AttrImpl.inc"

clang/lib/CodeGen/CGCall.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2846,7 +2846,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
28462846
break;
28472847
}
28482848

2849-
if (FI.getExtParameterInfo(ArgNo).isNoEscape())
2849+
if (FI.getExtParameterInfo(ArgNo).isNoEscape() &&
2850+
isValidPointerAttrType(ParamType, /*RefOkay=*/true))
28502851
Attrs.addAttribute(llvm::Attribute::NoCapture);
28512852

28522853
if (Attrs.hasAttributes()) {

clang/lib/Sema/SemaChecking.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3045,7 +3045,7 @@ static void CheckNonNullArguments(Sema &S,
30453045
if (!NonNull->args_size()) {
30463046
// Easy case: all pointer arguments are nonnull.
30473047
for (const auto *Arg : Args)
3048-
if (S.isValidPointerAttrType(Arg->getType()))
3048+
if (isValidPointerAttrType(Arg->getType()))
30493049
CheckNonNullArgument(S, Arg, CallSiteLoc);
30503050
return;
30513051
}

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,35 +1220,11 @@ static void handlePreferredName(Sema &S, Decl *D, const ParsedAttr &AL) {
12201220
<< TT->getDecl();
12211221
}
12221222

1223-
bool Sema::isValidPointerAttrType(QualType T, bool RefOkay) {
1224-
if (RefOkay) {
1225-
if (T->isReferenceType())
1226-
return true;
1227-
} else {
1228-
T = T.getNonReferenceType();
1229-
}
1230-
1231-
// The nonnull attribute, and other similar attributes, can be applied to a
1232-
// transparent union that contains a pointer type.
1233-
if (const RecordType *UT = T->getAsUnionType()) {
1234-
if (UT && UT->getDecl()->hasAttr<TransparentUnionAttr>()) {
1235-
RecordDecl *UD = UT->getDecl();
1236-
for (const auto *I : UD->fields()) {
1237-
QualType QT = I->getType();
1238-
if (QT->isAnyPointerType() || QT->isBlockPointerType())
1239-
return true;
1240-
}
1241-
}
1242-
}
1243-
1244-
return T->isAnyPointerType() || T->isBlockPointerType();
1245-
}
1246-
12471223
static bool attrNonNullArgCheck(Sema &S, QualType T, const ParsedAttr &AL,
12481224
SourceRange AttrParmRange,
12491225
SourceRange TypeRange,
12501226
bool isReturnValue = false) {
1251-
if (!S.isValidPointerAttrType(T)) {
1227+
if (!isValidPointerAttrType(T)) {
12521228
if (isReturnValue)
12531229
S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
12541230
<< AL << AttrParmRange << TypeRange;
@@ -1296,7 +1272,7 @@ static void handleNonNullAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
12961272
for (unsigned I = 0, E = getFunctionOrMethodNumParams(D);
12971273
I != E && !AnyPointers; ++I) {
12981274
QualType T = getFunctionOrMethodParamType(D, I);
1299-
if (T->isDependentType() || S.isValidPointerAttrType(T)) {
1275+
if (T->isDependentType() || isValidPointerAttrType(T)) {
13001276
AnyPointers = true;
13011277
/*TO_UPSTREAM(BoundsSafety) ON*/
13021278
if (auto DCPTy = T->getAs<CountAttributedType>()) {
@@ -1355,10 +1331,11 @@ static void handleNoEscapeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
13551331
if (D->isInvalidDecl())
13561332
return;
13571333

1358-
// noescape only applies to pointer types.
1334+
// noescape only applies to pointer and record types.
13591335
QualType T = cast<ParmVarDecl>(D)->getType();
1360-
if (!S.isValidPointerAttrType(T, /* RefOkay */ true)) {
1361-
S.Diag(AL.getLoc(), diag::warn_attribute_pointers_only)
1336+
if (!isValidPointerAttrType(T, /* RefOkay */ true) && !T->isRecordType()) {
1337+
S.Diag(AL.getLoc(),
1338+
diag::warn_attribute_pointer_or_reference_or_record_only)
13621339
<< AL << AL.getRange() << 0;
13631340
return;
13641341
}

clang/test/AST/ast-dump-attr.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,14 @@ __attribute__((external_source_symbol(generated_declaration, defined_in="module"
230230
// CHECK-NEXT: ExternalSourceSymbolAttr{{.*}} "Swift" "module" GeneratedDeclaration "testUSR"
231231

232232
namespace TestNoEscape {
233+
struct S { int *p; };
233234
void noescapeFunc(int *p0, __attribute__((noescape)) int *p1) {}
234-
// CHECK: `-FunctionDecl{{.*}} noescapeFunc 'void (int *, __attribute__((noescape)) int *)'
235+
// CHECK: |-FunctionDecl{{.*}} noescapeFunc 'void (int *, __attribute__((noescape)) int *)'
236+
// CHECK-NEXT: ParmVarDecl
237+
// CHECK-NEXT: ParmVarDecl
238+
// CHECK-NEXT: NoEscapeAttr
239+
void noescapeFunc2(int *p0, __attribute__((noescape)) S p1) {}
240+
// CHECK: `-FunctionDecl{{.*}} noescapeFunc2 'void (int *, __attribute__((noescape)) S)'
235241
// CHECK-NEXT: ParmVarDecl
236242
// CHECK-NEXT: ParmVarDecl
237243
// CHECK-NEXT: NoEscapeAttr

clang/test/CodeGenCXX/noescape.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ struct S {
66
S &operator=(int * __attribute__((noescape)));
77
void m0(int *, int * __attribute__((noescape)));
88
virtual void vm1(int *, int * __attribute__((noescape)));
9+
virtual void vm2(int *, S __attribute__((noescape)));
910
};
1011

1112
// CHECK: define{{.*}} void @_ZN1SC2EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} nocapture noundef {{%.*}})
@@ -23,6 +24,9 @@ void S::m0(int *, int * __attribute__((noescape))) {}
2324
// CHECK: define{{.*}} void @_ZN1S3vm1EPiS0_(ptr {{.*}}, {{.*}} nocapture noundef {{%.*}})
2425
void S::vm1(int *, int * __attribute__((noescape))) {}
2526

27+
// CHECK-NOT: nocapture
28+
void S::vm2(int *, S __attribute__((noescape))) {}
29+
2630
// CHECK-LABEL: define{{.*}} void @_Z5test0P1SPiS1_(
2731
// CHECK: call void @_ZN1SC1EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} nocapture noundef {{.*}})
2832
// CHECK: call {{.*}} ptr @_ZN1SaSEPi(ptr {{.*}}, {{.*}} nocapture noundef {{.*}})

clang/test/CodeGenObjC/noescape.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@
88
long long *ll;
99
} __attribute__((transparent_union));
1010

11+
struct S {
12+
int *p;
13+
};
14+
1115
void escapingFunc0(BlockTy);
1216
void noescapeFunc0(id, __attribute__((noescape)) BlockTy);
1317
void noescapeFunc1(__attribute__((noescape)) int *);
1418
void noescapeFunc2(__attribute__((noescape)) id);
1519
void noescapeFunc3(__attribute__((noescape)) union U);
20+
void noescapeFunc4(__attribute__((noescape)) struct S s);
1621

1722
// Block descriptors of non-escaping blocks don't need pointers to copy/dispose
1823
// helper functions.
@@ -53,6 +58,11 @@ void test3(union U u) {
5358
noescapeFunc3(u);
5459
}
5560

61+
// CHECK-NOT: nocapture
62+
void testNonPtr(struct S s) {
63+
noescapeFunc4(s);
64+
}
65+
5666
// CHECK: define internal void @"\01-[C0 m0:]"({{.*}}, {{.*}}, {{.*}} nocapture {{.*}})
5767

5868
// CHECK-LABEL: define{{.*}} void @test4(

clang/test/Sema/attr-noescape.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ int *global_var __attribute((noescape)); // expected-warning{{'noescape' attribu
88

99
void foo(__attribute__((noescape)) int *int_ptr,
1010
__attribute__((noescape)) int (^block)(int),
11-
__attribute((noescape)) int integer) { // expected-warning{{'noescape' attribute only applies to pointer arguments}}
11+
__attribute((noescape)) int integer) { // expected-warning{{'noescape' attribute only applies to a pointer, reference, class, struct, or union (0 is invalid)}}
1212
}

clang/test/SemaCXX/noescape-attr.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
3+
template<typename T>
4+
void test1(T __attribute__((noescape)) arr, int size);
5+
6+
void test2(int __attribute__((noescape)) a, int b); // expected-warning {{'noescape' attribute only applies to a pointer, reference, class, struct, or union (0 is invalid)}}
7+
8+
struct S { int *p; };
9+
void test3(S __attribute__((noescape)) s);
10+
11+
#if !__has_feature(attribute_noescape_nonpointer)
12+
#error "attribute_noescape_nonpointer should be supported"
13+
#endif

clang/test/SemaObjCXX/noescape.mm

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717
void noescapeFunc2(int *); // expected-error {{conflicting types for 'noescapeFunc2'}}
1818

1919
template <class T>
20-
void noescapeFunc5(__attribute__((noescape)) T); // expected-warning {{'noescape' attribute only applies to pointer arguments}}
20+
void noescapeFunc5(__attribute__((noescape)) T);
2121
template <class T>
2222
void noescapeFunc6(__attribute__((noescape)) const T &);
2323
template <class T>
2424
void noescapeFunc7(__attribute__((noescape)) T &&);
2525

26-
void invalidFunc0(int __attribute__((noescape))); // expected-warning {{'noescape' attribute only applies to pointer arguments}}
26+
void invalidFunc0(int __attribute__((noescape))); // expected-warning {{'noescape' attribute only applies to a pointer, reference, class, struct, or union (0 is invalid)}}
2727
void invalidFunc1(int __attribute__((noescape(0)))); // expected-error {{'noescape' attribute takes no arguments}}
2828
void invalidFunc2(int0 *__attribute__((noescape))); // expected-error {{use of undeclared identifier 'int0'; did you mean 'int'?}}
29-
void invalidFunc3(__attribute__((noescape)) int (S::*Ty)); // expected-warning {{'noescape' attribute only applies to pointer arguments}}
30-
void invalidFunc4(__attribute__((noescape)) void (S::*Ty)()); // expected-warning {{'noescape' attribute only applies to pointer arguments}}
29+
void invalidFunc3(__attribute__((noescape)) int (S::*Ty)); // expected-warning {{'noescape' attribute only applies to a pointer, reference, class, struct, or union (0 is invalid)}}
30+
void invalidFunc4(__attribute__((noescape)) void (S::*Ty)()); // expected-warning {{'noescape' attribute only applies to a pointer, reference, class, struct, or union (0 is invalid)}}
3131
int __attribute__((noescape)) g; // expected-warning {{'noescape' attribute only applies to parameters}}
3232

3333
struct S1 {
@@ -144,7 +144,7 @@ -(void) m1:(int*) p {
144144

145145
struct S6 {
146146
S6();
147-
S6(const S6 &) = delete; // expected-note 12 {{'S6' has been explicitly marked deleted here}}
147+
S6(const S6 &) = delete; // expected-note 11 {{'S6' has been explicitly marked deleted here}}
148148
int f;
149149
};
150150

@@ -161,7 +161,7 @@ void test1(C0 *c0) {
161161
__block S6 b6; // expected-error {{call to deleted constructor of 'S6'}}
162162
__block S6 b7; // expected-error {{call to deleted constructor of 'S6'}}
163163
__block S6 b8; // expected-error {{call to deleted constructor of 'S6'}}
164-
__block S6 b9; // expected-error {{call to deleted constructor of 'S6'}}
164+
__block S6 b9;
165165
__block S6 b10; // expected-error {{call to deleted constructor of 'S6'}}
166166
__block S6 b11; // expected-error {{call to deleted constructor of 'S6'}}
167167
__block S6 b12;
@@ -199,7 +199,6 @@ void test1(C0 *c0) {
199199
(void)b8;
200200
});
201201

202-
// FIXME: clang shouldn't reject this.
203202
noescapeFunc5(^{
204203
(void)b9;
205204
});

0 commit comments

Comments
 (0)