Skip to content

Commit 94287ea

Browse files
committed
AST: Fix bad interaction between vtable layout, access control and -enable-testing
Swift allows a method override to be more visible than the base method. In practice, this means that since it might be possible for client code to see the override but not the base method, we have to take extra care when emitting the override. Specifically, the override always receives a new vtable entry, and a vtable thunk is emitted in place of the base method's vtable entry which re-dispatches via the override's vtable entry. This allows client code to further override the method without any knowledge of the base method's vtable entry, which may be inaccessible to the client. In order for the above to work, three places in the code perform co-ordinated checks: - needsNewVTableEntry() determines whether the override is more visible than the base, in which case it receives a new vtable entry - SILGenModule::emitVTableMethod() performs the same check in order to emit the re-dispatching vtable thunk in place of the base method's entry - in the client, SILVTableVisitor then skips the base method's vtable entry entirely when emitting the derived class, since no thunk is to be emitted. The problem was that the first two used effective access (where internal declarations become public with -enable-testing), while the last check used formal access. As a result, it was possible for the method override vtable entry to never be emitted in the client. Consistently using either effective access or formal access would fix the problem. I fixed the first two to rely on formal access; the reason is that using effective access makes vtable layout depend on whether the library was built with -enable-testing or not, which is undesirable since we do not want -enable-testing to impact the ABI, even for non-resilient frameworks. Fixes rdar://problem/74108928.
1 parent bcfafc1 commit 94287ea

File tree

8 files changed

+213
-6
lines changed

8 files changed

+213
-6
lines changed

include/swift/AST/Decl.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2321,10 +2321,8 @@ class ValueDecl : public Decl {
23212321
/// dynamic methods on generic classes (see above).
23222322
bool isNativeMethodReplacement() const;
23232323

2324-
bool isEffectiveLinkageMoreVisibleThan(ValueDecl *other) const {
2325-
return (std::min(getEffectiveAccess(), AccessLevel::Public) >
2326-
std::min(other->getEffectiveAccess(), AccessLevel::Public));
2327-
}
2324+
/// Returns if this declaration has more visible formal access than 'other'.
2325+
bool isMoreVisibleThan(ValueDecl *other) const;
23282326

23292327
/// Set whether this type is 'dynamic' or not.
23302328
void setIsDynamic(bool value);

lib/AST/Decl.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3417,6 +3417,23 @@ static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
34173417
llvm_unreachable("bad access level");
34183418
}
34193419

3420+
bool ValueDecl::isMoreVisibleThan(ValueDecl *other) const {
3421+
auto scope = getFormalAccessScope();
3422+
3423+
// 'other' may have come from a @testable import, so we need to upgrade it's
3424+
// visibility to public here. That is not the same as whether 'other' is
3425+
// being built with -enable-testing though -- we don't want to treat it
3426+
// differently in that case.
3427+
auto otherScope = other->getFormalAccessScope(getDeclContext());
3428+
3429+
if (scope.isPublic())
3430+
return !otherScope.isPublic();
3431+
else if (scope.isInternal())
3432+
return !otherScope.isPublic() && !otherScope.isInternal();
3433+
else
3434+
return false;
3435+
}
3436+
34203437
bool ValueDecl::isAccessibleFrom(const DeclContext *useDC,
34213438
bool forConformance,
34223439
bool allowUsableFromInline) const {

lib/SILGen/SILGenType.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
111111
bool baseLessVisibleThanDerived =
112112
(!usesObjCDynamicDispatch &&
113113
!derivedDecl->isFinal() &&
114-
derivedDecl->isEffectiveLinkageMoreVisibleThan(baseDecl));
114+
derivedDecl->isMoreVisibleThan(baseDecl));
115115

116116
// Determine the derived thunk type by lowering the derived type against the
117117
// abstraction pattern of the base.

