Skip to content

[Clang] Permit noescape on non-pointer types #117344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ Attribute Changes in Clang

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

- Now ``__attribute__((noescape))`` can be applied to non-pointer types like ``std::span``.

Improvements to Clang's diagnostics
-----------------------------------

Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,13 @@ inline ParameterABI ParameterABIAttr::getABI() const {
llvm_unreachable("bad parameter ABI attribute kind");
}
}

/// Determine if type T is a valid subject for a nonnull and similar
/// attributes. Dependent types are considered valid so they can be checked
/// during instantiation time. By default, we look through references (the
/// behavior used by nonnull), but if the second parameter is true, then we
/// treat a reference type as valid.
bool isValidPointerAttrType(QualType T, bool RefOkay = false);
} // end namespace clang

#endif
13 changes: 7 additions & 6 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,13 @@ members, and static locals.
def NoEscapeDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
``noescape`` placed on a function parameter of a pointer type is used to inform
the compiler that the pointer cannot escape: that is, no reference to the object
the pointer points to that is derived from the parameter value will survive
after the function returns. Users are responsible for making sure parameters
annotated with ``noescape`` do not actually escape. Calling ``free()`` on such
a parameter does not constitute an escape.
``noescape`` placed on a function parameter is used to inform the compiler that
the pointer (or the pointer members in case of a user defined type) cannot
escape: that is, no reference to the object the pointer points to that is
derived from the parameter value will survive after the function returns. Users
are responsible for making sure parameters annotated with ``noescape`` do not
actually escape. Calling ``free()`` on such a parameter does not constitute an
escape.

For example:

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3346,6 +3346,9 @@ def warn_attribute_return_pointers_refs_only : Warning<
def warn_attribute_pointer_or_reference_only : Warning<
"%0 attribute only applies to a pointer or reference (%1 is invalid)">,
InGroup<IgnoredAttributes>;
def warn_attribute_pointer_or_reference_or_record_only : Warning<
"%0 attribute only applies to a pointer, reference, class, struct, or union (%1 is invalid)">,
InGroup<IgnoredAttributes>;
def err_attribute_no_member_pointers : Error<
"%0 attribute cannot be used with pointers to members">;
def err_attribute_invalid_implicit_this_argument : Error<
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ FEATURE(attribute_overloadable, true)
FEATURE(attribute_unavailable_with_message, true)
FEATURE(attribute_unused_on_fields, true)
FEATURE(attribute_diagnose_if_objc, true)
FEATURE(attribute_noescape_nonpointer, true)
FEATURE(blocks, LangOpts.Blocks)
FEATURE(c_thread_safety_attributes, true)
FEATURE(cxx_exceptions, LangOpts.CXXExceptions)
Expand Down
7 changes: 0 additions & 7 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -4479,13 +4479,6 @@ class Sema final : public SemaBase {
StringRef &Str,
SourceLocation *ArgLocation = nullptr);

/// Determine if type T is a valid subject for a nonnull and similar
/// attributes. Dependent types are considered valid so they can be checked
/// during instantiation time. By default, we look through references (the
/// behavior used by nonnull), but if the second parameter is true, then we
/// treat a reference type as valid.
bool isValidPointerAttrType(QualType T, bool RefOkay = false);

/// AddAssumeAlignedAttr - Adds an assume_aligned attribute to a particular
/// declaration.
void AddAssumeAlignedAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,
Expand Down
26 changes: 26 additions & 0 deletions clang/lib/AST/AttrImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,30 @@ unsigned AlignedAttr::getAlignment(ASTContext &Ctx) const {
return Ctx.getTargetDefaultAlignForAttributeAligned();
}

bool clang::isValidPointerAttrType(QualType T, bool RefOkay) {
if (T->isDependentType())
return true;
if (RefOkay) {
if (T->isReferenceType())
return true;
} else {
T = T.getNonReferenceType();
}

// The nonnull attribute, and other similar attributes, can be applied to a
// transparent union that contains a pointer type.
if (const RecordType *UT = T->getAsUnionType()) {
if (UT && UT->getDecl()->hasAttr<TransparentUnionAttr>()) {
RecordDecl *UD = UT->getDecl();
for (const auto *I : UD->fields()) {
QualType QT = I->getType();
if (QT->isAnyPointerType() || QT->isBlockPointerType())
return true;
}
}
}

return T->isAnyPointerType() || T->isBlockPointerType();
}

#include "clang/AST/AttrImpl.inc"
3 changes: 2 additions & 1 deletion clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2878,7 +2878,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
break;
}

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

