Skip to content

Commit 2863f19

Browse files
committed
[Runtime] Handle incomplete class metadata in _checkGenericRequirements.
When constructing the metadata for a type Gen<T : Super> where Super is a superclass constraint, the generic argument K at which the metadata for Gen is being instantiated is verified to be a subclass of Super via _checkGenericRequirements. Previously, that check was done using swift_dynamicCastMetatype. That worked for the most part but provided an incorrect answer if the metadata for K was not yet complete. These classes are incomplete more often thanks to __swift_instantiateConcreteTypeFromMangledNameAbstract. That issue occurred concretely in the following case: Framework with Library Evolution enabled: open class Super { ... } public struct Gen<T : Super> { } Target in a different resilience domain from that framework: class Sub : Super { var gen: Gen<Sub>? } Here, the mechanism for checking whether the generic argument K at which the metadata for Gen is being instantiated handles the case where K's metadata is incomplete. At worst, every superclass name from super(K) up to Super are demangled to instantiate metadata. A number of faster paths are included as well. rdar://problem/60790020
1 parent 5f8fece commit 2863f19

20 files changed

+588
-22
lines changed

include/swift/Runtime/Metadata.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,10 @@ SWIFT_CC(swift)
796796
SWIFT_RUNTIME_STDLIB_INTERNAL
797797
const Metadata *_swift_class_getSuperclass(const Metadata *theClass);
798798

799+
SWIFT_CC(swift)
800+
SWIFT_RUNTIME_STDLIB_INTERNAL MetadataResponse
801+
getSuperclassMetadata(MetadataRequest request, const ClassMetadata *self);
802+
799803
#if !NDEBUG
800804
/// Verify that the given metadata pointer correctly roundtrips its
801805
/// mangled name through the demangler.