lib/Sema/TypeCheckDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,7 @@ NeedsNewVTableEntryRequest::evaluate(Evaluator &evaluator,
10221022
// If the base is less visible than the override, we might need a vtable
10231023
// entry since callers of the override might not be able to see the base
10241024
// at all.
1025-
if (decl->isEffectiveLinkageMoreVisibleThan(base))
1025+
if (decl->isMoreVisibleThan(base))
10261026
return true;
10271027

10281028
using Direction = ASTContext::OverrideGenericSignatureReqCheck;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
open class Base {
2+
public init() {}
3+
4+
internal func method() -> Int {
5+
return 1
6+
}
7+
}
8+
9+
open class Middle : Base {
10+
open override func method() -> Int {
11+
return super.method() + 1
12+
}
13+
}
14+
15+
public func callBaseMethod(_ b: Base) -> Int {
16+
return b.method()
17+
}
18+
19+
public func callMiddleMethod(_ m: Middle) -> Int {
20+
return m.method()
21+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// We test various combinations to make sure that -enable-testing does not
2+
// break ABI with or without -enable-library-evolution.
3+
4+
////
5+
6+
// RUN: %empty-directory(%t)
7+
8+
// 1) -enable-testing OFF / -enable-library-evolution OFF
9+
10+
// RUN: %target-build-swift-dylib(%t/%target-library-name(vtables_multifile_testable_helper)) %S/Inputs/vtables_multifile_testable_helper.swift -emit-module -emit-module-path %t/vtables_multifile_testable_helper.swiftmodule
11+
// RUN: %target-codesign %t/%target-library-name(vtables_multifile_testable_helper)
12+
13+
// RUN: %target-build-swift %s -L %t -I %t -lvtables_multifile_testable_helper -o %t/main %target-rpath(%t)
14+
// RUN: %target-codesign %t/main
15+
16+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
17+
18+
// 2) -enable-testing ON / -enable-library-evolution OFF
19+
20+
// ... first without rebuilding the client:
21+
22+
// RUN: %target-build-swift-dylib(%t/%target-library-name(vtables_multifile_testable_helper)) %S/Inputs/vtables_multifile_testable_helper.swift -enable-testing -emit-module -emit-module-path %t/vtables_multifile_testable_helper.swiftmodule
23+
// RUN: %target-codesign %t/%target-library-name(vtables_multifile_testable_helper)
24+
25+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
26+
27+
// ... now try to rebuild the client:
28+
29+
// RUN: %target-build-swift %s -L %t -I %t -lvtables_multifile_testable_helper -o %t/main %target-rpath(%t)
30+
// RUN: %target-codesign %t/main
31+
32+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
33+
34+
////
35+
36+
// Delete build artifacts
37+
// RUN: %empty-directory(%t)
38+
39+
// 3) -enable-testing OFF / -enable-library-evolution ON
40+
41+
// RUN: %target-build-swift-dylib(%t/%target-library-name(vtables_multifile_testable_helper)) %S/Inputs/vtables_multifile_testable_helper.swift -enable-library-evolution -emit-module -emit-module-path %t/vtables_multifile_testable_helper.swiftmodule
42+
// RUN: %target-codesign %t/%target-library-name(vtables_multifile_testable_helper)
43+
44+
// RUN: %target-build-swift %s -L %t -I %t -lvtables_multifile_testable_helper -o %t/main %target-rpath(%t)
45+
// RUN: %target-codesign %t/main
46+
47+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
48+
49+
// 4) -enable-testing ON / -enable-library-evolution ON
50+
51+
// ... first without rebuilding the client:
52+
53+
// RUN: %target-build-swift-dylib(%t/%target-library-name(vtables_multifile_testable_helper)) %S/Inputs/vtables_multifile_testable_helper.swift -enable-testing -enable-library-evolution -emit-module -emit-module-path %t/vtables_multifile_testable_helper.swiftmodule
54+
// RUN: %target-codesign %t/%target-library-name(vtables_multifile_testable_helper)
55+
56+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
57+
58+
// ... now try to rebuild the client:
59+
60+
// RUN: %target-build-swift %s -L %t -I %t -lvtables_multifile_testable_helper -o %t/main %target-rpath(%t)
61+
// RUN: %target-codesign %t/main
62+
63+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
64+
65+
////
66+
67+
// Delete build artifacts
68+
// RUN: %empty-directory(%t)
69+
70+
// 5) -enable-testing OFF / -enable-library-evolution ON / textual interfaces
71+
72+
// RUN: %target-build-swift-dylib(%t/%target-library-name(vtables_multifile_testable_helper)) %S/Inputs/vtables_multifile_testable_helper.swift -enable-library-evolution -emit-module-interface -emit-module-interface-path %t/vtables_multifile_testable_helper.swiftinterface
73+
// RUN: %target-codesign %t/%target-library-name(vtables_multifile_testable_helper)
74+
75+
// RUN: %target-build-swift %s -L %t -I %t -lvtables_multifile_testable_helper -o %t/main %target-rpath(%t)
76+
// RUN: %target-codesign %t/main
77+
78+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
79+
80+
// 6) -enable-testing ON / -enable-library-evolution ON / textual interfaces
81+
82+
// ... first without rebuilding the client:
83+
84+
// RUN: %target-build-swift-dylib(%t/%target-library-name(vtables_multifile_testable_helper)) %S/Inputs/vtables_multifile_testable_helper.swift -enable-testing -enable-library-evolution -emit-module-interface -emit-module-interface-path %t/vtables_multifile_testable_helper.swiftinterface
85+
// RUN: %target-codesign %t/%target-library-name(vtables_multifile_testable_helper)
86+
87+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
88+
89+
// ... now try to rebuild the client:
90+
91+
// RUN: %target-build-swift %s -L %t -I %t -lvtables_multifile_testable_helper -o %t/main %target-rpath(%t)
92+
// RUN: %target-codesign %t/main
93+
94+
// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_testable_helper)
95+
96+
97+
// REQUIRES: executable_test
98+
99+
import StdlibUnittest
100+
import vtables_multifile_testable_helper
101+
102+
var VTableTestSuite = TestSuite("VTable")
103+
104+
public class Derived : Middle {
105+
public override func method() -> Int {
106+
return super.method() + 1
107+
}
108+
}
109+
110+
VTableTestSuite.test("Derived") {
111+
expectEqual(3, callBaseMethod(Derived()))
112+
expectEqual(3, callMiddleMethod(Derived()))
113+
}
114+
115+
runAllTests()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
open class Base {
2+
internal func method() {}
3+
}
4+
5+
open class Middle : Base {
6+
open override func method() {}
7+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-swift-frontend -emit-silgen %S/Inputs/accessibility_vtables_testable_helper.swift | %FileCheck %s --check-prefix=LIBRARY
4+
// RUN: %target-swift-frontend -enable-library-evolution -emit-silgen %S/Inputs/accessibility_vtables_testable_helper.swift | %FileCheck %s --check-prefix=LIBRARY
5+
// RUN: %target-swift-frontend -emit-silgen %S/Inputs/accessibility_vtables_testable_helper.swift | %FileCheck %s --check-prefix=LIBRARY
6+
// RUN: %target-swift-frontend -enable-library-evolution -emit-silgen %S/Inputs/accessibility_vtables_testable_helper.swift | %FileCheck %s --check-prefix=LIBRARY
7+
8+
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/accessibility_vtables_testable_helper.swift
9+
// RUN: %target-swift-emit-silgen -primary-file %s -I %t | %FileCheck %s --check-prefix=FRAGILE-CLIENT
10+
11+
// RUN: %target-swift-frontend -emit-module -enable-testing -o %t %S/Inputs/accessibility_vtables_testable_helper.swift
12+
// RUN: %target-swift-emit-silgen -primary-file %s -I %t | %FileCheck %s --check-prefix=FRAGILE-CLIENT
13+
14+
// RUN: %target-swift-frontend -enable-library-evolution -emit-module -o %t %S/Inputs/accessibility_vtables_testable_helper.swift
15+
// RUN: %target-swift-emit-silgen -primary-file %s -I %t | %FileCheck %s --check-prefix=RESILIENT-CLIENT
16+
17+
// RUN: %target-swift-frontend -enable-library-evolution -emit-module -enable-testing -o %t %S/Inputs/accessibility_vtables_testable_helper.swift
18+
// RUN: %target-swift-emit-silgen -primary-file %s -I %t | %FileCheck %s --check-prefix=RESILIENT-CLIENT
19+
20+
import accessibility_vtables_testable_helper
21+
22+
public class Derived : Middle {
23+
open override func method() {}
24+
}
25+
26+
// LIBRARY-LABEL: sil_vtable {{(\[serialized\] )?}}Base {
27+
// LIBRARY-NEXT: #Base.method: (Base) -> () -> () : @$s37accessibility_vtables_testable_helper4BaseC6methodyyF
28+
// LIBRARY-NEXT: #Base.init!allocator: (Base.Type) -> () -> Base : @$s37accessibility_vtables_testable_helper4BaseCACycfC
29+
// LIBRARY-NEXT: #Base.deinit!deallocator: @$s37accessibility_vtables_testable_helper4BaseCfD
30+
// LIBRARY-NEXT: }
31+
32+
// LIBRARY-LABEL: sil_vtable {{(\[serialized\] )?}}Middle {
33+
// LIBRARY-NEXT: #Base.method: (Base) -> () -> () : @$s37accessibility_vtables_testable_helper6MiddleC6methodyyFAA4BaseCADyyFTV [override]
34+
// LIBRARY-NEXT: #Base.init!allocator: (Base.Type) -> () -> Base : @$s37accessibility_vtables_testable_helper6MiddleCACycfC [override]
35+
// LIBRARY-NEXT: #Middle.method: (Middle) -> () -> () : @$s37accessibility_vtables_testable_helper6MiddleC6methodyyF
36+
// LIBRARY-NEXT: #Middle.deinit!deallocator: @$s37accessibility_vtables_testable_helper6MiddleCfD
37+
// LIBRARY-NEXT: }
38+
39+
// FRAGILE-CLIENT-LABEL: sil_vtable [serialized] Derived {
40+
// FRAGILE-CLIENT-NEXT: #Base.method: (Base) -> () -> () : @$s37accessibility_vtables_testable_helper6MiddleC6methodyyFAA4BaseCADyyFTV [inherited]
41+
// FRAGILE-CLIENT-NEXT: #Base.init!allocator: (Base.Type) -> () -> Base : @$s37accessibility_vtables_testable_helper6MiddleCACycfC [inherited]
42+
// FRAGILE-CLIENT-NEXT: #Middle.method: (Middle) -> () -> () : @$s30accessibility_vtables_testable7DerivedC6methodyyF [override]
43+
// FRAGILE-CLIENT-NEXT: #Derived.deinit!deallocator: @$s30accessibility_vtables_testable7DerivedCfD
44+
// FRAGILE-CLIENT-NEXT: }
45+
46+
// RESILIENT-CLIENT-LABEL: sil_vtable [serialized] Derived {
47+
// RESILIENT-CLIENT-NEXT: #Middle.method: (Middle) -> () -> () : @$s30accessibility_vtables_testable7DerivedC6methodyyF [override] // Derived.method()
48+
// RESILIENT-CLIENT-NEXT: #Derived.deinit!deallocator: @$s30accessibility_vtables_testable7DerivedCfD // Derived.__deallocating_deinit
49+
// RESILIENT-CLIENT-NEXT: }

0 commit comments

Comments
 (0)