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
This commit is contained in:
Erik Eckstein
2025-04-30 16:41:00 +02:00
parent 79e0b6a7e0
commit 7a5dbdc20b
4 changed files with 19 additions and 18 deletions

View File

@@ -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);

View File

@@ -58,7 +58,7 @@ Tests.test("NSString => Array<Int>. Crashing test case") {
// CHECK: [ OK ] BridgedCastFolding.NSString => Array<Int>. Crashing test case
// CHECK-OPT-LABEL: [ RUN ] BridgedCastFolding.NSString => Array<Int>. 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<Int>. 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 {

View File

@@ -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<String>, @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

View File

@@ -77,12 +77,9 @@ public func castObjCToSwift<T>(_ 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)