https://github.com/DataCorrupted updated https://github.com/llvm/llvm-project/pull/180817
>From 28cededb56b656d98839ee6449fea4807b2bf26c Mon Sep 17 00:00:00 2001 From: Peter Rong <[email protected]> Date: Tue, 10 Feb 2026 09:35:41 -0800 Subject: [PATCH 1/4] Reapply "[clang] Fix sema on ObjCLifetime conversion (#178524)" Clang can't handle objc lifetime correctly when casting We reuse the approach similar to lifetime: First remove it before the conversion, then add it back. Add a test Fixes https://github.com/llvm/llvm-project/issues/177478 --- clang/docs/ReleaseNotes.rst | 4 +- clang/lib/Sema/SemaInit.cpp | 33 +++++++++--- .../arc-lifetime-rvalue-ref-binding.mm | 50 +++++++++++++++++++ .../arc-lifetime-rvalue-ref-binding.mm | 39 +++++++++++++++ 4 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm create mode 100644 clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index a1bb1bd2467b7..d21e236563232 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -278,6 +278,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 ------------------------ @@ -360,7 +362,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/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index ff278bc7471bd..12e9c48ba0c52 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -5645,14 +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 T1QualsIgnoreConversions = T1Quals; + auto T2QualsIgnoreConversions = T2Quals; if (T1Quals.getAddressSpace() != T2Quals.getAddressSpace()) { - T1QualsIgnoreAS.removeAddressSpace(); - T2QualsIgnoreAS.removeAddressSpace(); - } - QualType cv1T4 = S.Context.getQualifiedType(cv2T2, T1QualsIgnoreAS); - if (T1QualsIgnoreAS != T2QualsIgnoreAS) + 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()) { + T1QualsIgnoreConversions.removeObjCLifetime(); + T2QualsIgnoreConversions.removeObjCLifetime(); + } + QualType cv1T4 = + S.Context.getQualifiedType(cv2T2, T1QualsIgnoreConversions); + if (T1QualsIgnoreConversions != T2QualsIgnoreConversions) Sequence.AddQualificationConversionStep(cv1T4, ValueKind); Sequence.AddReferenceBindingStep(cv1T4, ValueKind == VK_PRValue); ValueKind = isLValueRef ? VK_LValue : VK_XValue; @@ -5664,6 +5672,17 @@ 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()); + // 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; + } // In any case, the reference is bound to the resulting glvalue (or to // an appropriate base class subobject). 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..93f87bb8c1d3c --- /dev/null +++ b/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm @@ -0,0 +1,50 @@ +// 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: store ptr null, ptr [[OBJ]], align 8 +// 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 @_Z19test_lvalue_bindingv() +// CHECK: [[OBJ:%.*]] = alloca ptr, align 8 +// CHECK: store ptr null, ptr [[OBJ]], align 8 +// 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); +} + +// 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 @_Z4callIJU8__strongP11objc_objectEEvDpT_(ptr noundef null) +void test_fold_expression() { + call<id>(nullptr); +} + +// CHECK-LABEL: define{{.*}} void @_Z4callIJU8__strongP11objc_objectEEvDpT_(ptr noundef %args) +// CHECK: [[ARGS_ADDR:%.*]] = alloca ptr, align 8 +// 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 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 82375996fb8ec124e0e13b4e64c8bdacda90de23 Mon Sep 17 00:00:00 2001 From: Peter Rong <[email protected]> Date: Tue, 10 Feb 2026 11:47:08 -0800 Subject: [PATCH 2/4] format --- clang/docs/ReleaseNotes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index d21e236563232..458f32c9cf974 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -362,7 +362,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 >From da5b72d0437519f01878bfd9008d25951664db31 Mon Sep 17 00:00:00 2001 From: Peter Rong <[email protected]> Date: Wed, 11 Feb 2026 13:43:01 -0800 Subject: [PATCH 3/4] new fix, address reviewer's concern, and add more test --- clang/lib/Sema/SemaInit.cpp | 44 +++++++++---------- .../arc-lifetime-rvalue-ref-binding.mm | 19 ++++++++ .../arc-lifetime-rvalue-ref-binding.mm | 2 +- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 12e9c48ba0c52..89e769f8ef21c 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -5645,22 +5645,29 @@ 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 T1QualsIgnoreConversions = T1Quals; - auto T2QualsIgnoreConversions = T2Quals; + auto T1QualsIgnoreAS = T1Quals; + auto T2QualsIgnoreAS = T2Quals; if (T1Quals.getAddressSpace() != T2Quals.getAddressSpace()) { - 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. + T1QualsIgnoreAS.removeAddressSpace(); + T2QualsIgnoreAS.removeAddressSpace(); + } + // Strip the existing ObjC lifetime qualifier from cv2T2 before combining + // with T1's qualifiers. getQualifiedType adds qualifiers on top of + // existing ones, so if cv2T2 already carries e.g. __strong and T1Quals + // has __autoreleasing, we'd hit an assertion in addConsistentQualifiers. + // Stripping first and then applying T1's qualifiers ensures the correct + // lifetime is set *before* reference binding, which is critical for + // CodeGen to emit the right ARC semantics (e.g. retain+autorelease for + // __autoreleasing, not retain+release as for __strong). + QualType T2ForQualConv = cv2T2; if (T1Quals.getObjCLifetime() != T2Quals.getObjCLifetime()) { - T1QualsIgnoreConversions.removeObjCLifetime(); - T2QualsIgnoreConversions.removeObjCLifetime(); + Qualifiers T2BaseQuals = T2ForQualConv.getQualifiers(); + T2BaseQuals.removeObjCLifetime(); + T2ForQualConv = S.Context.getQualifiedType( + T2ForQualConv.getUnqualifiedType(), T2BaseQuals); } - QualType cv1T4 = - S.Context.getQualifiedType(cv2T2, T1QualsIgnoreConversions); - if (T1QualsIgnoreConversions != T2QualsIgnoreConversions) + QualType cv1T4 = S.Context.getQualifiedType(T2ForQualConv, T1QualsIgnoreAS); + if (T1QualsIgnoreAS != T2QualsIgnoreAS) Sequence.AddQualificationConversionStep(cv1T4, ValueKind); Sequence.AddReferenceBindingStep(cv1T4, ValueKind == VK_PRValue); ValueKind = isLValueRef ? VK_LValue : VK_XValue; @@ -5672,17 +5679,6 @@ 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()); - // 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; - } // In any case, the reference is bound to the resulting glvalue (or to // an appropriate base class subobject). diff --git a/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm b/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm index 93f87bb8c1d3c..71412a115b8ae 100644 --- a/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm +++ b/clang/test/CodeGenObjCXX/arc-lifetime-rvalue-ref-binding.mm @@ -48,3 +48,22 @@ void test_fold_expression() { // 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 + +// Test that binding a prvalue to an __autoreleasing rvalue reference emits +// retain+autorelease (not retain+release), per ARC semantics for __autoreleasing. +// CHECK-LABEL: define{{.*}} void @_Z32test_autoreleasing_rvalue_ref_prP11objc_object(ptr noundef %a) +// CHECK: [[A_ADDR:%.*]] = alloca ptr, align 8 +// CHECK: [[R:%.*]] = alloca ptr, align 8 +// CHECK: [[REF_TMP:%.*]] = alloca ptr, align 8 +// CHECK: store ptr null, ptr [[A_ADDR]], align 8 +// CHECK: call void @llvm.objc.storeStrong(ptr [[A_ADDR]], ptr %a) +// CHECK: [[LOAD:%.*]] = load ptr, ptr [[A_ADDR]], align 8 +// CHECK: [[RETAINED:%.*]] = call ptr @llvm.objc.retain(ptr [[LOAD]]) +// CHECK: [[AUTORELEASED:%.*]] = call ptr @llvm.objc.autorelease(ptr [[RETAINED]]) +// CHECK: store ptr [[AUTORELEASED]], ptr [[REF_TMP]], align 8 +// CHECK: store ptr [[REF_TMP]], ptr [[R]], align 8 +// CHECK: call void @llvm.objc.storeStrong(ptr [[A_ADDR]], ptr null) +// CHECK: ret void +void test_autoreleasing_rvalue_ref_pr(id a) { + id __autoreleasing && r = id{a}; +} diff --git a/clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm b/clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm index 5db3f59f8fa08..d6d9c4a6289ee 100644 --- a/clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm +++ b/clang/test/SemaObjCXX/arc-lifetime-rvalue-ref-binding.mm @@ -11,7 +11,7 @@ // CHECK-LABEL: FunctionDecl {{.*}} test_rvalue_binding // CHECK: CallExpr -// CHECK: ImplicitCastExpr {{.*}} 'const __autoreleasing id' lvalue <NoOp> +// CHECK: ImplicitCastExpr {{.*}} 'const __autoreleasing id' xvalue <NoOp> // CHECK-NEXT: CXXStaticCastExpr {{.*}} '__strong id' xvalue static_cast<__strong id &&> <NoOp> void test_rvalue_binding() { id obj = nullptr; >From e7d792781e1735a13bb7f842fe3295ac657e0bd7 Mon Sep 17 00:00:00 2001 From: Peter Rong <[email protected]> Date: Wed, 11 Feb 2026 13:50:14 -0800 Subject: [PATCH 4/4] comment --- clang/lib/Sema/SemaInit.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 89e769f8ef21c..fa234fb498e59 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -5652,13 +5652,7 @@ static void TryReferenceInitializationCore(Sema &S, T2QualsIgnoreAS.removeAddressSpace(); } // Strip the existing ObjC lifetime qualifier from cv2T2 before combining - // with T1's qualifiers. getQualifiedType adds qualifiers on top of - // existing ones, so if cv2T2 already carries e.g. __strong and T1Quals - // has __autoreleasing, we'd hit an assertion in addConsistentQualifiers. - // Stripping first and then applying T1's qualifiers ensures the correct - // lifetime is set *before* reference binding, which is critical for - // CodeGen to emit the right ARC semantics (e.g. retain+autorelease for - // __autoreleasing, not retain+release as for __strong). + // with T1's qualifiers. QualType T2ForQualConv = cv2T2; if (T1Quals.getObjCLifetime() != T2Quals.getObjCLifetime()) { Qualifiers T2BaseQuals = T2ForQualConv.getQualifiers(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
