Skip to content

Commit e08413c

Browse files
committed
[5.4] In backwards compatibility mode, be more permissive of Obj-C null references (#35825)
(This cherry-picks #35825 from main to release/5.4) 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 6e52b9a commit e08413c

File tree

1 file changed

+109
-21
lines changed

1 file changed

+109
-21
lines changed

stdlib/public/runtime/DynamicCast.cpp

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ extern "C" const StructDescriptor NOMINAL_TYPE_DESCR_SYM(Sh);
110110
/// Nominal type descriptor for Swift.String.
111111
extern "C" const StructDescriptor NOMINAL_TYPE_DESCR_SYM(SS);
112112

113+
// If this returns `true`, then we will call `fatalError` when we encounter a
114+
// null reference in a storage locaation whose type does not allow null.
115+
static bool unexpectedNullIsFatal() {
116+
return true; // Placeholder for an upcoming check.
117+
}
118+
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.
113124
static HeapObject * getNonNullSrcObject(OpaqueValue *srcValue,
114125
const Metadata *srcType,
115126
const Metadata *destType) {
@@ -120,12 +131,27 @@ static HeapObject * getNonNullSrcObject(OpaqueValue *srcValue,
120131

121132
std::string srcTypeName = nameForMetadata(srcType);
122133
std::string destTypeName = nameForMetadata(destType);
123-
swift::fatalError(/* flags = */ 0,
124-
"Found unexpected null pointer value"
134+
const char *msg = "Found unexpected null pointer value"
125135
" while trying to cast value of type '%s' (%p)"
126-
" to '%s' (%p)\n",
127-
srcTypeName.c_str(), srcType,
128-
destTypeName.c_str(), destType);
136+
" to '%s' (%p)%s\n";
137+
if (unexpectedNullIsFatal()) {
138+
// By default, Swift 5.4 and later issue a fatal error.
139+
swift::fatalError(/* flags = */ 0, msg,
140+
srcTypeName.c_str(), srcType,
141+
destTypeName.c_str(), destType,
142+
"");
143+
} 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.
149+
swift::warning(/* flags = */ 0, msg,
150+
srcTypeName.c_str(), srcType,
151+
destTypeName.c_str(), destType,
152+
": Continuing with null object, but expect problems later.");
153+
}
154+
return object;
129155
}
130156

131157
/******************************************************************************/
@@ -310,7 +336,9 @@ tryCastFromClassToObjCBridgeable(
310336
_getBridgedObjectiveCType(MetadataState::Complete, destType,
311337
destBridgeWitness).Value;
312338
void *srcObject = getNonNullSrcObject(srcValue, srcType, destType);
313-
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)) {
314342
destFailureType = targetBridgedClass;
315343
return DynamicCastResult::Failure;
316344
}
@@ -422,8 +450,12 @@ tryCastToSwiftClass(
422450
switch (srcType->getKind()) {
423451
case MetadataKind::Class: // Swift class => Swift class
424452
case MetadataKind::ObjCClassWrapper: { // Obj-C class => Swift class
425-
void *object = getNonNullSrcObject(srcValue, srcType, destType);
426-
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)) {
427459
auto castObject = const_cast<void *>(t);
428460
*(reinterpret_cast<void **>(destLocation)) = castObject;
429461
if (takeOnSuccess) {
@@ -465,6 +497,17 @@ tryCastToObjectiveCClass(
465497
case MetadataKind::ObjCClassWrapper: // Obj-C class => Obj-C class
466498
case MetadataKind::ForeignClass: { // CF class => Obj-C class
467499
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+
}
468511
auto destObjCClass = destObjCType->Class;
469512
if (auto resultObject
470513
= swift_dynamicCastObjCClass(srcObject, destObjCClass)) {
@@ -504,6 +547,19 @@ tryCastToForeignClass(
504547
case MetadataKind::ObjCClassWrapper: // Obj-C class => CF class
505548
case MetadataKind::ForeignClass: { // CF class => CF class
506549
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+
}
507563
if (auto resultObject
508564
= swift_dynamicCastForeignClass(srcObject, destClassType)) {
509565
*reinterpret_cast<const void **>(destLocation) = resultObject;
@@ -679,6 +735,10 @@ struct ObjCBridgeMemo {
679735
// Use the dynamic ISA type of the object always (Note that this
680736
// also implicitly gives us the ObjC type for a CF object.)
681737
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+
}
682742
Class srcObjCType = object_getClass((id)srcObject);
683743
// Fail if the ObjC object is not a subclass of the bridge class.
684744
while (srcObjCType != targetBridgedObjCClass) {
@@ -1402,15 +1462,26 @@ tryCastToClassExistential(
14021462
case MetadataKind::ObjCClassWrapper:
14031463
case MetadataKind::Class:
14041464
case MetadataKind::ForeignClass: {
1405-
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+
}
14061477
if (_conformsToProtocols(srcValue, srcType,
14071478
destExistentialType,
14081479
destExistentialLocation->getWitnessTables())) {
1409-
destExistentialLocation->Value = object;
1480+
destExistentialLocation->Value = srcObject;
14101481
if (takeOnSuccess) {
14111482
return DynamicCastResult::SuccessViaTake;
14121483
} else {
1413-
swift_unknownObjectRetain(object);
1484+
swift_unknownObjectRetain(srcObject);
14141485
return DynamicCastResult::SuccessViaCopy;
14151486
}
14161487
}
@@ -1672,8 +1743,14 @@ tryCastToMetatype(
16721743
case MetadataKind::ObjCClassWrapper: {
16731744
#if SWIFT_OBJC_INTEROP
16741745
// Some classes are actually metatypes
1675-
void *object = getNonNullSrcObject(srcValue, srcType, destType);
1676-
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)) {
16771754
auto srcInnerValue = reinterpret_cast<OpaqueValue *>(&metatype);
16781755
auto srcInnerType = swift_getMetatypeMetadata(metatype);
16791756
return tryCast(destLocation, destType, srcInnerValue, srcInnerType,
@@ -1783,6 +1860,12 @@ tryCastToExistentialMetatype(
17831860
// Some Obj-C classes are actually metatypes
17841861
#if SWIFT_OBJC_INTEROP
17851862
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+
}
17861869
if (auto metatype = _getUnknownClassAsMetatype(srcObject)) {
17871870
return _dynamicCastMetatypeToExistentialMetatype(
17881871
destLocation,
@@ -1947,14 +2030,19 @@ tryCast(
19472030
|| srcKind == MetadataKind::ObjCClassWrapper
19482031
|| srcKind == MetadataKind::ForeignClass) {
19492032
auto srcObject = getNonNullSrcObject(srcValue, srcType, destType);
1950-
auto srcDynamicType = swift_getObjectType(srcObject);
1951-
if (srcDynamicType != srcType) {
1952-
srcFailureType = srcDynamicType;
1953-
auto castResult = tryCastToDestType(
1954-
destLocation, destType, srcValue, srcDynamicType,
1955-
destFailureType, srcFailureType, takeOnSuccess, mayDeferChecks);
1956-
if (isSuccess(castResult)) {
1957-
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+
}
19582046
}
19592047
}
19602048
}

0 commit comments

Comments
 (0)