stdlib/public/runtime/Metadata.cpp

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,19 +2735,14 @@ initGenericObjCClass(ClassMetadata *self, size_t numFields,
27352735
#endif
27362736

27372737
SWIFT_CC(swift)
2738-
static std::pair<MetadataDependency, const ClassMetadata *>
2739-
getSuperclassMetadata(ClassMetadata *self, bool allowDependency) {
2738+
SWIFT_RUNTIME_STDLIB_INTERNAL MetadataResponse
2739+
getSuperclassMetadata(MetadataRequest request, const ClassMetadata *self) {
27402740
// If there is a mangled superclass name, demangle it to the superclass
27412741
// type.
2742-
const ClassMetadata *super = nullptr;
27432742
if (auto superclassNameBase = self->getDescription()->SuperclassType.get()) {
27442743
StringRef superclassName =
27452744
Demangle::makeSymbolicMangledNameStringRef(superclassNameBase);
27462745
SubstGenericParametersFromMetadata substitutions(self);
2747-
MetadataRequest request(allowDependency
2748-
? MetadataState::NonTransitiveComplete
2749-
: /*FIXME*/ MetadataState::Abstract,
2750-
/*non-blocking*/ allowDependency);
27512746
MetadataResponse response =
27522747
swift_getTypeByMangledName(request, superclassName,
27532748
substitutions.getGenericArgs(),
@@ -2765,22 +2760,42 @@ getSuperclassMetadata(ClassMetadata *self, bool allowDependency) {
27652760
superclassName.str().c_str());
27662761
}
27672762

2768-
// If the request isn't satisfied, we have a new dependency.
2769-
if (!request.isSatisfiedBy(response.State)) {
2770-
assert(allowDependency);
2771-
return {MetadataDependency(superclass, request.getState()),
2772-
cast<ClassMetadata>(superclass)};
2773-
}
2763+
return response;
2764+
} else {
2765+
return MetadataResponse();
2766+
}
2767+
}
2768+
2769+
SWIFT_CC(swift)
2770+
static std::pair<MetadataDependency, const ClassMetadata *>
2771+
getSuperclassMetadata(ClassMetadata *self, bool allowDependency) {
2772+
MetadataRequest request(allowDependency ? MetadataState::NonTransitiveComplete
2773+
: /*FIXME*/ MetadataState::Abstract,
2774+
/*non-blocking*/ allowDependency);
2775+
auto response = getSuperclassMetadata(request, self);
2776+
2777+
auto *superclass = response.Value;
2778+
if (!superclass)
2779+
return {MetadataDependency(), nullptr};
27742780

2781+
const ClassMetadata *second;
27752782
#if SWIFT_OBJC_INTEROP
2776-
if (auto objcWrapper = dyn_cast<ObjCClassWrapperMetadata>(superclass))
2777-
superclass = objcWrapper->Class;
2783+
if (auto objcWrapper = dyn_cast<ObjCClassWrapperMetadata>(superclass)) {
2784+
second = objcWrapper->Class;
2785+
} else {
2786+
second = cast<ClassMetadata>(superclass);
2787+
}
2788+
#else
2789+
second = cast<ClassMetadata>(superclass);
27782790
#endif
27792791

2780-
super = cast<ClassMetadata>(superclass);
2792+
// If the request isn't satisfied, we have a new dependency.
2793+
if (!request.isSatisfiedBy(response.State)) {
2794+
assert(allowDependency);
2795+
return {MetadataDependency(superclass, request.getState()), second};
27812796
}
27822797

2783-
return {MetadataDependency(), super};
2798+
return {MetadataDependency(), second};
27842799
}
27852800

27862801
// Suppress diagnostic about the availability of _objc_realizeClassFromSwift.

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,137 @@ swift::_searchConformancesByMangledTypeName(Demangle::NodePointer node) {
665665
return nullptr;
666666
}
667667

668+
static MetadataState
669+
tryGetCompleteMetadataNonblocking(const Metadata *metadata) {
670+
return swift_checkMetadataState(
671+
MetadataRequest(MetadataState::Complete, /*isNonBlocking*/ true),
672+
metadata)
673+
.State;
674+
}
675+
676+
template <typename HandleObjc>
677+
bool isSwiftClassMetadataSubclass(const ClassMetadata *subclass,
678+
const ClassMetadata *superclass,
679+
HandleObjc handleObjc) {
680+
assert(subclass);
681+
assert(superclass);
682+
683+
MetadataState subclassState = tryGetCompleteMetadataNonblocking(subclass);
684+
685+
do {
686+
if (subclassState == MetadataState::Complete) {
687+
// The subclass metadata is complete. That means not just that its
688+
// Superclass field is valid, but that the Superclass field of the
689+
// referenced class metadata is valid, and the Superclass field of the
690+
// class metadata referenced there, and so on transitively.
691+
//
692+
// Scan the superclass chains in the ClassMetadata looking for a match.
693+
while ((subclass = subclass->Superclass)) {
694+
if (subclass == superclass)
695+
return true;
696+
}
697+
return false;
698+
}
699+
if (subclassState == MetadataState::NonTransitiveComplete) {
700+
// The subclass metadata is complete, but, unlike above, not transitively.
701+
// Its Superclass field is valid, so just read that field to get to the
702+
// superclass to proceed to the next step.
703+
subclass = subclass->Superclass;
704+
if (subclass->isPureObjC()) {
705+
return handleObjc(subclass, superclass);
706+
}
707+
subclassState = tryGetCompleteMetadataNonblocking(subclass);
708+
} else {
709+
// The subclass metadata is either LayoutComplete or Abstract, so the
710+
// Superclass field is not valid. To get to the superclass, make the
711+
// expensive call to getSuperclassMetadata which demangles the superclass
712+
// name from the nominal type descriptor to get the metadata for the
713+
// superclass.
714+
MetadataRequest request(MetadataState::Complete,
715+
/*non-blocking*/ true);
716+
auto response = getSuperclassMetadata(request, subclass);
717+
auto newMetadata = response.Value;
718+
if (auto newSubclass = dyn_cast<ClassMetadata>(newMetadata)) {
719+
subclass = newSubclass;
720+
subclassState = response.State;
721+
} else {
722+
return handleObjc(newMetadata, superclass);
723+
}
724+
}
725+
if (subclass == superclass)
726+
return true;
727+
} while (subclass);
728+
return false;
729+
}
730+
731+
// Whether the provided `subclass` is metadata for a subclass* of the superclass
732+
// whose metadata is specified.
733+
//
734+
// The function is robust against incomplete metadata for both subclass and
735+
// superclass. In the worst case, each intervening class between subclass and
736+
// superclass is demangled. Besides that slow path, there are a number of fast
737+
// paths:
738+
// - both classes are ObjC: swift_dynamicCastMetatype
739+
// - Complete subclass metadata: loop over Superclass fields
740+
// - NonTransitiveComplete: read the Superclass field once
741+
//
742+
// * A non-strict subclass; that is, given a class X, isSubclass(X.self, X.self)
743+
// is true.
744+
static bool isSubclass(const Metadata *subclass, const Metadata *superclass) {
745+
assert(subclass);
746+
assert(superclass);
747+
assert(subclass->isAnyClass());
748+
assert(superclass->isAnyClass());
749+
750+
if (subclass == superclass)
751+
return true;
752+
if (!isa<ClassMetadata>(subclass)) {
753+
if (!isa<ClassMetadata>(superclass)) {
754+
// Only ClassMetadata can be incomplete; when the class metadata is not
755+
// ClassMetadata, just use swift_dynamicCastMetatype.
756+
return swift_dynamicCastMetatype(subclass, superclass);
757+
} else {
758+
// subclass is ObjC, but superclass is not; since it is not possible for
759+
// any ObjC class to be a subclass of any Swift class, this subclass is
760+
// not a subclass of this superclass.
761+
return false;
762+
}
763+
}
764+
const ClassMetadata *swiftSubclass = cast<ClassMetadata>(subclass);
765+
if (auto *objcSuperclass = dyn_cast<ObjCClassWrapperMetadata>(superclass)) {
766+
// Walk up swiftSubclass's ancestors until we get to an ObjC class, then
767+
// kick over to swift_dynamicCastMetatype.
768+
return isSwiftClassMetadataSubclass(
769+
swiftSubclass, objcSuperclass->Class,
770+
[](const Metadata *intermediate, const Metadata *superclass) {
771+
// Intermediate is an ObjC class, and superclass is an ObjC class;
772+
// as above, just use swift_dynamicCastMetatype.
773+
return swift_dynamicCastMetatype(intermediate, superclass);
774+
});
775+
return false;
776+
}
777+
if (isa<ForeignClassMetadata>(superclass)) {
778+
// superclass is foreign, but subclass is not (if it were, the above
779+
// !isa<ClassMetadata> condition would have been entered). Since it is not
780+
// possible for any Swift class to be a subclass of any foreign superclass,
781+
// this subclass is not a subclass of this superclass.
782+
return false;
783+
}
784+
auto swiftSuperclass = cast<ClassMetadata>(superclass);
785+
return isSwiftClassMetadataSubclass(swiftSubclass, swiftSuperclass,
786+
[](const Metadata *, const Metadata *) {
787+
// Because (1) no ObjC classes inherit
788+
// from Swift classes and (2)
789+
// `superclass` is not ObjC, if some
790+
// ancestor of `subclass` is ObjC, then
791+
// `subclass` cannot descend from
792+
// `superclass` (otherwise at some point
793+
// some ObjC class would have to inherit
794+
// from a Swift class).
795+
return false;
796+
});
797+
}
798+
668799
bool swift::_checkGenericRequirements(
669800
llvm::ArrayRef<GenericRequirementDescriptor> requirements,
670801
SmallVectorImpl<const void *> &extraArguments,
@@ -738,11 +869,8 @@ bool swift::_checkGenericRequirements(
738869
substGenericParam, substWitnessTable).getMetadata();
739870
if (!baseType) return true;
740871

741-
// Check whether it's dynamically castable, which works as a superclass
742-
// check.
743-
// FIXME: We should be explicitly checking the superclass, so we
744-
// don't require the subject type to be complete.
745-
if (!swift_dynamicCastMetatype(subjectType, baseType)) return true;
872+
if (!isSubclass(subjectType, baseType))
873+
return true;
746874

747875
continue;
748876
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@interface Subclass : NSObject
4+
5+
@end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@interface Subclass : NSObject
4+
5+
@end
6+
7+
@implementation Subclass
8+
@end

test/Runtime/Inputs/Superclass.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@interface Superclass : NSObject
4+
@end

test/Runtime/Inputs/Superclass.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@interface Superclass : NSObject
4+
@end
5+
6+
@implementation Superclass
7+
@end
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Framework
2+
3+
open class Subclass1 : Superclass {
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// CHECK: Subclass
2+
print(Subclass().self)
3+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Foundation
2+
3+
public struct Gen<Subclass : NSObject> {
4+
public init() {
5+
}
6+
}

0 commit comments

Comments
 (0)