Merge pull request #85179 from xymus/exportability-nle-structs

Sema: Opt-in check for structs references to hidden dependencies in non-library-evolution mode
This commit is contained in:
Alexis Laferrière
2025-11-05 15:32:05 -08:00
committed by GitHub
12 changed files with 295 additions and 15 deletions

View File

@@ -6727,7 +6727,10 @@ public:
///
/// From the standpoint of access control and exportability checking, this
/// var will behave as if it was public, even if it is internal or private.
bool isLayoutExposedToClients() const;
///
/// If \p applyImplicit, consider implicitly exposed layouts as well.
/// This applies to non-resilient modules.
bool isLayoutExposedToClients(bool applyImplicit = false) const;
/// Is this a special debugger variable?
bool isDebuggerVar() const { return Bits.VarDecl.IsDebuggerVar; }

View File

@@ -474,7 +474,7 @@ SIMPLE_DECL_ATTR(_alwaysEmitIntoClient, AlwaysEmitIntoClient,
83)
SIMPLE_DECL_ATTR(_implementationOnly, ImplementationOnly,
OnImport | OnFunc | OnConstructor | OnVar | OnSubscript,
OnImport | OnFunc | OnConstructor | OnVar | OnSubscript | OnStruct,
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | UnreachableInABIAttr,
84)

View File

