Skip to content

Commit f29d049

Browse files
authored
In backwards compatibility mode, be more permissive of Obj-C null references (#35825)
The new cast logic checks and aborts if a non-nullable pointer has a null value at runtime. However, because this was tolerated by the old casting logic, some apps inadvertently rely on being able to cast such null references. This change adds specific checks for null pointers in compatibility mode and handles them similarly to the previous casting logic: * Casting to Obj-C or CF type: casting a null pointer succeeds * Casting to class-constrained existential: casting a null pointer succeeds * Casting to a Swift class type: cast fails without crashing * Bridging from Obj-C class to Swift struct type: cast fails without crashing This also adds a guard to avoid trying to lookup the dynamic type of a null class reference. In non-compatibility mode, all of the above cause an immediate runtime crash. The changes here are only intended to support existing binaries running against new Swift runtimes. Resolves rdar://72323929
1 parent bd2064a commit f29d049

File tree

1 file changed

+89
-16
lines changed

1 file changed

+89
-16
lines changed

stdlib/public/runtime/DynamicCast.cpp

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ static bool unexpectedNullIsFatal() {
116116
return true; // Placeholder for an upcoming check.
117117
}
118118

119+
/// This issues a fatal error or warning if the srcValue contains a null object
120+
/// reference. It is used when the srcType is a non-nullable reference type, in
121+
/// which case it is dangerous to continue with a null reference. The null
122+
/// reference is returned if we're operating in backwards-compatibility mode, so
123+
/// callers still have to check for null.
119124
static HeapObject * getNonNullSrcObject(OpaqueValue *srcValue,
120125
const Metadata *srcType,
121126
const Metadata *destType) {
@@ -130,11 +135,17 @@ static HeapObject * getNonNullSrcObject(OpaqueValue *srcValue,
130135
" while trying to cast value of type '%s' (%p)"
131136
" to '%s' (%p)%s\n";
132137
if (unexpectedNullIsFatal()) {
138+
// By default, Swift 5.4 and later issue a fatal error.
133139
swift::fatalError(/* flags = */ 0, msg,
134140
srcTypeName.c_str(), srcType,
135141
destTypeName.c_str(), destType,
136142
"");
137143
} else {
144+
// In backwards compatibility mode, this code will warn and return the null
145+
// reference anyway: If you examine the calls to the function, you'll see
146+
// that most callers fail the cast in that case, but a few casts (e.g., with
147+
// Obj-C or CF destination type) sill succeed in that case. This is
148+
// dangerous, but necessary for compatibility.
138149
swift::warning(/* flags = */ 0, msg,
139150
srcTypeName.c_str(), srcType,
140151
destTypeName.c_str(), destType,
@@ -325,7 +336,9 @@ tryCastFromClassToObjCBridgeable(
325336
_getBridgedObjectiveCType(MetadataState::Complete, destType,
326337
destBridgeWitness).Value;
327338
void *srcObject = getNonNullSrcObject(srcValue, srcType, destType);
328-
if (nullptr == swift_dynamicCastUnknownClass(srcObject, targetBridgedClass)) {
339+
// Note: srcObject can be null here in compatibility mode
340+
if (nullptr == srcObject
341+
|| nullptr == swift_dynamicCastUnknownClass(srcObject, targetBridgedClass)) {
329342
destFailureType = targetBridgedClass;
330343
return DynamicCastResult::Failure;
331344
}
@@ -437,8 +450,12 @@ tryCastToSwiftClass(
437450
switch (srcType->getKind()) {
438451
case MetadataKind::Class: // Swift class => Swift class
439452
case MetadataKind::ObjCClassWrapper: { // Obj-C class => Swift class
440-
void *object = getNonNullSrcObject(srcValue, srcType, destType);
441-
if (auto t = swift_dynamicCastClass(object, destClassType)) {
453+
void *srcObject = getNonNullSrcObject(srcValue, srcType, destType);
454+
// Note: srcObject can be null in compatibility mode.
455+
if (srcObject == nullptr) {
456+
return DynamicCastResult::Failure;
457+
}
458+
if (auto t = swift_dynamicCastClass(srcObject, destClassType)) {
442459
auto castObject = const_cast<void *>(t);
443460
*(reinterpret_cast<void **>(destLocation)) = castObject;
444461
if (takeOnSuccess) {
@@ -480,6 +497,17 @@ tryCastToObjectiveCClass(
480497
case MetadataKind::ObjCClassWrapper: // Obj-C class => Obj-C class
481498
case MetadataKind::ForeignClass: { // CF class => Obj-C class
482499
auto srcObject = getNonNullSrcObject(srcValue, srcType, destType);
500+
// If object is null, then we're in the compatibility mode.
501+
// Earlier cast logic always succeeded `as!` casts of nil
502+
// class references but failed `as?` and `is`
503+
if (srcObject == nullptr) {
504+
if (mayDeferChecks) {
505+
*reinterpret_cast<const void **>(destLocation) = nullptr;
506+
return DynamicCastResult::SuccessViaCopy;
507+
} else {
508+
return DynamicCastResult::Failure;
509+
}
510+
}
483511
auto destObjCClass = destObjCType->Class;
484512
if (auto resultObject
485513
= swift_dynamicCastObjCClass(srcObject, destObjCClass)) {
@@ -519,6 +547,19 @@ tryCastToForeignClass(
519547
case MetadataKind::ObjCClassWrapper: // Obj-C class => CF class
520548
case MetadataKind::ForeignClass: { // CF class => CF class
521549
auto srcObject = getNonNullSrcObject(srcValue, srcType, destType);
550+
// If srcObject is null, then we're in compatibility mode.
551+
// Earlier cast logic always succeeded `as!` casts of nil
552+
// class references. Yes, this is very dangerous, which
553+
// is why we no longer permit it.
554+
if (srcObject == nullptr) {
555+
if (mayDeferChecks) {
556+
*reinterpret_cast<const void **>(destLocation) = nullptr;
557+
return DynamicCastResult::SuccessViaCopy;
558+
} else {
559+
// `as?` and `is` checks always fail on nil sources
560+
return DynamicCastResult::Failure;
561+
}
562+
}
522563
if (auto resultObject
523564
= swift_dynamicCastForeignClass(srcObject, destClassType)) {
524565
*reinterpret_cast<const void **>(destLocation) = resultObject;
@@ -694,6 +735,10 @@ struct ObjCBridgeMemo {
694735
// Use the dynamic ISA type of the object always (Note that this
695736
// also implicitly gives us the ObjC type for a CF object.)
696737
void *srcObject = getNonNullSrcObject(srcValue, srcType, destType);
738+
// If srcObject is null, then we're in backwards compatibility mode.
739+
if (srcObject == nullptr) {
740+
return DynamicCastResult::Failure;
741+
}
697742
Class srcObjCType = object_getClass((id)srcObject);
698743
// Fail if the ObjC object is not a subclass of the bridge class.
699744
while (srcObjCType != targetBridgedObjCClass) {
@@ -1417,15 +1462,26 @@ tryCastToClassExistential(
14171462
case MetadataKind::ObjCClassWrapper:
14181463
case MetadataKind::Class:
14191464
case MetadataKind::ForeignClass: {
1420-
auto object = getNonNullSrcObject(srcValue, srcType, destType);
1465+
auto srcObject = getNonNullSrcObject(srcValue, srcType, destType);
1466+
// If srcObject is null, then we're in compatibility mode.
1467+
// Earlier cast logic always succeeded `as!` casts of nil
1468+
// class references:
1469+
if (srcObject == nullptr) {
1470+
if (mayDeferChecks) {
1471+
*reinterpret_cast<const void **>(destLocation) = nullptr;
1472+
return DynamicCastResult::SuccessViaCopy;
1473+
} else {
1474+
return DynamicCastResult::Failure;
1475+
}
1476+
}
14211477
if (_conformsToProtocols(srcValue, srcType,
14221478
destExistentialType,
14231479
destExistentialLocation->getWitnessTables())) {
1424-
destExistentialLocation->Value = object;
1480+
destExistentialLocation->Value = srcObject;
14251481
if (takeOnSuccess) {
14261482
return DynamicCastResult::SuccessViaTake;
14271483
} else {
1428-
swift_unknownObjectRetain(object);
1484+
swift_unknownObjectRetain(srcObject);
14291485
return DynamicCastResult::SuccessViaCopy;
14301486
}
14311487
}
@@ -1687,8 +1743,14 @@ tryCastToMetatype(
16871743
case MetadataKind::ObjCClassWrapper: {
16881744
#if SWIFT_OBJC_INTEROP
16891745
// Some classes are actually metatypes
1690-
void *object = getNonNullSrcObject(srcValue, srcType, destType);
1691-
if (auto metatype = _getUnknownClassAsMetatype(object)) {
1746+
void *srcObject = getNonNullSrcObject(srcValue, srcType, destType);
1747+
// If object is null, then we're in compatibility mode.
1748+
// Continuing here at all is dangerous, but that's what the
1749+
// pre-Swift-5.4 casting logic did.
1750+
if (srcObject == nullptr) {
1751+
return DynamicCastResult::Failure;
1752+
}
1753+
if (auto metatype = _getUnknownClassAsMetatype(srcObject)) {
16921754
auto srcInnerValue = reinterpret_cast<OpaqueValue *>(&metatype);
16931755
auto srcInnerType = swift_getMetatypeMetadata(metatype);
16941756
return tryCast(destLocation, destType, srcInnerValue, srcInnerType,
@@ -1798,6 +1860,12 @@ tryCastToExistentialMetatype(
17981860
// Some Obj-C classes are actually metatypes
17991861
#if SWIFT_OBJC_INTEROP
18001862
void *srcObject = getNonNullSrcObject(srcValue, srcType, destType);
1863+
// If srcObject is null, we're in compatibility mode.
1864+
// Continuing here at al is dangerous, but that's what the
1865+
// pre-Swift-5.4 casting logic did.
1866+
if (srcObject == nullptr) {
1867+
return DynamicCastResult::Failure;
1868+
}
18011869
if (auto metatype = _getUnknownClassAsMetatype(srcObject)) {
18021870
return _dynamicCastMetatypeToExistentialMetatype(
18031871
destLocation,
@@ -1962,14 +2030,19 @@ tryCast(
19622030
|| srcKind == MetadataKind::ObjCClassWrapper
19632031
|| srcKind == MetadataKind::ForeignClass) {
19642032
auto srcObject = getNonNullSrcObject(srcValue, srcType, destType);
1965-
auto srcDynamicType = swift_getObjectType(srcObject);
1966-
if (srcDynamicType != srcType) {
1967-
srcFailureType = srcDynamicType;
1968-
auto castResult = tryCastToDestType(
1969-
destLocation, destType, srcValue, srcDynamicType,
1970-
destFailureType, srcFailureType, takeOnSuccess, mayDeferChecks);
1971-
if (isSuccess(castResult)) {
1972-
return castResult;
2033+
// If srcObject is null, we're in compability mode.
2034+
// But we can't lookup dynamic type for a null class reference, so
2035+
// just skip this in that case.
2036+
if (srcObject != nullptr) {
2037+
auto srcDynamicType = swift_getObjectType(srcObject);
2038+
if (srcDynamicType != srcType) {
2039+
srcFailureType = srcDynamicType;
2040+
auto castResult = tryCastToDestType(
2041+
destLocation, destType, srcValue, srcDynamicType,
2042+
destFailureType, srcFailureType, takeOnSuccess, mayDeferChecks);
2043+
if (isSuccess(castResult)) {
2044+
return castResult;
2045+
}
19732046
}
19742047
}
19752048
}

0 commit comments

Comments
 (0)