Skip to content

Commit b9f4e12

Browse files
committed
SIL: Fix visibility of final method symbols in resilient classes
For resilient classes, we hide method symbols if possible, to enforce that only resilient access patterns are used to call and override methods. Final methods can be referenced directly, however, but we were incorrectly hiding them anyway if they were overrides. To consider overrides differently here only makes sense in the non-resilient case; in fact this was a regression from <rdar://problem/55559104>, which fixed a bug with non-resilient classes by adding this check. I tried to add comments and clean up the logic here a bit to be less confusing in the future. Fixes <rdar://problem/57864425>.
1 parent 7619c63 commit b9f4e12

File tree

2 files changed

+56
-25
lines changed

2 files changed

+56
-25
lines changed

lib/SIL/SILDeclRef.cpp

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -958,67 +958,94 @@ SubclassScope SILDeclRef::getSubclassScope() const {
958958
if (!isa<AbstractFunctionDecl>(decl))
959959
return SubclassScope::NotApplicable;
960960

961-
// If this declaration is a function which goes into a vtable, then it's
962-
// symbol must be as visible as its class, because derived classes have to put
963-
// all less visible methods of the base class into their vtables.
961+
DeclContext *context = decl->getDeclContext();
962+
963+
// Only methods in non-final classes go in the vtable.
964+
auto *classType = dyn_cast<ClassDecl>(context);
965+
if (!classType || classType->isFinal())
966+
return SubclassScope::NotApplicable;
967+
968+
// If a method appears in the vtable of a class, we must give it's symbol
969+
// special consideration when computing visibility because the SIL-level
970+
// linkage does not map to the symbol's visibility in a straightforward
971+
// way.
972+
//
973+
// In particular, the rules are:
974+
// - If the class metadata is not resilient, then all method symbols must
975+
// be visible from any translation unit where a subclass might be defined,
976+
// because the subclass metadata will re-emit all vtable entries.
977+
//
978+
// - For resilient classes, we do the opposite: generally, a method's symbol
979+
// can be hidden from other translation units, because we want to enforce
980+
// that resilient access patterns are used for method calls and overrides.
981+
//
982+
// Constructors and final methods are the exception here, because they can
983+
// be called directly.
984+
985+
// FIXME: This is too narrow. Any class with resilient metadata should
986+
// probably have this, at least for method overrides that don't add new
987+
// vtable entries.
988+
bool isResilientClass = classType->isResilient();
964989

965990
if (auto *CD = dyn_cast<ConstructorDecl>(decl)) {
991+
if (isResilientClass)
992+
return SubclassScope::NotApplicable;
966993
// Initializing entry points do not appear in the vtable.
967994
if (kind == SILDeclRef::Kind::Initializer)
968995
return SubclassScope::NotApplicable;
969-
// Non-required convenience inits do not apper in the vtable.
996+
// Non-required convenience inits do not appear in the vtable.
970997
if (!CD->isRequired() && !CD->isDesignatedInit())
971998
return SubclassScope::NotApplicable;
972999
} else if (isa<DestructorDecl>(decl)) {
973-
// Detructors do not appear in the vtable.
1000+
// Destructors do not appear in the vtable.
9741001
return SubclassScope::NotApplicable;
9751002
} else {
9761003
assert(isa<FuncDecl>(decl));
9771004
}
9781005

979-
DeclContext *context = decl->getDeclContext();
980-
981-
// Methods from extensions don't go in the vtable.
982-
if (isa<ExtensionDecl>(context))
983-
return SubclassScope::NotApplicable;
984-
985-
// Various forms of thunks don't either.
1006+
// Various forms of thunks don't go in the vtable.
9861007
if (isThunk() || isForeign)
9871008
return SubclassScope::NotApplicable;
9881009

9891010
// Default arg generators don't go in the vtable.
9901011
if (isDefaultArgGenerator())
9911012
return SubclassScope::NotApplicable;
9921013

993-
// Only methods in non-final classes go in the vtable.
994-
auto *classType = context->getSelfClassDecl();
995-
if (!classType || classType->isFinal())
996-
return SubclassScope::NotApplicable;
1014+
if (decl->isFinal()) {
1015+
// Final methods only go in the vtable if they override something.
1016+
if (!decl->getOverriddenDecl())
1017+
return SubclassScope::NotApplicable;
9971018

998-
// Final methods only go in the vtable if they override something.
999-
if (decl->isFinal() && !decl->getOverriddenDecl())
1000-
return SubclassScope::NotApplicable;
1019+
// In the resilient case, we're going to be making symbols _less_
1020+
// visible, so make sure we stop now; final methods can always be
1021+
// called directly.
1022+
if (isResilientClass)
1023+
return SubclassScope::Internal;
1024+
}
10011025

10021026
assert(decl->getEffectiveAccess() <= classType->getEffectiveAccess() &&
10031027
"class must be as visible as its members");
10041028

1005-
// FIXME: This is too narrow. Any class with resilient metadata should
1006-
// probably have this, at least for method overrides that don't add new
1007-
// vtable entries.
1008-
if (classType->isResilient()) {
1009-
if (isa<ConstructorDecl>(decl))
1010-
return SubclassScope::NotApplicable;
1029+
if (isResilientClass) {
1030+
// The symbol should _only_ be reached via the vtable, so we're
1031+
// going to make it hidden.
10111032
return SubclassScope::Resilient;
10121033
}
10131034

10141035
switch (classType->getEffectiveAccess()) {
10151036
case AccessLevel::Private:
10161037
case AccessLevel::FilePrivate:
1038+
// If the class is private, it can only be subclassed from the same
1039+
// SILModule, so we don't need to do anything.
10171040
return SubclassScope::NotApplicable;
10181041
case AccessLevel::Internal:
10191042
case AccessLevel::Public:
1043+
// If the class is internal or public, it can only be subclassed from
1044+
// the same AST Module, but possibly a different SILModule.
10201045
return SubclassScope::Internal;
10211046
case AccessLevel::Open:
1047+
// If the class is open, it can be subclassed from a different
1048+
// AST Module. All method symbols are public.
10221049
return SubclassScope::External;
10231050
}
10241051

test/IRGen/method_linkage.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ open class OpenSubclass : OpenClass {
118118
fileprivate final override var prop: () {
119119
return ()
120120
}
121+
122+
// CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc void @"$s14method_linkage12OpenSubclassC4pbazyyF"
123+
// RESILIENT: define{{( dllexport)?}}{{( protected)?}} swiftcc void @"$s14method_linkage12OpenSubclassC4pbazyyF"
124+
public final override func pbaz() {}
121125
}
122126

123127
// Just in case anyone wants to delete unused methods...

0 commit comments

Comments
 (0)