@@ -3847,7 +3847,8 @@ ERROR(decl_from_hidden_module,none,
"%2 was not imported by this file|"
"C++ types from imported module %2 do not support library evolution|"
"it was imported via the internal bridging header|"
"%2 was not imported publicly}3",
"%2 was not imported publicly|"
"it is a struct marked '@_implementationOnly'}3",
(const Decl *, unsigned, Identifier, unsigned))
ERROR(typealias_desugars_to_type_from_hidden_module,none,
"%0 aliases '%1.%2' and cannot be used %select{here|"
@@ -3865,7 +3866,8 @@ ERROR(typealias_desugars_to_type_from_hidden_module,none,
"%4 was not imported by this file|"
"C++ types from imported module %4 do not support library evolution|"
"it was imported via the internal bridging header|"
"%4 was not imported publicly}5",
"%4 was not imported publicly|"
"it is a struct marked '@_implementationOnly'}5",
(const TypeAliasDecl *, StringRef, StringRef, unsigned, Identifier, unsigned))
ERROR(conformance_from_implementation_only_module,none,
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
@@ -3881,7 +3883,8 @@ ERROR(conformance_from_implementation_only_module,none,
"%3 was not imported by this file|"
"C++ types from imported module %3 do not support library evolution|"
"it was imported via the internal bridging header|"
"%3 was not imported publicly}4",
"%3 was not imported publicly|"
"it is a struct marked '@_implementationOnly'}4",
(Type, Identifier, unsigned, Identifier, unsigned))
NOTE(assoc_conformance_from_implementation_only_module,none,
"in associated type %0 (inferred as %1)", (Type, Type))
@@ -3946,6 +3949,9 @@ ERROR(implementation_only_override_import_without_attr,none,
"override of %kindonly0 imported as implementation-only must be declared "
"'@_implementationOnly'",
(const ValueDecl *))
ERROR(implementation_only_on_structs_feature,none,
"'@_implementationOnly' on structs requires "
"'-enable-experimental-feature CheckImplementationOnly'", ())
ERROR(import_attr_conflict,none,
"%0 inconsistently imported with %1",
@@ -4128,7 +4134,7 @@ WARNING(attr_has_no_effect_on_decl_with_access_level,none,
(DeclAttribute, AccessLevel))
ERROR(attr_not_on_decl_with_invalid_access_level,none,
"'%0' may not be used on "
"%select{private|fileprivate|internal|package|%error|%error}1 declarations",
"%select{private|fileprivate|internal|package|public|%error}1 declarations",
(DeclAttribute, AccessLevel))
ERROR(attr_has_no_effect_decl_not_available_before,none,
@@ -7350,7 +7356,8 @@ ERROR(inlinable_decl_ref_from_hidden_module,
"%2 was not imported by this file|"
"C++ APIs from imported module %2 do not support library evolution|"
"it was imported via the internal bridging header|"
"%2 was not imported publicly}3",
"%2 was not imported publicly|"
"it is a struct marked '@_implementationOnly'}3",
(const ValueDecl *, unsigned, Identifier, unsigned))
ERROR(inlinable_typealias_desugars_to_type_from_hidden_module,
@@ -7362,7 +7369,8 @@ ERROR(inlinable_typealias_desugars_to_type_from_hidden_module,
"%4 was not imported by this file|"
"C++ types from imported module %4 do not support library evolution|"
"it was imported via the internal bridging header|"
"%4 was not imported publicly}5",
"%4 was not imported publicly|"
"it is a struct marked '@_implementationOnly'}5",
(const TypeAliasDecl *, StringRef, StringRef, unsigned, Identifier, unsigned))
NOTE(missing_import_inserted,

View File

@@ -558,6 +558,9 @@ EXPERIMENTAL_FEATURE(EmbeddedExistentials, false)
/// Allow use of the 'anyAppleOS' availability domain.
EXPERIMENTAL_FEATURE(AnyAppleOSAvailability, true)
/// Check @_implementationOnly imports in non-library-evolution mode.
EXPERIMENTAL_FEATURE(CheckImplementationOnly, true)
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE

View File

@@ -1006,7 +1006,7 @@ bool swift::isExported(const ValueDecl *VD) {
// Is this a stored property in a @frozen struct or class?
if (auto *property = dyn_cast<VarDecl>(VD))
if (property->isLayoutExposedToClients())
if (property->isLayoutExposedToClients(/*applyImplicit=*/true))
return true;
return false;

View File

@@ -2753,20 +2753,33 @@ bool VarDecl::isInitExposedToClients() const {
return hasInitialValue() && isLayoutExposedToClients();
}
bool VarDecl::isLayoutExposedToClients() const {
bool VarDecl::isLayoutExposedToClients(bool applyImplicit) const {
auto parent = dyn_cast<NominalTypeDecl>(getDeclContext());
if (!parent) return false;
if (isStatic()) return false;
auto M = getDeclContext()->getParentModule();
auto nominalAccess =
parent->getFormalAccessScope(/*useDC=*/nullptr,
/*treatUsableFromInlineAsPublic=*/true);
if (!nominalAccess.isPublic()) return false;
if (!parent->getAttrs().hasAttribute<FrozenAttr>() &&
!parent->getAttrs().hasAttribute<FixedLayoutAttr>())
// Resilient modules and classes hide layouts by default.
bool layoutIsHiddenByDefault = !applyImplicit ||
!getASTContext().LangOpts.hasFeature(Feature::CheckImplementationOnly) ||
M->getResilienceStrategy() == ResilienceStrategy::Resilient;
if (layoutIsHiddenByDefault) {
if (!nominalAccess.isPublic())
return false;
if (!parent->getAttrs().hasAttribute<FrozenAttr>() &&
!parent->getAttrs().hasAttribute<FixedLayoutAttr>())
return false;
} else {
// Non-resilient module: layouts are exposed by default unless marked
// otherwise.
if (parent->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return false;
}
if (!hasStorage() &&
!getAttrs().hasAttribute<LazyAttr>() &&

View File

@@ -144,6 +144,7 @@ UNINTERESTING_FEATURE(ExtractConstantsFromMembers)
UNINTERESTING_FEATURE(GroupActorErrors)
UNINTERESTING_FEATURE(SameElementRequirements)
UNINTERESTING_FEATURE(SendingArgsAndResults)
UNINTERESTING_FEATURE(CheckImplementationOnly)
static bool findUnderscoredLifetimeAttr(Decl *decl) {
auto hasUnderscoredLifetimeAttr = [](Decl *decl) {

View File

@@ -338,6 +338,7 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
case DisallowedOriginKind::ImplementationOnly:
case DisallowedOriginKind::FragileCxxAPI:
case DisallowedOriginKind::ImplementationOnlyMemoryLayout:
break;
}

View File

@@ -2163,6 +2163,14 @@ swift::getDisallowedOriginKind(const Decl *decl,
Feature::AssumeResilientCxxTypes))
return DisallowedOriginKind::FragileCxxAPI;
if (isa<StructDecl>(decl) || isa<EnumDecl>(decl)) {
if (decl->getASTContext().LangOpts.hasFeature(
Feature::CheckImplementationOnly) &&
decl->getAttrs().hasAttribute<ImplementationOnlyAttr>()) {
return DisallowedOriginKind::ImplementationOnlyMemoryLayout;
}
}
// Report non-public import last as it can be ignored by the caller.
// See \c diagnoseValueDeclRefExportability.
auto importSource = decl->getImportAccessFrom(where.getDeclContext());

View File

@@ -32,7 +32,7 @@ class SourceFile;
/// itself. Related checks may also be performed.
void checkAccessControl(Decl *D);
/// Problematic origin of an exported type.
/// Problematic origin of a decl that may restrict its exportability.
///
/// This enum must be kept in sync with a number of diagnostics:
/// diag::inlinable_decl_ref_from_hidden_module
@@ -52,6 +52,7 @@ enum class DisallowedOriginKind : uint8_t {
InternalBridgingHeaderImport,
NonPublicImport,
ImplementationOnlyMemoryLayout,
None
};

View File

@@ -4995,7 +4995,25 @@ AttributeChecker::visitImplementationOnlyAttr(ImplementationOnlyAttr *attr) {
return;
}
// @_implementationOnly on structs only applies to non-public types.
auto *VD = cast<ValueDecl>(D);
if (isa<StructDecl>(VD)) {
if (!Ctx.LangOpts.hasFeature(Feature::CheckImplementationOnly)) {
diagnoseAndRemoveAttr(attr,
diag::implementation_only_on_structs_feature);
return;
}
auto access =
VD->getFormalAccessScope(/*useDC=*/nullptr,
/*treatUsableFromInlineAsPublic=*/true);
if (access.isPublicOrPackage())
diagnoseAndRemoveAttr(
attr, diag::attr_not_on_decl_with_invalid_access_level,
attr, access.accessLevelForDiagnostics());
return;
}
auto *overridden = VD->getOverriddenDecl();
if (!overridden) {
diagnoseAndRemoveAttr(attr, diag::implementation_only_decl_non_override);

View File

@@ -0,0 +1,224 @@
/// Test @_implementationOnly import exportability diagnostics in non-library-evolution mode
/// Standard / non-embedded
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -o %t/indirects.swiftmodule \
// RUN: %S/Inputs/implementation-only-imports/indirects.swift \
// RUN: -swift-version 5
// RUN: %target-swift-frontend -emit-module -o %t/directs.swiftmodule -I %t\
// RUN: %S/Inputs/implementation-only-imports/directs.swift \
// RUN: -swift-version 5
/// Old diags
// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \
// RUN: -swift-version 5 \
// RUN: -verify-additional-prefix not-opt-in-
/// New diags
// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \
// RUN: -swift-version 5 \
// RUN: -verify-additional-prefix opt-in- -DUseImplementationOnly \
// RUN: -enable-experimental-feature CheckImplementationOnly
/// Embedded
/// Will also show errors in non-@_neverEmitIntoClient functions.
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -o %t/indirects.swiftmodule \
// RUN: %S/Inputs/implementation-only-imports/indirects.swift \
// RUN: -swift-version 5 -target arm64-apple-none-macho \
// RUN: -enable-experimental-feature Embedded
// RUN: %target-swift-frontend -emit-module -o %t/directs.swiftmodule -I %t\
// RUN: %S/Inputs/implementation-only-imports/directs.swift \
// RUN: -swift-version 5 -target arm64-apple-none-macho \
// RUN: -enable-experimental-feature Embedded
/// Old diags
// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \
// RUN: -swift-version 5 -target arm64-apple-none-macho \
// RUN: -enable-experimental-feature Embedded \
// RUN: -verify-additional-prefix not-opt-in-
/// New diags
// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \
// RUN: -swift-version 5 -target arm64-apple-none-macho \
// RUN: -enable-experimental-feature Embedded \
// RUN: -verify-additional-prefix opt-in- -DUseImplementationOnly \
// RUN: -verify-additional-prefix embedded-opt-in- \
// RUN: -enable-experimental-feature CheckImplementationOnly
// REQUIRES: swift_feature_Embedded
// REQUIRES: swift_feature_CheckImplementationOnly
// REQUIRES: embedded_stdlib_cross_compiling
@_implementationOnly import directs
// expected-warning @-1 {{using '@_implementationOnly' without enabling library evolution for 'main' may lead to instability during execution}}
import indirects
/// Referenced types
public struct ExposedLayoutPublic {
public init() { fatalError() }
}
internal struct ExposedLayoutInternal {
}
private struct ExposedLayoutPrivate {
// expected-note @-1 2 {{struct 'ExposedLayoutPrivate' is not '@usableFromInline' or public}}
init() { fatalError() } // expected-note {{initializer 'init()' is not '@usableFromInline' or public}}
}
#if UseImplementationOnly
@_implementationOnly
private struct HiddenLayout {
// expected-opt-in-note @-1 2 {{struct 'HiddenLayout' is not '@usableFromInline' or public}}
// expected-opt-in-note @-2 1 {{initializer 'init()' is not '@usableFromInline' or public}}
// expected-opt-in-note @-3 2 {{struct declared here}}
// expected-opt-in-note @-4 {{struct declared here}}
}
#else
private struct HiddenLayout {
// expected-not-opt-in-note @-1 2 {{struct 'HiddenLayout' is not '@usableFromInline' or public}}
// expected-not-opt-in-note @-2 1 {{initializer 'init()' is not '@usableFromInline' or public}}
}
#endif
public enum ExposedEnumPublic {
case A
case B
}
private enum ExposedEnumPrivate {
// expected-note @-1 2 {{enum 'ExposedEnumPrivate' is not '@usableFromInline' or public}}
case A
// expected-note @-1 1 {{enum case 'A' is not '@usableFromInline' or public}}
case B
}
/// Function use sites
@inlinable
public func explicitlyInlinable() {
let _: ExposedLayoutPublic = ExposedLayoutPublic()
let _: ExposedLayoutPrivate = ExposedLayoutPrivate()
// expected-error @-1 2 {{struct 'ExposedLayoutPrivate' is private and cannot be referenced from an '@inlinable' function}}
// expected-error @-2 {{initializer 'init()' is private and cannot be referenced from an '@inlinable' function}}
let _: HiddenLayout = HiddenLayout()
// expected-error @-1 2 {{struct 'HiddenLayout' is private and cannot be referenced from an '@inlinable' function}}
// expected-error @-2 {{initializer 'init()' is private and cannot be referenced from an '@inlinable' function}}
let _: ExposedEnumPublic = ExposedEnumPublic.A
let _: ExposedEnumPrivate = ExposedEnumPrivate.A
// expected-error @-1 2 {{enum 'ExposedEnumPrivate' is private and cannot be referenced from an '@inlinable' function}}
// expected-error @-2 {{enum case 'A' is private and cannot be referenced from an '@inlinable' function}}
}
public func implicitlyInlinablePublic() {
let _: ExposedLayoutPublic = ExposedLayoutPublic()
let _: ExposedLayoutPrivate = ExposedLayoutPrivate()
let _: HiddenLayout = HiddenLayout()
// expected-embedded-opt-in-error @-1 2 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}}
let _: ExposedEnumPublic = ExposedEnumPublic.A
let _: ExposedEnumPrivate = ExposedEnumPrivate.A
}
private func implicitlyInlinablePrivate() {
let _: ExposedLayoutPublic = ExposedLayoutPublic()
let _: ExposedLayoutPrivate = ExposedLayoutPrivate()
let _: HiddenLayout = HiddenLayout()
// expected-embedded-opt-in-error @-1 2 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}}
let _: ExposedEnumPublic = ExposedEnumPublic.A
let _: ExposedEnumPrivate = ExposedEnumPrivate.A
}
@_neverEmitIntoClient
public func explicitNonInliable() {
let _: ExposedLayoutPublic = ExposedLayoutPublic()
let _: ExposedLayoutPrivate = ExposedLayoutPrivate()
let _: HiddenLayout = HiddenLayout()
let _: ExposedEnumPublic = ExposedEnumPublic.A
let _: ExposedEnumPrivate = ExposedEnumPrivate.A
}
@_neverEmitIntoClient
internal func explicitNonInliableInternal() {
let _: ExposedLayoutPublic = ExposedLayoutPublic()
let _: ExposedLayoutPrivate = ExposedLayoutPrivate()
let _: HiddenLayout = HiddenLayout()
let _: ExposedEnumPublic = ExposedEnumPublic.A
let _: ExposedEnumPrivate = ExposedEnumPrivate.A
}
/// Struct use sites
public struct ExposedLayoutPublicUser {
public var publicField: StructFromDirect
// expected-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}}
private var privateField: StructFromDirect
// expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}}
private var a: ExposedLayoutPublic
private var aa: ExposedLayoutInternal
private var b: ExposedLayoutPrivate
private var c: HiddenLayout
// expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; it is a struct marked '@_implementationOnly'}}
private func privateFunc(h: HiddenLayout) {}
// expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}}
}
private struct ExposedLayoutInternalUser {
private var privateField: StructFromDirect
// expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}}
private var a: ExposedLayoutPublic
private var aa: ExposedLayoutInternal
private var b: ExposedLayoutPrivate
private var c: HiddenLayout
// expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; it is a struct marked '@_implementationOnly'}}
private func privateFunc(h: HiddenLayout) {}
// expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}}
}
private struct ExposedLayoutPrivateUser {
private var privateField: StructFromDirect
// expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}}
private var a: ExposedLayoutPublic
private var aa: ExposedLayoutInternal
private var b: ExposedLayoutPrivate
private var c: HiddenLayout
// expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; it is a struct marked '@_implementationOnly'}}
private func privateFunc(h: HiddenLayout) {}
// expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}}
}
#if UseImplementationOnly
@_implementationOnly
private struct HiddenLayoutUser {
public var publicField: StructFromDirect
private var privateField: StructFromDirect
private var a: ExposedLayoutPublic
private var aa: ExposedLayoutInternal
private var b: ExposedLayoutPrivate
private var c: HiddenLayout
@_neverEmitIntoClient
private func privateFunc(h: HiddenLayout) {}
}
@_implementationOnly // expected-opt-in-error {{'@_implementationOnly' may not be used on public declarations}}
public struct PublicHiddenStruct {}
#endif