if (Attrs.hasAttributes()) {
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3110,7 +3110,7 @@ static void CheckNonNullArguments(Sema &S,
if (!NonNull->args_size()) {
// Easy case: all pointer arguments are nonnull.
for (const auto *Arg : Args)
if (S.isValidPointerAttrType(Arg->getType()))
if (isValidPointerAttrType(Arg->getType()))
CheckNonNullArgument(S, Arg, CallSiteLoc);
return;
}
Expand Down
37 changes: 6 additions & 31 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1220,37 +1220,11 @@ static void handleNoSpecializations(Sema &S, Decl *D, const ParsedAttr &AL) {
NoSpecializationsAttr::Create(S.Context, Message, AL));
}

bool Sema::isValidPointerAttrType(QualType T, bool RefOkay) {
if (T->isDependentType())
return true;
if (RefOkay) {
if (T->isReferenceType())
return true;
} else {
T = T.getNonReferenceType();
}

// The nonnull attribute, and other similar attributes, can be applied to a
// transparent union that contains a pointer type.
if (const RecordType *UT = T->getAsUnionType()) {
if (UT && UT->getDecl()->hasAttr<TransparentUnionAttr>()) {
RecordDecl *UD = UT->getDecl();
for (const auto *I : UD->fields()) {
QualType QT = I->getType();
if (QT->isAnyPointerType() || QT->isBlockPointerType())
return true;
}
}
}

return T->isAnyPointerType() || T->isBlockPointerType();
}

static bool attrNonNullArgCheck(Sema &S, QualType T, const ParsedAttr &AL,
SourceRange AttrParmRange,
SourceRange TypeRange,
bool isReturnValue = false) {
if (!S.isValidPointerAttrType(T)) {
if (!isValidPointerAttrType(T)) {
if (isReturnValue)
S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
<< AL << AttrParmRange << TypeRange;
Expand Down Expand Up @@ -1291,7 +1265,7 @@ static void handleNonNullAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
for (unsigned I = 0, E = getFunctionOrMethodNumParams(D);
I != E && !AnyPointers; ++I) {
QualType T = getFunctionOrMethodParamType(D, I);
if (S.isValidPointerAttrType(T))
if (isValidPointerAttrType(T))
AnyPointers = true;
}

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

// noescape only applies to pointer types.
// noescape only applies to pointer and record types.
QualType T = cast<ParmVarDecl>(D)->getType();
if (!S.isValidPointerAttrType(T, /* RefOkay */ true)) {
S.Diag(AL.getLoc(), diag::warn_attribute_pointers_only)
if (!isValidPointerAttrType(T, /* RefOkay */ true) && !T->isRecordType()) {
S.Diag(AL.getLoc(),
diag::warn_attribute_pointer_or_reference_or_record_only)
<< AL << AL.getRange() << 0;
return;
}
Expand Down
8 changes: 7 additions & 1 deletion clang/test/AST/ast-dump-attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,14 @@ __attribute__((external_source_symbol(generated_declaration, defined_in="module"
// CHECK-NEXT: ExternalSourceSymbolAttr{{.*}} "Swift" "module" GeneratedDeclaration "testUSR"

namespace TestNoEscape {
struct S { int *p; };
void noescapeFunc(int *p0, __attribute__((noescape)) int *p1) {}
// CHECK: `-FunctionDecl{{.*}} noescapeFunc 'void (int *, __attribute__((noescape)) int *)'
// CHECK: |-FunctionDecl{{.*}} noescapeFunc 'void (int *, __attribute__((noescape)) int *)'
// CHECK-NEXT: ParmVarDecl
// CHECK-NEXT: ParmVarDecl
// CHECK-NEXT: NoEscapeAttr
void noescapeFunc2(int *p0, __attribute__((noescape)) S p1) {}
// CHECK: `-FunctionDecl{{.*}} noescapeFunc2 'void (int *, __attribute__((noescape)) S)'
// CHECK-NEXT: ParmVarDecl
// CHECK-NEXT: ParmVarDecl
// CHECK-NEXT: NoEscapeAttr
Expand Down
4 changes: 4 additions & 0 deletions clang/test/CodeGenCXX/noescape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ struct S {
S &operator=(int * __attribute__((noescape)));
void m0(int *, int * __attribute__((noescape)));
virtual void vm1(int *, int * __attribute__((noescape)));
virtual void vm2(int *, int __attribute__((noescape)));
};

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

// CHECK-NOT: nocapture
void S::vm2(int *, int __attribute__((noescape))) {}

// CHECK-LABEL: define{{.*}} void @_Z5test0P1SPiS1_(
// CHECK: call void @_ZN1SC1EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} nocapture noundef {{.*}})
// CHECK: call {{.*}} ptr @_ZN1SaSEPi(ptr {{.*}}, {{.*}} nocapture noundef {{.*}})
Expand Down
10 changes: 10 additions & 0 deletions clang/test/CodeGenObjC/noescape.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
long long *ll;
} __attribute__((transparent_union));

struct S {
int *p;
};

void escapingFunc0(BlockTy);
void noescapeFunc0(id, __attribute__((noescape)) BlockTy);
void noescapeFunc1(__attribute__((noescape)) int *);
void noescapeFunc2(__attribute__((noescape)) id);
void noescapeFunc3(__attribute__((noescape)) union U);
void noescapeFunc4(__attribute__((noescape)) struct S s);

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

// CHECK-NOT: nocapture
void testNonPtr(struct S s) {
noescapeFunc4(s);
}

// CHECK: define internal void @"\01-[C0 m0:]"({{.*}}, {{.*}}, {{.*}} nocapture {{.*}})

// CHECK-LABEL: define{{.*}} void @test4(
Expand Down
10 changes: 8 additions & 2 deletions clang/test/SemaCXX/noescape-attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@
template<typename T>
void test1(T __attribute__((noescape)) arr, int size);

// expected-warning@+1 {{'noescape' attribute only applies to pointer arguments}}
void test2(int __attribute__((noescape)) arr, int size);
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)}}

struct S { int *p; };
void test3(S __attribute__((noescape)) s);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get this macro/error here. This will never happen in clang, we support this feature.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduced this feature and the goal of this test to make sure feature testing works as expected. I am happy to remove the test if you find it redundant.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for clarifying.

#if !__has_feature(attribute_noescape_nonpointer)
#error "attribute_noescape_nonpointer should be supported"
#endif
6 changes: 3 additions & 3 deletions clang/test/SemaObjCXX/noescape.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
template <class T>
void noescapeFunc7(__attribute__((noescape)) T &&);

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

struct S1 {
Expand Down
Loading