From 7a5dbdc20beb649e01548569a1efdaa0d7bf211f Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Wed, 30 Apr 2025 16:41:00 +0200 Subject: [PATCH] CastOptimizer: don't assume dynamic casts from ObjectiveC classes to unrelated classes will fail In case of ObjectiveC classes, the runtime type can differ from its declared type. Therefore a cast between (compile-time) unrelated classes may succeed at runtime. rdar://149810124 --- lib/SIL/Utils/DynamicCasts.cpp | 11 ++++++++++- test/Interpreter/bridged_casts_folding.swift | 6 +++--- test/SILOptimizer/cast_folding_no_bridging.sil | 13 ++++--------- test/SILOptimizer/cast_folding_objc.swift | 7 ++----- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/SIL/Utils/DynamicCasts.cpp b/lib/SIL/Utils/DynamicCasts.cpp index ce05f2a73b0fc..781c9775ac41c 100644 --- a/lib/SIL/Utils/DynamicCasts.cpp +++ b/lib/SIL/Utils/DynamicCasts.cpp @@ -741,7 +741,10 @@ swift::classifyDynamicCast(SILFunction *function, if (targetClass->isSuperclassOf(sourceClass)) return DynamicCastFeasibility::WillSucceed; - return DynamicCastFeasibility::WillFail; + // In case of ObjectiveC classes, the runtime type can differ from its + // declared type. Therefore a cast between (compile-time) unrelated + // classes may succeed at runtime. + return DynamicCastFeasibility::MaySucceed; } } @@ -750,6 +753,12 @@ swift::classifyDynamicCast(SILFunction *function, if (hierarchyResult != DynamicCastFeasibility::WillFail) return hierarchyResult; + // In case of ObjectiveC classes, the runtime type can differ from its + // declared type. Therefore a cast between (compile-time) unrelated + // classes may succeed at runtime. + if (sourceClass->hasClangNode()) + return DynamicCastFeasibility::MaySucceed; + // As a backup, consider whether either type is a CF class type // with an NS bridged equivalent. CanType bridgedSource = getNSBridgedClassOfCFClass(source); diff --git a/test/Interpreter/bridged_casts_folding.swift b/test/Interpreter/bridged_casts_folding.swift index 7fe9f52fab4a3..afb9731c46c24 100644 --- a/test/Interpreter/bridged_casts_folding.swift +++ b/test/Interpreter/bridged_casts_folding.swift @@ -58,7 +58,7 @@ Tests.test("NSString => Array. Crashing test case") { // CHECK: [ OK ] BridgedCastFolding.NSString => Array. Crashing test case // CHECK-OPT-LABEL: [ RUN ] BridgedCastFolding.NSString => Array. Crashing test case - // CHECK-OPT: stderr>>> OK: saw expected "crashed: sig{{ill|trap}}" + // CHECK-OPT: stderr>>> OK: saw expected "crashed: sigabrt" // CHECK-OPT: [ OK ] BridgedCastFolding.NSString => Array. Crashing test case expectCrashLater() do { @@ -130,7 +130,7 @@ Tests.test("NSNumber (Int) -> String. Crashing test.") { // CHECK: [ OK ] BridgedCastFolding.NSNumber (Int) -> String. Crashing test. // CHECK-OPT-LABEL: [ RUN ] BridgedCastFolding.NSNumber (Int) -> String. Crashing test. - // CHECK-OPT: stderr>>> OK: saw expected "crashed: sig{{ill|trap}}" + // CHECK-OPT: stderr>>> OK: saw expected "crashed: sigabrt" // CHECK-OPT: [ OK ] BridgedCastFolding.NSNumber (Int) -> String. Crashing test. expectCrashLater() do { @@ -393,7 +393,7 @@ Tests.test("String -> NSNumber. Crashing Test Case") { // CHECK: [ OK ] BridgedCastFolding.String -> NSNumber. Crashing Test Case // CHECK-OPT-LABEL: [ RUN ] BridgedCastFolding.String -> NSNumber. Crashing Test Case - // CHECK-OPT: stderr>>> OK: saw expected "crashed: sig{{ill|trap}}" + // CHECK-OPT: stderr>>> OK: saw expected "crashed: sigabrt" // CHECK-OPT: [ OK ] BridgedCastFolding.String -> NSNumber. Crashing Test Case expectCrashLater() do { diff --git a/test/SILOptimizer/cast_folding_no_bridging.sil b/test/SILOptimizer/cast_folding_no_bridging.sil index d9a85e9810d1f..cdc2e8e4fc59f 100644 --- a/test/SILOptimizer/cast_folding_no_bridging.sil +++ b/test/SILOptimizer/cast_folding_no_bridging.sil @@ -64,10 +64,8 @@ bb3: sil @fail : $@convention(thin) () -> Never // CHECK-LABEL: sil {{.*}}@testCFToObjC -// CHECK: bb0( -// CHECK-NEXT: [[T0:%.*]] = load %1 : $*CFString -// CHECK-NEXT: [[T1:%.*]] = unchecked_ref_cast [[T0]] : $CFString to $NSString -// CHECK-NEXT: store [[T1]] to %0 : $*NSString +// CHECK: checked_cast_addr_br +// CHECK: } // end sil function 'testCFToObjC' sil @testCFToObjC : $@convention(thin) (@in CFString) -> @out NSString { bb0(%0 : $*NSString, %1 : $*CFString): checked_cast_addr_br take_always CFString in %1 : $*CFString to NSString in %0 : $*NSString, bb1, bb2 @@ -83,11 +81,8 @@ bb2: } // CHECK-LABEL: sil {{.*}}@testCFToSwift -// CHECK: bb0( -// CHECK-NEXT: [[T0:%.*]] = load %1 : $*CFString -// CHECK-NEXT: [[T1:%.*]] = unchecked_ref_cast [[T0]] : $CFString to $NSString -// CHECK: [[FN:%.*]] = function_ref @$sSS10FoundationE34_conditionallyBridgeFromObjectiveC_6resultSbSo8NSStringC_SSSgztFZ : $@convention(method) (@guaranteed NSString, @inout Optional, @thin String.Type) -> Bool -// CHECK: apply [[FN]]([[T1]], {{.*}}, {{.*}}) +// CHECK: checked_cast +// CHECK: } // end sil function 'testCFToSwift' sil @testCFToSwift : $@convention(thin) (@in CFString) -> @out String { bb0(%0 : $*String, %1 : $*CFString): checked_cast_addr_br take_always CFString in %1 : $*CFString to String in %0 : $*String, bb1, bb2 diff --git a/test/SILOptimizer/cast_folding_objc.swift b/test/SILOptimizer/cast_folding_objc.swift index dfccaa25f7174..026cd55401e16 100644 --- a/test/SILOptimizer/cast_folding_objc.swift +++ b/test/SILOptimizer/cast_folding_objc.swift @@ -77,12 +77,9 @@ public func castObjCToSwift(_ t: T) -> Int { return t as! Int } -// Check that compiler understands that this cast always fails // CHECK-LABEL: sil [noinline] {{.*}}@$s17cast_folding_objc37testFailingBridgedCastFromObjCtoSwiftySiSo8NSStringCF -// CHECK: [[ONE:%[0-9]+]] = integer_literal $Builtin.Int1, -1 -// CHECK: cond_fail [[ONE]] : $Builtin.Int1, "failed cast" -// CHECK-NEXT: unreachable -// CHECK-NEXT: } +// CHECK: unconditional_checked_cast %0 : $NSString to NSNumber +// CHECK: } // end sil function '$s17cast_folding_objc37testFailingBridgedCastFromObjCtoSwiftySiSo8NSStringCF' @inline(never) public func testFailingBridgedCastFromObjCtoSwift(_ ns: NSString) -> Int { return castObjCToSwift(ns)