[clang] [clang] Fix sema on ObjCLifetime conversion (PR #178524)
Peter Rong via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 29 10:25:37 PST 2026
https://github.com/DataCorrupted updated https://github.com/llvm/llvm-project/pull/178524
>From f57c297bc41117d67a5d89d06f78ac2b030b4de3 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Thu, 22 Jan 2026 14:48:30 -0800
Subject: [PATCH 1/6] [clang] Fix sema
clang can't handle objc lifetime correctly when casting
add a test
---
clang/lib/Sema/SemaInit.cpp | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index ff278bc7471bd..117c2e4e6674d 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -5651,6 +5651,13 @@ static void TryReferenceInitializationCore(Sema &S,
T1QualsIgnoreAS.removeAddressSpace();
T2QualsIgnoreAS.removeAddressSpace();
}
+ // Postpone ObjC lifetime conversions to after the temporary materialization
+ // conversion, similar to address space conversions. This handles cases like
+ // binding a __strong rvalue to a const __autoreleasing reference.
+ if (T1Quals.getObjCLifetime() != T2Quals.getObjCLifetime()) {
+ T1QualsIgnoreAS.removeObjCLifetime();
+ T2QualsIgnoreAS.removeObjCLifetime();
+ }
QualType cv1T4 = S.Context.getQualifiedType(cv2T2, T1QualsIgnoreAS);
if (T1QualsIgnoreAS != T2QualsIgnoreAS)
Sequence.AddQualificationConversionStep(cv1T4, ValueKind);
@@ -5664,6 +5671,14 @@ static void TryReferenceInitializationCore(Sema &S,
Sequence.AddQualificationConversionStep(cv1T4WithAS, ValueKind);
cv1T4 = cv1T4WithAS;
}
+ // Add ObjC lifetime conversion if required.
+ if (T1Quals.getObjCLifetime() != T2Quals.getObjCLifetime()) {
+ auto T4Quals = cv1T4.getQualifiers();
+ T4Quals.setObjCLifetime(T1Quals.getObjCLifetime());
+ QualType cv1T4WithLifetime = S.Context.getQualifiedType(T2, T4Quals);
+ Sequence.AddQualificationConversionStep(cv1T4WithLifetime, ValueKind);
+ cv1T4 = cv1T4WithLifetime;
+ }
// In any case, the reference is bound to the resulting glvalue (or to
// an appropriate base class subobject).
>From bea74c7aabe1bd5f2eba2567c374c6be3047bbcc Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Thu, 22 Jan 2026 14:48:30 -0800
Subject: [PATCH 2/6] Address reviewers
---
clang/lib/Sema/SemaInit.cpp | 22 ++++++-----
.../arc-lifetime-rvalue-ref-binding.mm | 39 +++++++++++++++++++
2 files changed, 52 insertions(+), 9 deletions(-)
create mode 100644 clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 117c2e4e6674d..fad1a7775dc5c 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -5645,21 +5645,22 @@ static void TryReferenceInitializationCore(Sema &S,
// applied.
// Postpone address space conversions to after the temporary materialization
// conversion to allow creating temporaries in the alloca address space.
- auto T1QualsIgnoreAS = T1Quals;
- auto T2QualsIgnoreAS = T2Quals;
+ auto T1QualsIgnoreConvertions = T1Quals;
+ auto T2QualsIgnoreConvertions = T2Quals;
if (T1Quals.getAddressSpace() != T2Quals.getAddressSpace()) {
- T1QualsIgnoreAS.removeAddressSpace();
- T2QualsIgnoreAS.removeAddressSpace();
+ T1QualsIgnoreConvertions.removeAddressSpace();
+ T2QualsIgnoreConvertions.removeAddressSpace();
}
// Postpone ObjC lifetime conversions to after the temporary materialization
// conversion, similar to address space conversions. This handles cases like
// binding a __strong rvalue to a const __autoreleasing reference.
if (T1Quals.getObjCLifetime() != T2Quals.getObjCLifetime()) {
- T1QualsIgnoreAS.removeObjCLifetime();
- T2QualsIgnoreAS.removeObjCLifetime();
+ T1QualsIgnoreConvertions.removeObjCLifetime();
+ T2QualsIgnoreConvertions.removeObjCLifetime();
}
- QualType cv1T4 = S.Context.getQualifiedType(cv2T2, T1QualsIgnoreAS);
- if (T1QualsIgnoreAS != T2QualsIgnoreAS)
+ QualType cv1T4 =
+ S.Context.getQualifiedType(cv2T2, T1QualsIgnoreConvertions);
+ if (T1QualsIgnoreConvertions != T2QualsIgnoreConvertions)
Sequence.AddQualificationConversionStep(cv1T4, ValueKind);
Sequence.AddReferenceBindingStep(cv1T4, ValueKind == VK_PRValue);
ValueKind = isLValueRef ? VK_LValue : VK_XValue;
@@ -5675,7 +5676,10 @@ static void TryReferenceInitializationCore(Sema &S,
if (T1Quals.getObjCLifetime() != T2Quals.getObjCLifetime()) {
auto T4Quals = cv1T4.getQualifiers();
T4Quals.setObjCLifetime(T1Quals.getObjCLifetime());
- QualType cv1T4WithLifetime = S.Context.getQualifiedType(T2, T4Quals);
+ // Apply T4Quals to the unqualified base type to avoid conflicting
+ // ObjC lifetime qualifiers in getQualifiedType.
+ QualType cv1T4WithLifetime =
+ S.Context.getQualifiedType(cv1T4.getUnqualifiedType(), T4Quals);
Sequence.AddQualificationConversionStep(cv1T4WithLifetime, ValueKind);
cv1T4 = cv1T4WithLifetime;
}
diff --git a/clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm b/clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm
new file mode 100644
index 0000000000000..5db3f59f8fa08
--- /dev/null
+++ b/clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -fobjc-arc -verify %s
+// RUN: %clang_cc1 -std=c++17 -fobjc-arc -ast-dump %s 2>&1 | FileCheck %s
+// expected-no-diagnostics
+
+// Test for binding ObjC ARC __strong rvalues to const __autoreleasing references.
+// This previously caused an assertion failure in Qualifiers::addConsistentQualifiers
+// when the compiler attempted to add conflicting ObjC lifetime qualifiers.
+
+// The const id& parameter has implicit __autoreleasing lifetime.
+void take(const id&);
+
+// CHECK-LABEL: FunctionDecl {{.*}} test_rvalue_binding
+// CHECK: CallExpr
+// CHECK: ImplicitCastExpr {{.*}} 'const __autoreleasing id' lvalue <NoOp>
+// CHECK-NEXT: CXXStaticCastExpr {{.*}} '__strong id' xvalue static_cast<__strong id &&> <NoOp>
+void test_rvalue_binding() {
+ id obj = nullptr;
+ take(static_cast<id&&>(obj));
+}
+
+// CHECK-LABEL: FunctionDecl {{.*}} test_lvalue_binding
+// CHECK: CallExpr
+// CHECK: ImplicitCastExpr {{.*}} 'const __autoreleasing id' lvalue <NoOp>
+// CHECK-NEXT: DeclRefExpr {{.*}} '__strong id' lvalue
+void test_lvalue_binding() {
+ id obj = nullptr;
+ take(obj);
+}
+
+// Test with fold expressions and perfect forwarding (original crash case).
+template <typename... Args>
+void call(Args... args) {
+ (take(static_cast<Args&&>(args)), ...);
+}
+
+// CHECK-LABEL: FunctionDecl {{.*}} test_fold_expression
+void test_fold_expression() {
+ call<id>(nullptr);
+}
>From 7260abe13189325120a1267f1c829bc52061bad8 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Thu, 29 Jan 2026 08:57:26 -0800
Subject: [PATCH 3/6] add codegen test
---
clang/docs/ReleaseNotes.rst | 4 +-
.../arc-lifetime-rvalue-ref-binding.mm | 57 +++++++++++++++++++
2 files changed, 60 insertions(+), 1 deletion(-)
create mode 100644 clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 42a300ca94581..c492c40ddbbba 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -236,6 +236,8 @@ Miscellaneous Clang Crashes Fixed
- Fixed a crash when using loop hint with a value dependent argument inside a
generic lambda. (#GH172289)
- Fixed a crash in C++ overload resolution with ``_Atomic``-qualified argument types. (#GH170433)
+- Fixed an assertion failure in ObjC++ ARC when binding a ``__strong`` rvalue reference to a ``const __autoreleasing`` reference. (#GH178524)
+
OpenACC Specific Changes
------------------------
@@ -317,7 +319,7 @@ AST Matchers
clang-format
------------
-- Add ``ObjCSpaceAfterMethodDeclarationPrefix`` option to control space between the
+- Add ``ObjCSpaceAfterMethodDeclarationPrefix`` option to control space between the
'-'/'+' and the return type in Objective-C method declarations
libclang
diff --git a/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm b/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm
new file mode 100644
index 0000000000000..03cc2762a9810
--- /dev/null
+++ b/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm
@@ -0,0 +1,57 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-apple-darwin10 -emit-llvm -fobjc-arc -O0 -disable-llvm-passes -o - %s | FileCheck %s
+
+// Test for correct IR generation when binding ObjC ARC __strong rvalues
+// to const __autoreleasing references. Previously, this caused an assertion
+// failure in Qualifiers::addConsistentQualifiers.
+
+// The const id& parameter has implicit __autoreleasing lifetime.
+void take(const id&);
+
+// CHECK-LABEL: define{{.*}} void @_Z19test_rvalue_bindingv()
+// CHECK: [[OBJ:%.*]] = alloca ptr, align 8
+// CHECK: [[REF_TMP:%.*]] = alloca ptr, align 8
+// CHECK: store ptr null, ptr [[OBJ]], align 8
+// CHECK: [[LOAD:%.*]] = load ptr, ptr [[OBJ]], align 8
+// CHECK: [[RETAIN:%.*]] = call ptr @llvm.objc.retain(ptr [[LOAD]])
+// CHECK: store ptr [[RETAIN]], ptr [[REF_TMP]], align 8
+// CHECK: [[AUTORELEASE:%.*]] = call ptr @llvm.objc.autorelease(ptr [[RETAIN]])
+// CHECK: call void @_Z4takeRKU15__autoreleasingP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[REF_TMP]])
+void test_rvalue_binding() {
+ id obj = nullptr;
+ take(static_cast<id&&>(obj));
+}
+
+// CHECK-LABEL: define{{.*}} void @_Z18test_lvalue_bindingv()
+// CHECK: [[OBJ:%.*]] = alloca ptr, align 8
+// CHECK: [[REF_TMP:%.*]] = alloca ptr, align 8
+// CHECK: store ptr null, ptr [[OBJ]], align 8
+// CHECK: [[LOAD:%.*]] = load ptr, ptr [[OBJ]], align 8
+// CHECK: store ptr [[LOAD]], ptr [[REF_TMP]], align 8
+// CHECK: [[AUTORELEASE:%.*]] = call ptr @llvm.objc.autorelease(ptr [[LOAD]])
+// CHECK: call void @_Z4takeRKU15__autoreleasingP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[REF_TMP]])
+void test_lvalue_binding() {
+ id obj = nullptr;
+ take(obj);
+}
+
+// Test with fold expressions and perfect forwarding (original crash case).
+template <typename... Args>
+void call(Args... args) {
+ (take(static_cast<Args&&>(args)), ...);
+}
+
+// CHECK-LABEL: define{{.*}} void @_Z20test_fold_expressionv()
+// CHECK: call void @_Z4callIJP11objc_objectEEvDpT_(ptr noundef null)
+void test_fold_expression() {
+ call<id>(nullptr);
+}
+
+// CHECK-LABEL: define{{.*}} void @_Z4callIJP11objc_objectEEvDpT_(ptr noundef %args)
+// CHECK: [[ARGS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK: [[REF_TMP:%.*]] = alloca ptr, align 8
+// CHECK: store ptr %args, ptr [[ARGS_ADDR]], align 8
+// CHECK: [[LOAD:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 8
+// CHECK: [[RETAIN:%.*]] = call ptr @llvm.objc.retain(ptr [[LOAD]])
+// CHECK: store ptr [[RETAIN]], ptr [[REF_TMP]], align 8
+// CHECK: [[AUTORELEASE:%.*]] = call ptr @llvm.objc.autorelease(ptr [[RETAIN]])
+// CHECK: call void @_Z4takeRKU15__autoreleasingP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[REF_TMP]])
>From 7f3db6b5aa1ca9d408f8055377f610c237390dd0 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Thu, 29 Jan 2026 10:05:58 -0800
Subject: [PATCH 4/6] fix test
---
.../arc-lifetime-rvalue-ref-binding.mm | 35 ++++++++-----------
1 file changed, 14 insertions(+), 21 deletions(-)
diff --git a/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm b/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm
index 03cc2762a9810..93f87bb8c1d3c 100644
--- a/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm
+++ b/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm
@@ -9,26 +9,21 @@
// CHECK-LABEL: define{{.*}} void @_Z19test_rvalue_bindingv()
// CHECK: [[OBJ:%.*]] = alloca ptr, align 8
-// CHECK: [[REF_TMP:%.*]] = alloca ptr, align 8
// CHECK: store ptr null, ptr [[OBJ]], align 8
-// CHECK: [[LOAD:%.*]] = load ptr, ptr [[OBJ]], align 8
-// CHECK: [[RETAIN:%.*]] = call ptr @llvm.objc.retain(ptr [[LOAD]])
-// CHECK: store ptr [[RETAIN]], ptr [[REF_TMP]], align 8
-// CHECK: [[AUTORELEASE:%.*]] = call ptr @llvm.objc.autorelease(ptr [[RETAIN]])
-// CHECK: call void @_Z4takeRKU15__autoreleasingP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[REF_TMP]])
+// CHECK: call void @_Z4takeRU15__autoreleasingKP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[OBJ]])
+// CHECK: call void @llvm.objc.storeStrong(ptr [[OBJ]], ptr null)
+// CHECK: ret void
void test_rvalue_binding() {
id obj = nullptr;
take(static_cast<id&&>(obj));
}
-// CHECK-LABEL: define{{.*}} void @_Z18test_lvalue_bindingv()
+// CHECK-LABEL: define{{.*}} void @_Z19test_lvalue_bindingv()
// CHECK: [[OBJ:%.*]] = alloca ptr, align 8
-// CHECK: [[REF_TMP:%.*]] = alloca ptr, align 8
// CHECK: store ptr null, ptr [[OBJ]], align 8
-// CHECK: [[LOAD:%.*]] = load ptr, ptr [[OBJ]], align 8
-// CHECK: store ptr [[LOAD]], ptr [[REF_TMP]], align 8
-// CHECK: [[AUTORELEASE:%.*]] = call ptr @llvm.objc.autorelease(ptr [[LOAD]])
-// CHECK: call void @_Z4takeRKU15__autoreleasingP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[REF_TMP]])
+// CHECK: call void @_Z4takeRU15__autoreleasingKP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[OBJ]])
+// CHECK: call void @llvm.objc.storeStrong(ptr [[OBJ]], ptr null)
+// CHECK: ret void
void test_lvalue_binding() {
id obj = nullptr;
take(obj);
@@ -41,17 +36,15 @@ void call(Args... args) {
}
// CHECK-LABEL: define{{.*}} void @_Z20test_fold_expressionv()
-// CHECK: call void @_Z4callIJP11objc_objectEEvDpT_(ptr noundef null)
+// CHECK: call void @_Z4callIJU8__strongP11objc_objectEEvDpT_(ptr noundef null)
void test_fold_expression() {
call<id>(nullptr);
}
-// CHECK-LABEL: define{{.*}} void @_Z4callIJP11objc_objectEEvDpT_(ptr noundef %args)
+// CHECK-LABEL: define{{.*}} void @_Z4callIJU8__strongP11objc_objectEEvDpT_(ptr noundef %args)
// CHECK: [[ARGS_ADDR:%.*]] = alloca ptr, align 8
-// CHECK: [[REF_TMP:%.*]] = alloca ptr, align 8
-// CHECK: store ptr %args, ptr [[ARGS_ADDR]], align 8
-// CHECK: [[LOAD:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 8
-// CHECK: [[RETAIN:%.*]] = call ptr @llvm.objc.retain(ptr [[LOAD]])
-// CHECK: store ptr [[RETAIN]], ptr [[REF_TMP]], align 8
-// CHECK: [[AUTORELEASE:%.*]] = call ptr @llvm.objc.autorelease(ptr [[RETAIN]])
-// CHECK: call void @_Z4takeRKU15__autoreleasingP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[REF_TMP]])
+// CHECK: store ptr null, ptr [[ARGS_ADDR]], align 8
+// CHECK: call void @llvm.objc.storeStrong(ptr [[ARGS_ADDR]], ptr %args)
+// CHECK: call void @_Z4takeRU15__autoreleasingKP11objc_object(ptr noundef nonnull align 8 dereferenceable(8) [[ARGS_ADDR]])
+// CHECK: call void @llvm.objc.storeStrong(ptr [[ARGS_ADDR]], ptr null)
+// CHECK: ret void
>From 57d3afbfa0c29a13ab041d01de0039a401f0615a Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Thu, 29 Jan 2026 10:23:31 -0800
Subject: [PATCH 5/6] fix typo
---
clang/lib/Sema/SemaInit.cpp | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index fad1a7775dc5c..ec7486a846b25 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -5645,22 +5645,22 @@ static void TryReferenceInitializationCore(Sema &S,
// applied.
// Postpone address space conversions to after the temporary materialization
// conversion to allow creating temporaries in the alloca address space.
- auto T1QualsIgnoreConvertions = T1Quals;
- auto T2QualsIgnoreConvertions = T2Quals;
+ auto T1QualsIgnoreConversions = T1Quals;
+ auto T2QualsIgnoreConversions = T2Quals;
if (T1Quals.getAddressSpace() != T2Quals.getAddressSpace()) {
- T1QualsIgnoreConvertions.removeAddressSpace();
- T2QualsIgnoreConvertions.removeAddressSpace();
+ T1QualsIgnoreConversions.removeAddressSpace();
+ T2QualsIgnoreConversions.removeAddressSpace();
}
// Postpone ObjC lifetime conversions to after the temporary materialization
// conversion, similar to address space conversions. This handles cases like
// binding a __strong rvalue to a const __autoreleasing reference.
if (T1Quals.getObjCLifetime() != T2Quals.getObjCLifetime()) {
- T1QualsIgnoreConvertions.removeObjCLifetime();
- T2QualsIgnoreConvertions.removeObjCLifetime();
+ T1QualsIgnoreConversions.removeObjCLifetime();
+ T2QualsIgnoreConversions.removeObjCLifetime();
}
QualType cv1T4 =
- S.Context.getQualifiedType(cv2T2, T1QualsIgnoreConvertions);
- if (T1QualsIgnoreConvertions != T2QualsIgnoreConvertions)
+ S.Context.getQualifiedType(cv2T2, T1QualsIgnoreConversions);
+ if (T1QualsIgnoreConversions != T2QualsIgnoreConversions)
Sequence.AddQualificationConversionStep(cv1T4, ValueKind);
Sequence.AddReferenceBindingStep(cv1T4, ValueKind == VK_PRValue);
ValueKind = isLValueRef ? VK_LValue : VK_XValue;
>From 4cf6b4737984a6bc023edf8355de1a2d01a764fc Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Thu, 29 Jan 2026 10:25:10 -0800
Subject: [PATCH 6/6] naming
---
clang/lib/Sema/SemaInit.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index ec7486a846b25..12e9c48ba0c52 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -5678,10 +5678,10 @@ static void TryReferenceInitializationCore(Sema &S,
T4Quals.setObjCLifetime(T1Quals.getObjCLifetime());
// Apply T4Quals to the unqualified base type to avoid conflicting
// ObjC lifetime qualifiers in getQualifiedType.
- QualType cv1T4WithLifetime =
+ QualType CV1T4WithLifetime =
S.Context.getQualifiedType(cv1T4.getUnqualifiedType(), T4Quals);
- Sequence.AddQualificationConversionStep(cv1T4WithLifetime, ValueKind);
- cv1T4 = cv1T4WithLifetime;
+ Sequence.AddQualificationConversionStep(CV1T4WithLifetime, ValueKind);
+ cv1T4 = CV1T4WithLifetime;
}
// In any case, the reference is bound to the resulting glvalue (or to
More information about the cfe-commits
mailing list