[clang] [Clang][CodeGen] map `noescape` to capture(address) (was capture(none)) (PR #199281)
Henrik G. Olsson via cfe-commits
cfe-commits at lists.llvm.org
Fri May 22 14:24:26 PDT 2026
https://github.com/hnrklssn updated https://github.com/llvm/llvm-project/pull/199281
>From 14269c8940c87088181d3168ddfe40127323620b Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Fri, 22 May 2026 13:25:11 -0700
Subject: [PATCH 1/2] [Clang][CodeGen] map `noescape` to capture(address) (was
capture(none))
`capture(none)` has very restrictive semantics and an easy footgun to
accidentally fire some UB into your code with. Most significantly it
does not allow any visible side-effects of whether a pointer was null or
not to escape the function. This means that the function cannot perform
different side effects depending on whether a pointer marked `noescape`
is null. Relax this to `captures(address)`, which allows information
about the numerical address to escape the function, but no provenance
(i.e. nothing that could be dereferenced) may escape.
As discussed in https://discourse.llvm.org/t/rfc-updating-the-semantics-of-the-noescape-attribute/90326.
---
clang/lib/CodeGen/CGCall.cpp | 2 +-
clang/test/CodeGenCXX/noescape.cpp | 30 +++++++++++++++---------------
clang/test/CodeGenObjC/noescape.m | 26 +++++++++++++-------------
3 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 7194b81459922..d09e62eb52a53 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -3163,7 +3163,7 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
}
if (FI.getExtParameterInfo(ArgNo).isNoEscape())
- Attrs.addCapturesAttr(llvm::CaptureInfo::none());
+ Attrs.addCapturesAttr(llvm::CaptureInfo(llvm::CaptureComponents::Address));
if (Attrs.hasAttributes()) {
unsigned FirstIRArg, NumIRArgs;
diff --git a/clang/test/CodeGenCXX/noescape.cpp b/clang/test/CodeGenCXX/noescape.cpp
index c3fc90e2ea54d..9c1ea1719bea5 100644
--- a/clang/test/CodeGenCXX/noescape.cpp
+++ b/clang/test/CodeGenCXX/noescape.cpp
@@ -8,26 +8,26 @@ struct S {
virtual void vm1(int *, int * __attribute__((noescape)));
};
-// CHECK: define{{.*}} void @_ZN1SC2EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(none) {{%.*}})
-// CHECK: define{{.*}} void @_ZN1SC1EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(none) {{%.*}}) {{.*}} {
-// CHECK: call void @_ZN1SC2EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} captures(none) {{.*}})
+// CHECK: define{{.*}} void @_ZN1SC2EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(address) {{%.*}})
+// CHECK: define{{.*}} void @_ZN1SC1EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(address) {{%.*}}) {{.*}} {
+// CHECK: call void @_ZN1SC2EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} captures(address) {{.*}})
S::S(int *, int * __attribute__((noescape))) {}
-// CHECK: define {{.*}} ptr @_ZN1SaSEPi(ptr {{.*}}, {{.*}} noundef captures(none) {{%.*}})
+// CHECK: define {{.*}} ptr @_ZN1SaSEPi(ptr {{.*}}, {{.*}} noundef captures(address) {{%.*}})
S &S::operator=(int * __attribute__((noescape))) { return *this; }
-// CHECK: define{{.*}} void @_ZN1S2m0EPiS0_(ptr {{.*}}, {{.*}} noundef captures(none) {{%.*}})
+// CHECK: define{{.*}} void @_ZN1S2m0EPiS0_(ptr {{.*}}, {{.*}} noundef captures(address) {{%.*}})
void S::m0(int *, int * __attribute__((noescape))) {}
-// CHECK: define{{.*}} void @_ZN1S3vm1EPiS0_(ptr {{.*}}, {{.*}} noundef captures(none) {{%.*}})
+// CHECK: define{{.*}} void @_ZN1S3vm1EPiS0_(ptr {{.*}}, {{.*}} noundef captures(address) {{%.*}})
void S::vm1(int *, int * __attribute__((noescape))) {}
// CHECK-LABEL: define{{.*}} void @_Z5test0P1SPiS1_(
-// CHECK: call void @_ZN1SC1EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(none) {{.*}})
-// CHECK: call {{.*}} ptr @_ZN1SaSEPi(ptr {{.*}}, {{.*}} noundef captures(none) {{.*}})
-// CHECK: call void @_ZN1S2m0EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(none) {{.*}})
-// CHECK: call void {{.*}}(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(none) {{.*}})
+// CHECK: call void @_ZN1SC1EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(address) {{.*}})
+// CHECK: call {{.*}} ptr @_ZN1SaSEPi(ptr {{.*}}, {{.*}} noundef captures(address) {{.*}})
+// CHECK: call void @_ZN1S2m0EPiS0_(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(address) {{.*}})
+// CHECK: call void {{.*}}(ptr {{.*}}, {{.*}}, {{.*}} noundef captures(address) {{.*}})
void test0(S *s, int *p0, int *p1) {
S t(p0, p1);
t = p1;
@@ -39,27 +39,27 @@ namespace std {
typedef decltype(sizeof(0)) size_t;
}
-// CHECK: define {{.*}} @_ZnwmPv({{.*}}, {{.*}} captures(none) {{.*}})
+// CHECK: define {{.*}} @_ZnwmPv({{.*}}, {{.*}} captures(address) {{.*}})
void *operator new(std::size_t, void * __attribute__((noescape)) p) {
return p;
}
// CHECK-LABEL: define{{.*}} ptr @_Z5test1Pv(
-// CHECK: %call = call {{.*}} @_ZnwmPv({{.*}}, {{.*}} captures(none) {{.*}})
+// CHECK: %call = call {{.*}} @_ZnwmPv({{.*}}, {{.*}} captures(address) {{.*}})
void *test1(void *p0) {
return ::operator new(16, p0);
}
// CHECK-LABEL: define{{.*}} void @_Z5test2PiS_(
-// CHECK: call void @"_ZZ5test2PiS_ENK3$_0clES_S_"({{.*}}, {{.*}}, {{.*}} captures(none) {{.*}})
-// CHECK: define internal void @"_ZZ5test2PiS_ENK3$_0clES_S_"({{.*}}, {{.*}}, {{.*}} noundef captures(none) {{%.*}})
+// CHECK: call void @"_ZZ5test2PiS_ENK3$_0clES_S_"({{.*}}, {{.*}}, {{.*}} captures(address) {{.*}})
+// CHECK: define internal void @"_ZZ5test2PiS_ENK3$_0clES_S_"({{.*}}, {{.*}}, {{.*}} noundef captures(address) {{%.*}})
void test2(int *p0, int *p1) {
auto t = [](int *, int * __attribute__((noescape))){};
t(p0, p1);
}
// CHECK-LABEL: define{{.*}} void @_Z5test3PFvU8noescapePiES_(
-// CHECK: call void {{.*}}(ptr noundef captures(none) {{.*}})
+// CHECK: call void {{.*}}(ptr noundef captures(address) {{.*}})
typedef void (*NoEscapeFunc)(__attribute__((noescape)) int *);
void test3(NoEscapeFunc f, int *p) {
diff --git a/clang/test/CodeGenObjC/noescape.m b/clang/test/CodeGenObjC/noescape.m
index e1dbc0eb92e54..80d4bb8e1c876 100644
--- a/clang/test/CodeGenObjC/noescape.m
+++ b/clang/test/CodeGenObjC/noescape.m
@@ -26,37 +26,37 @@
// CHECK: @[[BLOCK_DESCIPTOR_TMP_2:.*ls32l8"]] = linkonce_odr hidden unnamed_addr constant { i64, i64, ptr, i64 } { i64 0, i64 40, ptr @{{.*}}, i64 256 }, align 8
// CHECK-LABEL: define{{.*}} void @test0(
-// CHECK: call void @noescapeFunc0({{.*}}, {{.*}} captures(none) {{.*}})
-// CHECK: declare void @noescapeFunc0(ptr noundef, {{.*}} noundef captures(none))
+// CHECK: call void @noescapeFunc0({{.*}}, {{.*}} captures(address) {{.*}})
+// CHECK: declare void @noescapeFunc0(ptr noundef, {{.*}} noundef captures(address))
void test0(BlockTy b) {
noescapeFunc0(0, b);
}
// CHECK-LABEL: define{{.*}} void @test1(
-// CHECK: call void @noescapeFunc1({{.*}} captures(none) {{.*}})
-// CHECK: declare void @noescapeFunc1({{.*}} noundef captures(none))
+// CHECK: call void @noescapeFunc1({{.*}} captures(address) {{.*}})
+// CHECK: declare void @noescapeFunc1({{.*}} noundef captures(address))
void test1(int *i) {
noescapeFunc1(i);
}
// CHECK-LABEL: define{{.*}} void @test2(
-// CHECK: call void @noescapeFunc2({{.*}} captures(none) {{.*}})
-// CHECK: declare void @noescapeFunc2({{.*}} noundef captures(none))
+// CHECK: call void @noescapeFunc2({{.*}} captures(address) {{.*}})
+// CHECK: declare void @noescapeFunc2({{.*}} noundef captures(address))
void test2(id i) {
noescapeFunc2(i);
}
// CHECK-LABEL: define{{.*}} void @test3(
-// CHECK: call void @noescapeFunc3({{.*}} captures(none) {{.*}})
-// CHECK: declare void @noescapeFunc3({{.*}} captures(none))
+// CHECK: call void @noescapeFunc3({{.*}} captures(address) {{.*}})
+// CHECK: declare void @noescapeFunc3({{.*}} captures(address))
void test3(union U u) {
noescapeFunc3(u);
}
-// CHECK: define internal void @"\01-[C0 m0:]"({{.*}}, {{.*}}, {{.*}} captures(none) {{.*}})
+// CHECK: define internal void @"\01-[C0 m0:]"({{.*}}, {{.*}}, {{.*}} captures(address) {{.*}})
// CHECK-LABEL: define{{.*}} void @test4(
-// CHECK: call void @objc_msgSend(ptr {{.*}}, ptr {{.*}}, ptr noundef captures(none) {{.*}})
+// CHECK: call void @objc_msgSend(ptr {{.*}}, ptr {{.*}}, ptr noundef captures(address) {{.*}})
@interface C0
-(void) m0:(int*)__attribute__((noescape)) p0;
@@ -72,9 +72,9 @@ void test4(C0 *c0, int *p) {
}
// CHECK-LABEL: define{{.*}} void @test5(
-// CHECK: call void {{.*}}(ptr noundef @{{.*}}, ptr noundef captures(none) {{.*}})
-// CHECK: call void {{.*}}(ptr {{.*}}, ptr noundef captures(none) {{.*}})
-// CHECK: define internal void @{{.*}}(ptr {{.*}}, ptr noundef captures(none) {{.*}})
+// CHECK: call void {{.*}}(ptr noundef @{{.*}}, ptr noundef captures(address) {{.*}})
+// CHECK: call void {{.*}}(ptr {{.*}}, ptr noundef captures(address) {{.*}})
+// CHECK: define internal void @{{.*}}(ptr {{.*}}, ptr noundef captures(address) {{.*}})
typedef void (^BlockTy2)(__attribute__((noescape)) int *);
>From aa36ddb634cff1cc5e5eb870acc45d185ddb9286 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Fri, 22 May 2026 14:24:10 -0700
Subject: [PATCH 2/2] clang format
---
clang/lib/CodeGen/CGCall.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index d09e62eb52a53..40cc275d40273 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -3163,7 +3163,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
}
if (FI.getExtParameterInfo(ArgNo).isNoEscape())
- Attrs.addCapturesAttr(llvm::CaptureInfo(llvm::CaptureComponents::Address));
+ Attrs.addCapturesAttr(
+ llvm::CaptureInfo(llvm::CaptureComponents::Address));
if (Attrs.hasAttributes()) {
unsigned FirstIRArg, NumIRArgs;
More information about the cfe-commits
mailing list