Skip to content

Commit 7c31f05

Browse files
committed
Package CMO: Prevent serializing types from SDK/system modules imported as @_implementationOnly.
Currently, types from @_implementationOnly modules can be serialized into client modules if the modules are SDK or system modules. However, @_implementationOnly is intended to hide types from external clients, and when combined with serialization, it may cause the type’s metadata (e.g., field offsets) to be stripped. This can lead to linker errors, especially in dynamic contexts like Codable, where runtime metadata is required for type resolution. This PR prevents all types imported with @_implementationOnly from being serialized. Resolves rdar://144181455
1 parent c14561f commit 7c31f05

File tree

2 files changed

+92
-25
lines changed

2 files changed

+92
-25
lines changed

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -793,11 +793,29 @@ bool CrossModuleOptimization::checkImports(DeclContext *ctxt) const {
793793
ModuleDecl::ImportFilter filter;
794794

795795
if (isPackageCMOEnabled(M.getSwiftModule())) {
796-
// If Package CMO is enabled, decls imported with `package import`
797-
// or `@_spiOnly import` into this module should be allowed to be
798-
// serialized. They are used in decls with `package` or higher
799-
// access level, with or without @_spi; a client of this module
800-
// should be able to access them directly if in the same package.
796+
// When Package CMO is enabled, decls imported with `package import` or
797+
// `@_spiOnly import` into this module should be allowed to be serialized.
798+
// They are used in decls with `package` or higher access level, with or
799+
// without `@_spi`; a client of this module should be able to access them
800+
// directly if it is in the same package.
801+
//
802+
// However, decls that contain types from modules imported as
803+
// `@_implementationOnly` should not be serialized. If such a type is
804+
// serialized, its metadata including field offsets may be stripped, as the
805+
// type is meant to remain hidden from external clients. In such cases, if
806+
// the client attempts to use the serialized accessor in a dynamic context
807+
// (e.g. Codable), it may require the missing metadata to resolve field
808+
// offsets, type info, etc. Since the metadata has been stripped, this can
809+
// lead to a linker error.
810+
//
811+
// Because modules imported with `@_implementationOnly` affect their
812+
// dependencies transitively, the same rule that strips metadata applies to
813+
// types from those transitively imported modules as well. For example,
814+
// `@_implementationOnly import Foundation` indirectly imports `ObjectiveC`,
815+
// meaning that types from `ObjectiveC` such as `NSObject` can also have
816+
// their metadata stripped. To avoid such issues, decls that reference types
817+
// from `@_implementationOnly` modules should not be serialized into client
818+
// modules.
801819
filter = { ModuleDecl::ImportFilterKind::ImplementationOnly };
802820
} else {
803821
// See if context is imported in a "regular" way, i.e. not with
@@ -813,17 +831,8 @@ bool CrossModuleOptimization::checkImports(DeclContext *ctxt) const {
813831

814832
auto &imports = M.getSwiftModule()->getASTContext().getImportCache();
815833
for (auto &desc : results) {
816-
if (imports.isImportedBy(moduleOfCtxt, desc.importedModule)) {
817-
// E.g. `@_implementationOnly import QuartzCore_Private.CALayerPrivate`
818-
// imports `Foundation` as its transitive dependency module; use of a
819-
// a `public` decl in `Foundation` such as `IndexSet` in a function
820-
// signature should not block serialization in Package CMO given the
821-
// function has `package` or higher access level.
822-
if (isPackageCMOEnabled(M.getSwiftModule()) &&
823-
moduleOfCtxt->isNonUserModule())
824-
continue;
834+
if (imports.isImportedBy(moduleOfCtxt, desc.importedModule))
825835
return false;
826-
}
827836
}
828837
return true;
829838
}

test/SILOptimizer/package-cmo-import-filter.swift

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
33

4+
/// 1. Test `@_implementationOnly import`.
5+
// RUN: %target-build-swift-dylib(%t/%target-library-name(Utils)) \
6+
// RUN: %t/UtilsA.swift %t/UtilsB.swift \
7+
// RUN: -module-name Utils -emit-module -package-name Pkg \
8+
// RUN: -Xfrontend -experimental-package-cmo -Xfrontend -experimental-allow-non-resilient-access \
9+
// RUN: -enable-library-evolution -O -wmo
10+
// RUN: %target-sil-opt %t/Utils.swiftmodule -I %t -sil-verify-all -o %t/Utils.sil
11+
// RUN: %FileCheck %s --check-prefix=CHECK-UTILS < %t/Utils.sil
12+
13+
/// Ensure PkgKlass.second accessor symbols are present in the final binary.
14+
// RUN: nm -gU %t/%target-library-name(Utils) | grep "second" | %FileCheck %s --check-prefix=CHECK-NM
15+
// CHECK-NM: s5Utils8PkgKlassC6secondSo8NSObjectCvM
16+
// CHECK-NM: s5Utils8PkgKlassC6secondSo8NSObjectCvMTj
17+
// CHECK-NM: s5Utils8PkgKlassC6secondSo8NSObjectCvMTq
18+
// CHECK-NM: s5Utils8PkgKlassC6secondSo8NSObjectCvg
19+
// CHECK-NM: s5Utils8PkgKlassC6secondSo8NSObjectCvgTj
20+
// CHECK-NM: s5Utils8PkgKlassC6secondSo8NSObjectCvgTq
21+
22+
/// Ensure accessing PkgKlass.second from a client does not cause a linker error.
23+
// RUN: %target-build-swift -I %t -L %t %t/Client.swift -package-name Pkg \
24+
// RUN: %target-rpath(%t) -lUtils -o %t/a.out
25+
26+
27+
/// 2. Test `package import` and `@_spiOnly import`.
428
// RUN: %target-swift-frontend %t/CoreA.swift \
529
// RUN: -module-name=CoreA -package-name Pkg \
630
// RUN: -parse-as-library -emit-module \
@@ -13,31 +37,65 @@
1337
// RUN: -emit-module-path %t/CoreB.swiftmodule -I%t \
1438
// RUN: -O -wmo -enable-library-evolution
1539

16-
// RUN: %target-swift-frontend %t/Lib.swift \
17-
// RUN: -module-name=Lib -package-name Pkg \
40+
// RUN: %target-swift-frontend %t/UI.swift \
41+
// RUN: -module-name=UI -package-name Pkg \
1842
// RUN: -parse-as-library -emit-module \
1943
// RUN: -experimental-spi-only-imports \
20-
// RUN: -emit-module-path %t/Lib.swiftmodule -I %t \
44+
// RUN: -emit-module-path %t/UI.swiftmodule -I %t \
2145
// RUN: -experimental-package-cmo -experimental-allow-non-resilient-access \
22-
// RUN: -O -wmo -enable-library-evolution -Rmodule-loading 2> %t/Lib-result.txt
23-
// RUN: %target-sil-opt %t/Lib.swiftmodule -I %t -sil-verify-all -o %t/Lib.sil
24-
// RUN: %FileCheck %s < %t/Lib.sil
46+
// RUN: -O -wmo -enable-library-evolution -Rmodule-loading 2> %t/UI-result.txt
47+
// RUN: %target-sil-opt %t/UI.swiftmodule -I %t -sil-verify-all -o %t/UI.sil
48+
// RUN: %FileCheck %s < %t/UI.sil
2549

2650
// REQUIRES: swift_in_compiler
2751

52+
//--- Client.swift
53+
public import Utils
54+
55+
package func clientFunc(_ arg: PkgKlass) {
56+
print(arg.second as! AnyObject)
57+
}
58+
59+
//--- UtilsA.swift
60+
public import Foundation
61+
62+
package class PkgKlass {
63+
/// Serialized since it does not reference a type from module imported as @_implementationOnly.
64+
// PkgKlass.first.getter
65+
// CHECK-UTILS-DAG: sil package [serialized_for_package] [canonical] [ossa] @$s5Utils8PkgKlassC5firstSSvg : $@convention(method) (@guaranteed PkgKlass) -> @owned String {
66+
package var first: String
67+
68+
/// Not serialized since it references a type from module imported as @_implementationOnly.
69+
// PkgKlass.second.getter
70+
// CHECK-UTILS-DAG: sil package_external [canonical] @$s5Utils8PkgKlassC6secondSo8NSObjectCvg : $@convention(method) (@guaranteed PkgKlass) -> @owned NSObject
71+
package var second: NSObject
72+
73+
package init(_ arg1: String, _ arg2: NSObject) {
74+
self.first = arg1
75+
self.second = arg2
76+
}
77+
}
78+
79+
//--- UtilsB.swift
80+
@_implementationOnly import Foundation
81+
82+
public func utilsFunc() {
83+
let x: NSString = "utilsfunc"
84+
print(x)
85+
}
2886

29-
//--- Lib.swift
87+
//--- UI.swift
3088
package import CoreA
3189
@_spiOnly public import CoreB
3290

3391
/// PkgStruct is imported with `package import` and should be serialized.
34-
// CHECK-DAG: sil package [serialized_for_package] [canonical] [ossa] @$s3Lib7libFuncyy5CoreA9PkgStructVF : $@convention(thin) (@in_guaranteed PkgStruct) -> () {
35-
package func libFunc(_ arg: PkgStruct) {
92+
// CHECK-DAG: sil package [serialized_for_package] [canonical] [ossa] @$s2UI6uiFuncyy5CoreA9PkgStructVF : $@convention(thin) (@in_guaranteed PkgStruct) -> () {
93+
package func uiFunc(_ arg: PkgStruct) {
3694
print(arg.pkgVar)
3795
}
3896

3997
/// PubStruct is imported with `@_spiOnly public import` and should be serialized.
40-
// CHECK-DAG: sil [serialized_for_package] [canonical] [ossa] @$s3Lib7spiFuncyy5CoreB15PubStructForSPIVF : $@convention(thin) (@in_guaranteed PubStructForSPI) -> () {
98+
// CHECK-DAG: sil [serialized_for_package] [canonical] [ossa] @$s2UI7spiFuncyy5CoreB15PubStructForSPIVF : $@convention(thin) (@in_guaranteed PubStructForSPI) -> () {
4199
@_spi(InCoreB)
42100
public func spiFunc(_ arg: PubStructForSPI) {
43101
print(arg.pubVarForSPI)

0 commit comments

Comments
 (0)