diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index ebb751d3027e4..4134902584047 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2684,8 +2684,29 @@ class ValueDecl : public Decl { SourceLoc getNameLoc() const { return NameLoc; } + /// Returns \c true if this value decl is inlinable with attributes + /// \c \@usableFromInline, \c \@inlinalbe, and \c \@_alwaysEmitIntoClient bool isUsableFromInline() const; + /// Returns \c true if this value decl needs a special case handling for an + /// interface file. + /// + /// One such case is a reference of an inlinable decl with a `package` access level + /// in an interface file as follows: Package decls are only printed in interface files if + /// they are inlinable (as defined in \c isUsableFromInline). They could be + /// referenced by a module outside of its defining module that belong to the same + /// package determined by the `package-name` flag. However, the flag is only in + /// .swiftmodule and .private.swiftinterface, thus type checking references of inlinable + /// package symbols in public interfaces fails due to the missing flag. + /// Instead of adding the package-name flag to the public interfaces, which + /// could raise a security concern, we grant access to such cases. + /// + /// \sa useDC The use site where this value decl is referenced. + /// \sa useAcl The access level of its use site. + /// \sa declScope The access scope of this decl site. + bool skipAccessCheckIfInterface(const DeclContext *useDC, AccessLevel useAcl, + AccessScope declScope) const; + /// Returns \c true if this declaration is *not* intended to be used directly /// by application developers despite the visibility. bool shouldHideFromEditor() const; diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 76789e5b96d92..7c5f0485d7323 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -3818,6 +3818,17 @@ bool ValueDecl::isUsableFromInline() const { return false; } +bool ValueDecl::skipAccessCheckIfInterface(const DeclContext *useDC, + AccessLevel useAcl, + AccessScope declScope) const { + if (!useDC || useAcl != AccessLevel::Package || !declScope.isPackage() || + !isUsableFromInline() || + getDeclContext()->getParentModule() == useDC->getParentModule()) + return false; + auto useSF = useDC->getParentSourceFile(); + return useSF && useSF->Kind == SourceFileKind::Interface; +} + bool ValueDecl::shouldHideFromEditor() const { // Hide private stdlib declarations. if (isPrivateStdlibDecl(/*treatNonBuiltinProtocolsAsPublic*/ false) || @@ -4154,8 +4165,13 @@ static bool checkAccessUsingAccessScopes(const DeclContext *useDC, VD, access, useDC, /*treatUsableFromInlineAsPublic*/ includeInlineable); if (accessScope.getDeclContext() == useDC) return true; - if (!AccessScope(useDC).isChildOf(accessScope)) return false; - + if (!AccessScope(useDC).isChildOf(accessScope)) { + // Grant access if this VD is an inlinable package decl referenced by + // another module in an interface file. + if (VD->skipAccessCheckIfInterface(useDC, access, accessScope)) + return true; + return false; + } // useDC is null only when caller wants to skip non-public type checks. if (!useDC) return true; diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 839a7be26fddf..9e5ead15d52d4 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -4372,6 +4372,10 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) { requiredAccessScope.requiredAccessForDiagnostics(); auto proto = conformance->getProtocol(); auto protoAccessScope = proto->getFormalAccessScope(DC); + // Skip diagnostics of a witness of a package protocol that is inlinalbe + // referenced in an interface file. + if (proto->skipAccessCheckIfInterface(DC, requiredAccess, protoAccessScope)) + return; bool protoForcesAccess = requiredAccessScope.hasEqualDeclContextWith(protoAccessScope); auto diagKind = protoForcesAccess diff --git a/test/Sema/accessibility_package_interface.swift b/test/Sema/accessibility_package_interface.swift index 6cd2c8bcf1e70..c4679f5753c1e 100644 --- a/test/Sema/accessibility_package_interface.swift +++ b/test/Sema/accessibility_package_interface.swift @@ -10,29 +10,118 @@ // RUN: -emit-private-module-interface-path %t/Utils.private.swiftinterface // RUN: %target-swift-typecheck-module-from-interface(%t/Utils.swiftinterface) -I %t -// RUN: %FileCheck %s --check-prefix=CHECK-PUBLIC < %t/Utils.swiftinterface -// CHECK-PUBLIC-NOT: -package-name swift-utils.log -// CHECK-PUBLIC-NOT: package func packageFunc() -// CHECK-PUBLIC: -module-name Utils -// CHECK-PUBLIC: public func publicFunc() +// RUN: %FileCheck %s --check-prefix=CHECK-PUBLIC-UTILS < %t/Utils.swiftinterface + +// CHECK-PUBLIC-UTILS-NOT: -package-name swift-utils.log +// CHECK-PUBLIC-UTILS-NOT: package func packageFunc() +// CHECK-PUBLIC-UTILS-NOT: package protocol PackageProto +// CHECK-PUBLIC-UTILS-NOT: var pkgVar +// CHECK-PUBLIC-UTILS-NOT: package class PackageKlass +// CHECK-PUBLIC-UTILS-NOT: package var pkgVar +// CHECK-PUBLIC-UTILS: -module-name Utils +// CHECK-PUBLIC-UTILS: public func publicFunc() +// CHECK-PUBLIC-UTILS: @usableFromInline +// CHECK-PUBLIC-UTILS: package func ufiPackageFunc() +// CHECK-PUBLIC-UTILS: @usableFromInline +// CHECK-PUBLIC-UTILS: package protocol UfiPackageProto +// CHECK-PUBLIC-UTILS: var ufiPkgVar +// CHECK-PUBLIC-UTILS: @usableFromInline +// CHECK-PUBLIC-UTILS: package class UfiPackageKlass +// CHECK-PUBLIC-UTILS: @usableFromInline +// CHECK-PUBLIC-UTILS: package var ufiPkgVar // RUN: %target-swift-typecheck-module-from-interface(%t/Utils.private.swiftinterface) -module-name Utils -I %t -// RUN: %FileCheck %s --check-prefix=CHECK-PRIVATE < %t/Utils.private.swiftinterface +// RUN: %FileCheck %s --check-prefix=CHECK-PRIVATE-UTILS < %t/Utils.private.swiftinterface + +// CHECK-PRIVATE-UTILS-NOT: package func packageFunc() +// CHECK-PRIVATE-UTILS-NOT: package protocol PackageProto +// CHECK-PRIVATE-UTILS-NOT: var pkgVar +// CHECK-PRIVATE-UTILS-NOT: package class PackageKlass +// CHECK-PRIVATE-UTILS-NOT: package var pkgVar +// CHECK-PRIVATE-UTILS: -module-name Utils +// CHECK-PRIVATE-UTILS: swift-module-flags-ignorable-private: -package-name swift-utils.log +// CHECK-PRIVATE-UTILS: public func publicFunc() +// CHECK-PRIVATE-UTILS: @usableFromInline +// CHECK-PRIVATE-UTILS: package func ufiPackageFunc() +// CHECK-PRIVATE-UTILS: @usableFromInline +// CHECK-PRIVATE-UTILS: package protocol UfiPackageProto +// CHECK-PRIVATE-UTILS: var ufiPkgVar +// CHECK-PRIVATE-UTILS: @usableFromInline +// CHECK-PRIVATE-UTILS: package class UfiPackageKlass +// CHECK-PRIVATE-UTILS: @usableFromInline +// CHECK-PRIVATE-UTILS: package var ufiPkgVar + +// RUN: %target-swift-frontend -emit-module %t/Client.swift \ +// RUN: -module-name Client -swift-version 5 -I %t \ +// RUN: -package-name swift-utils.log \ +// RUN: -enable-library-evolution \ +// RUN: -emit-module-path %t/Client.swiftmodule \ +// RUN: -emit-module-interface-path %t/Client.swiftinterface \ +// RUN: -emit-private-module-interface-path %t/Client.private.swiftinterface + +// RUN: rm -rf %t/Utils.swiftmodule +// RUN: rm -rf %t/Client.swiftmodule -// CHECK-PRIVATE-NOT: package func packageFunc() -// CHECK-PRIVATE: swift-module-flags-ignorable-private: -package-name swift-utils.log -// CHECK-PRIVATE: public func publicFunc() +// RUN: %target-swift-typecheck-module-from-interface(%t/Client.swiftinterface) -I %t -verify +// RUN: %FileCheck %s --check-prefix=CHECK-PUBLIC-CLIENT < %t/Client.swiftinterface +// CHECK-PUBLIC-CLIENT-NOT: -package-name swift-utils.log +// CHECK-PUBLIC-CLIENT: @inlinable public func clientFunc() +// CHECK-PUBLIC-CLIENT: publicFunc() +// CHECK-PUBLIC-CLIENT: ufiPackageFunc() +// CHECK-PUBLIC-CLIENT: let u = UfiPackageKlass() +// CHECK-PUBLIC-CLIENT: return u.ufiPkgVar +// CHECK-PUBLIC-CLIENT: public class ClientKlass1 : Utils.UfiPackageProto +// CHECK-PUBLIC-CLIENT: @usableFromInline +// CHECK-PUBLIC-CLIENT: package var ufiPkgVar: Swift.String +// CHECK-PUBLIC-CLIENT: public class ClientKlass2 : Utils.UfiPackageProto +// CHECK-PUBLIC-CLIENT: public var ufiPkgVar: Swift.String + +// RUN: %target-swift-typecheck-module-from-interface(%t/Client.private.swiftinterface) -module-name Client -I %t -verify -// RUN: %target-swift-frontend -typecheck %t/Client.swift -package-name swift-utils.log -I %t -verify //--- Utils.swift -package func packageFunc() {} public func publicFunc() {} +package func packageFunc() {} +@usableFromInline +package func ufiPackageFunc() {} + +package protocol PackageProto { + var pkgVar: String { get set } +} +package class PackageKlass: PackageProto { + package var pkgVar = "" +} + +@usableFromInline +package protocol UfiPackageProto { + var ufiPkgVar: String { get set } +} + +@usableFromInline +package class UfiPackageKlass: UfiPackageProto { + @usableFromInline + package init() {} + @usableFromInline + package var ufiPkgVar = "" +} + + //--- Client.swift import Utils -func clientFunc() { - packageFunc() +@inlinable public func clientFunc() -> String { publicFunc() + ufiPackageFunc() + let u = UfiPackageKlass() + return u.ufiPkgVar +} + +public class ClientKlass1: UfiPackageProto { + @usableFromInline + package var ufiPkgVar = "B" +} + +public class ClientKlass2: UfiPackageProto { + public var ufiPkgVar = "C" }