Skip to content

Commit f6bb3c5

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. 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.
1 parent c1dcf75 commit f6bb3c5

File tree

15 files changed

+83
-52
lines changed

15 files changed

+83
-52
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ Attribute Changes in Clang
512512

513513
- The ``target_version`` attribute is now only supported for AArch64 and RISC-V architectures.
514514

515+
- Now ``__attribute__((noescape))`` can be applied to non-pointer types like ``std::span``.
516+
515517
Improvements to Clang's diagnostics
516518
-----------------------------------
517519

clang/include/clang/AST/Attr.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,13 @@ inline ParameterABI ParameterABIAttr::getABI() const {
406406
llvm_unreachable("bad parameter ABI attribute kind");
407407
}
408408
}
409+
410+
/// Determine if type T is a valid subject for a nonnull and similar
411+
/// attributes. Dependent types are considered valid so they can be checked
412+
/// during instantiation time. By default, we look through references (the
413+
/// behavior used by nonnull), but if the second parameter is true, then we
414+
/// treat a reference type as valid.
415+
bool isValidPointerAttrType(QualType T, bool RefOkay = false);
409416
} // end namespace clang
410417

411418
#endif

clang/include/clang/Basic/AttrDocs.td

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,13 @@ members, and static locals.
228228
def NoEscapeDocs : Documentation {
229229
let Category = DocCatVariable;
230230
let Content = [{
231-
``noescape`` placed on a function parameter of a pointer type is used to inform
232-
the compiler that the pointer cannot escape: that is, no reference to the object
233-
the pointer points to that is derived from the parameter value will survive
234-
after the function returns. Users are responsible for making sure parameters
235-
annotated with ``noescape`` do not actually escape. Calling ``free()`` on such
236-
a parameter does not constitute an escape.
231+
``noescape`` placed on a function parameter is used to inform the compiler that
232+
the pointer (or the pointer members in case of a user defined type) cannot
233+
escape: that is, no reference to the object the pointer points to that is
234+
derived from the parameter value will survive after the function returns. Users
235+
are responsible for making sure parameters annotated with ``noescape`` do not
236+
actually escape. Calling ``free()`` on such a parameter does not constitute an
237+
escape.
237238

238239
For example:
239240

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3346,6 +3346,9 @@ def warn_attribute_return_pointers_refs_only : Warning<
33463346
def warn_attribute_pointer_or_reference_only : Warning<
33473347
"%0 attribute only applies to a pointer or reference (%1 is invalid)">,
33483348
InGroup<IgnoredAttributes>;
3349+
def warn_attribute_pointer_or_reference_or_record_only : Warning<
3350+
"%0 attribute only applies to a pointer, reference, class, struct, or union (%1 is invalid)">,
3351+
InGroup<IgnoredAttributes>;
33493352
def err_attribute_no_member_pointers : Error<
33503353
"%0 attribute cannot be used with pointers to members">;
33513354
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
@@ -87,6 +87,7 @@ FEATURE(attribute_overloadable, true)
8787
FEATURE(attribute_unavailable_with_message, true)
8888
FEATURE(attribute_unused_on_fields, true)
8989
FEATURE(attribute_diagnose_if_objc, true)
90+
FEATURE(attribute_noescape_nonpointer, true)
9091
FEATURE(blocks, LangOpts.Blocks)
9192
FEATURE(c_thread_safety_attributes, true)
9293
FEATURE(cxx_exceptions, LangOpts.CXXExceptions)

clang/include/clang/Sema/Sema.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4479,13 +4479,6 @@ class Sema final : public SemaBase {
44794479
StringRef &Str,
44804480
SourceLocation *ArgLocation = nullptr);
44814481

4482-
/// Determine if type T is a valid subject for a nonnull and similar
4483-
/// attributes. Dependent types are considered valid so they can be checked
4484-
/// during instantiation time. By default, we look through references (the
4485-
/// behavior used by nonnull), but if the second parameter is true, then we
4486-
/// treat a reference type as valid.
4487-
bool isValidPointerAttrType(QualType T, bool RefOkay = false);
4488-
44894482
/// AddAssumeAlignedAttr - Adds an assume_aligned attribute to a particular
44904483
/// declaration.
44914484
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
@@ -2878,7 +2878,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
28782878
break;
28792879
}
28802880

2881-
if (FI.getExtParameterInfo(ArgNo).isNoEscape())
2881+
if (FI.getExtParameterInfo(ArgNo).isNoEscape() &&
2882+
isValidPointerAttrType(ParamType, /*RefOkay=*/true))
28822883
Attrs.addAttribute(llvm::Attribute::NoCapture);
28832884

28842885
if (Attrs.hasAttributes()) {

clang/lib/Sema/SemaChecking.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3110,7 +3110,7 @@ static void CheckNonNullArguments(Sema &S,
31103110
if (!NonNull->args_size()) {
31113111
// Easy case: all pointer arguments are nonnull.
31123112
for (const auto *Arg : Args)
3113-
if (S.isValidPointerAttrType(Arg->getType()))
3113+
if (isValidPointerAttrType(Arg->getType()))
31143114
CheckNonNullArgument(S, Arg, CallSiteLoc);
31153115
return;
31163116
}

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,37 +1220,11 @@ static void handleNoSpecializations(Sema &S, Decl *D, const ParsedAttr &AL) {
12201220
NoSpecializationsAttr::Create(S.Context, Message, AL));
12211221
}
12221222

1223-
bool Sema::isValidPointerAttrType(QualType T, bool RefOkay) {
1224-
if (T->isDependentType())
1225-
return true;
1226-
if (RefOkay) {
1227-
if (T->isReferenceType())
1228-
return true;
1229-
} else {
1230-
T = T.getNonReferenceType();
1231-
}
1232-
1233-
// The nonnull attribute, and other similar attributes, can be applied to a
1234-
// transparent union that contains a pointer type.
1235-
if (const RecordType *UT = T->getAsUnionType()) {
1236-
if (UT && UT->getDecl()->hasAttr<TransparentUnionAttr>()) {
1237-
RecordDecl *UD = UT->getDecl();
1238-
for (const auto *I : UD->fields()) {
1239-
QualType QT = I->getType();
1240-
if (QT->isAnyPointerType() || QT->isBlockPointerType())
1241-
return true;
1242-
}
1243-
}
1244-
}
1245-
1246-
return T->isAnyPointerType() || T->isBlockPointerType();
1247-
}
1248-
12491223
static bool attrNonNullArgCheck(Sema &S, QualType T, const ParsedAttr &AL,
12501224
SourceRange AttrParmRange,
12511225
SourceRange TypeRange,
12521226
bool isReturnValue = false) {
1253-
if (!S.isValidPointerAttrType(T)) {
1227+
if (!isValidPointerAttrType(T)) {
12541228
if (isReturnValue)
12551229
S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
12561230
<< AL << AttrParmRange << TypeRange;
@@ -1291,7 +1265,7 @@ static void handleNonNullAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
12911265
for (unsigned I = 0, E = getFunctionOrMethodNumParams(D);
12921266
I != E && !AnyPointers; ++I) {
12931267
QualType T = getFunctionOrMethodParamType(D, I);
1294-
if (S.isValidPointerAttrType(T))
1268+
if (isValidPointerAttrType(T))
12951269
AnyPointers = true;
12961270
}
12971271

@@ -1339,10 +1313,11 @@ static void handleNoEscapeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
13391313
if (D->isInvalidDecl())
13401314
return;
13411315

1342-
// noescape only applies to pointer types.
1316+
// noescape only applies to pointer and record types.
13431317
QualType T = cast<ParmVarDecl>(D)->getType();
1344-
if (!S.isValidPointerAttrType(T, /* RefOkay */ true)) {
1345-
S.Diag(AL.getLoc(), diag::warn_attribute_pointers_only)
1318+
if (!isValidPointerAttrType(T, /* RefOkay */ true) && !T->isRecordType()) {
1319+
S.Diag(AL.getLoc(),
1320+
diag::warn_attribute_pointer_or_reference_or_record_only)
13461321
<< AL << AL.getRange() << 0;
13471322
return;
13481323
}

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 *, int __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 *, int __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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
void noescapeFunc1(__attribute__((noescape)) int *);
1414
void noescapeFunc2(__attribute__((noescape)) id);
1515
void noescapeFunc3(__attribute__((noescape)) union U);
16+
void noescapeFunc4(__attribute__((noescape)) int);
1617

1718
// Block descriptors of non-escaping blocks don't need pointers to copy/dispose
1819
// helper functions.
@@ -53,6 +54,11 @@ void test3(union U u) {
5354
noescapeFunc3(u);
5455
}
5556

57+
// CHECK-NOT: nocapture
58+
void testNonPtr(int i) {
59+
noescapeFunc4(i);
60+
}
61+
5662
// CHECK: define internal void @"\01-[C0 m0:]"({{.*}}, {{.*}}, {{.*}} nocapture {{.*}})
5763

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

clang/test/SemaCXX/noescape-attr.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,11 @@
33
template<typename T>
44
void test1(T __attribute__((noescape)) arr, int size);
55

6-
// expected-warning@+1 {{'noescape' attribute only applies to pointer arguments}}
7-
void test2(int __attribute__((noescape)) arr, int size);
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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
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 {

0 commit comments

Comments
 (0)