Files
swift-mirror/lib/Sema/ResilienceDiagnostics.cpp
T
Xi Ge 00897a0efb [Serialization] Add precomputed layout table for hidden types
Add the module-format machinery that lets a Swift library record the
physical layout of hidden types (currently limited to C types imported via internal bridging header).
into binary modules, so downstream consumers can pull the layouts of these hidden types without
loading the internal dependency.

To test this, this change also added a frontend action to print hidden types' layouts
from both the module under compilation and all the modules being imported.
2026-05-15 10:07:19 -07:00

554 lines
23 KiB
C++

//===--- ResilienceDiagnostics.cpp - Resilience Inlineability Diagnostics -===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements diagnostics for fragile functions, like those with
// @inlinable, @_alwaysEmitIntoClient, or @backDeployed.
//
//===----------------------------------------------------------------------===//
#include "TypeChecker.h"
#include "TypeCheckAvailability.h"
#include "TypeCheckAccess.h"
#include "swift/AST/Attr.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DeclContext.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/TypeDeclFinder.h"
#include "swift/Basic/Assertions.h"
using namespace swift;
static bool addMissingImport(SourceLoc loc, const Decl *D,
const ExportContext &where) {
ASTContext &ctx = where.getDeclContext()->getASTContext();
ModuleDecl *M = D->getModuleContext();
auto *SF = where.getDeclContext()->getParentSourceFile();
// Only add imports of API level modules if this is an API level module.
if (M->getLibraryLevel() != LibraryLevel::API &&
SF->getParentModule()->getLibraryLevel() == LibraryLevel::API)
return false;
// Hack to fix swiftinterfaces in case of missing imports. We can get rid of
// this logic when we don't leak the use of non-locally imported things in
// API.
auto missingImport = ImportedModule(ImportPath::Access(),
const_cast<ModuleDecl *>(M));
SF->addImplicitImportForModuleInterface(missingImport);
ctx.Diags.diagnose(loc, diag::missing_import_inserted, M->getName());
return true;
}
bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
const ValueDecl *D,
const ExportContext &where) {
auto fragileKind = where.getFragileFunctionKind();
if (fragileKind.kind == FragileFunctionKind::None)
return false;
// Local declarations are OK.
if (D->getDeclContext()->isLocalContext())
return false;
auto *DC = where.getDeclContext();
auto &Context = DC->getASTContext();
if (auto *init = dyn_cast<ConstructorDecl>(DC)) {
if (init->isDesignatedInit()) {
auto *storage = dyn_cast<AbstractStorageDecl>(D);
if (storage && storage->hasInitAccessor()) {
if (diagnoseInlinableDeclRefAccess(
loc, storage->getAccessor(AccessorKind::Init), where))
return true;
}
}
}
// Remember that the module defining the decl must be imported publicly.
recordRequiredImportAccessLevelForDecl(D, DC, AccessLevel::Public, loc);
// General check on access-level of the decl.
auto declAccessScope =
D->getFormalAccessScope(/*useDC=*/DC,
/*allowUsableFromInline=*/true);
if (declAccessScope.isPublic()) {
// Diagnose private setters accessed from inlinable functions
if (auto *accessor = dyn_cast<AccessorDecl>(D)) {
if (accessor->getAccessorKind() == AccessorKind::Set) {
auto storage = accessor->getStorage();
if (accessor->getFormalAccess() < storage->getFormalAccess()) {
auto diagID = diag::resilience_decl_unavailable;
Context.Diags
.diagnose(loc, diagID, D, accessor->getFormalAccess(),
fragileKind.getSelector())
.warnUntilLanguageModeIf(
!Context.LangOpts.hasFeature(Feature::StrictAccessControl),
LanguageMode::future);
Context.Diags.diagnose(D, diag::resilience_decl_declared_here, D);
}
}
}
// Public declarations are OK, even if they're SPI or came from an
// implementation-only import. We'll diagnose exportability violations
// from diagnoseDeclRefExportability().
return false;
}
// Dynamic declarations were mistakenly not checked in Swift 4.2.
// Do enforce the restriction even in pre-Swift-5 modes if the module we're
// building is resilient, though.
if (D->shouldUseObjCDispatch() &&
!Context.isLanguageModeAtLeast(LanguageMode::v5) &&
!DC->getParentModule()->isResilient()) {
return false;
}
// Embedded functions can reference non-public decls as they are visible
// to clients.
if (fragileKind.kind == FragileFunctionKind::EmbeddedAlwaysEmitIntoClient)
return false;
DowngradeToWarning downgradeToWarning = DowngradeToWarning::No;
// Swift 4.2 did not perform any checks for type aliases.
if (isa<TypeAliasDecl>(D)) {
if (!Context.isLanguageModeAtLeast(LanguageMode::v4_2))
return false;
if (!Context.isLanguageModeAtLeast(LanguageMode::v5))
downgradeToWarning = DowngradeToWarning::Yes;
}
// Swift 4.2 did not check accessor accessibility.
if (auto accessor = dyn_cast<AccessorDecl>(D)) {
if (!accessor->isInitAccessor() &&
!Context.isLanguageModeAtLeast(LanguageMode::v5))
downgradeToWarning = DowngradeToWarning::Yes;
}
// Swift 5.0 did not check the underlying types of local typealiases.
if (isa<TypeAliasDecl>(DC) &&
!Context.LangOpts.hasFeature(Feature::StrictAccessControl) &&
!Context.isLanguageModeAtLeast(LanguageMode::v6))
downgradeToWarning = DowngradeToWarning::Yes;
auto diagID = diag::resilience_decl_unavailable;
if (downgradeToWarning == DowngradeToWarning::Yes)
diagID = diag::resilience_decl_unavailable_warn;
AccessLevel diagAccessLevel = declAccessScope.accessLevelForDiagnostics();
Context.Diags.diagnose(loc, diagID, D, diagAccessLevel,
fragileKind.getSelector());
Context.Diags.diagnose(D, diag::resilience_decl_declared_here, D);
ImportAccessLevel problematicImport = D->getImportAccessFrom(DC);
if (problematicImport.has_value() &&
problematicImport->accessLevel < D->getFormalAccess()) {
Context.Diags.diagnose(problematicImport->importLoc,
diag::decl_import_via_here, D,
problematicImport->accessLevel,
problematicImport->module.importedModule,
problematicImport->module.importedModule
->isClangHeaderImportModule());
}
return (downgradeToWarning == DowngradeToWarning::No);
}
static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc,
const TypeAliasDecl *TAD,
const ExportContext &where) {
assert(where.mustOnlyReferenceExportedDecls());
auto *D = TAD->getUnderlyingType()->getAnyNominal();
if (!D)
return false;
const DeclContext *DC = where.getDeclContext();
auto exportingModule = DC->getParentModule();
ASTContext &ctx = exportingModule->getASTContext();
// Remember that the module defining the underlying type must be imported
// publicly.
recordRequiredImportAccessLevelForDecl(
D, DC, AccessLevel::Public,
[&](AttributedImport<ImportedModule> attributedImport) {
ModuleDecl *importedVia = attributedImport.module.importedModule,
*sourceModule = D->getModuleContext();
ctx.Diags.diagnose(loc, diag::module_api_import_aliases, D, importedVia,
sourceModule, importedVia == sourceModule);
});
auto ignoredDowngradeToWarning = DowngradeToWarning::No;
auto originKind =
getDisallowedOriginKind(D, where, ignoredDowngradeToWarning);
auto commonBehavior = where.behaviorForReferenceToOrigin(D, originKind);
if (commonBehavior == DiagnosticBehavior::Ignore)
return false;
// As an exception, if the import of the module that defines the desugared
// decl is just missing (as opposed to imported explicitly with reduced
// visibility) then we should only diagnose if we're building a resilient
// module.
if (originKind == DisallowedOriginKind::MissingImport &&
!exportingModule->isResilient())
return false;
auto definingModule = D->getModuleContext();
auto fragileKind = where.getFragileFunctionKind();
bool warnPreSwift6 = originKind != DisallowedOriginKind::SPIOnly &&
originKind != DisallowedOriginKind::NonPublicImport;
if (fragileKind.kind == FragileFunctionKind::None) {
auto reason = where.getExportabilityReason();
ctx.Diags
.diagnose(loc, diag::typealias_desugars_to_type_from_hidden_module,
TAD, definingModule->getNameStr(), D->getNameStr(),
static_cast<unsigned>(*reason), definingModule->getName(),
static_cast<unsigned>(originKind))
.warnUntilLanguageModeIf(warnPreSwift6, LanguageMode::v6)
.limitBehaviorIfMorePermissive(commonBehavior);
} else {
ctx.Diags
.diagnose(loc,
diag::inlinable_typealias_desugars_to_type_from_hidden_module,
TAD, definingModule->getNameStr(), D->getNameStr(),
fragileKind.getSelector(), definingModule->getName(),
static_cast<unsigned>(originKind))
.warnUntilLanguageModeIf(warnPreSwift6, LanguageMode::v6)
.limitBehaviorIfMorePermissive(commonBehavior);
}
D->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
if (!ctx.LangOpts.hasFeature(Feature::StrictAccessControl) &&
originKind == DisallowedOriginKind::MissingImport &&
!ctx.isLanguageModeAtLeast(LanguageMode::v6))
addMissingImport(loc, D, where);
// If limited by an import, note which one.
if (originKind == DisallowedOriginKind::NonPublicImport) {
ImportAccessLevel limitImport = D->getImportAccessFrom(DC);
assert(limitImport.has_value() &&
limitImport->accessLevel < AccessLevel::Public &&
"The import should still be non-public");
ctx.Diags.diagnose(limitImport->importLoc,
diag::decl_import_via_here, D,
limitImport->accessLevel,
limitImport->module.importedModule,
limitImport->module.importedModule
->isClangHeaderImportModule());
}
return true;
}
/// Returns true if access to \p D should be diagnosed during exportability
/// checking. These diagnostics would typically be handled by the access
/// checker, and therefore should be suppressed to avoid duplicate diagnostics.
/// However, extensions are special because they do not have an intrinsic access
/// level and therefore the access checker does not currently handle them.
/// Instead, diagnostics for decls referenced in extension signatures are
/// deferred to exportability checking. An exportable extension is effectively a
/// public extension.
static bool shouldDiagnoseDeclAccess(const ValueDecl *D,
const ExportContext &where,
bool isInternalBridgingHeader) {
auto reason = where.getExportabilityReason();
auto DC = where.getDeclContext();
if (!reason)
return false;
switch (*reason) {
case ExportabilityReason::ExtensionWithPublicMembers:
case ExportabilityReason::ExtensionWithConditionalConformances:
// Allow public members in extensions of implicitly exported types.
// Extensions cannot define stored variables avoiding the memory layout
// concerns and we don't print swiftinterfaces in non-library-evolution
// mode.
// We should be able to always allow this if it's correctly guarded
// at generating module interfaces and generated headers.
return where.getExportedLevel() == ExportedLevel::Exported;
case ExportabilityReason::Inheritance:
case ExportabilityReason::ImplicitlyPublicInheritance:
return isa<ProtocolDecl>(D);
case ExportabilityReason::AvailableAttribute:
// If the context is an extension and that extension has an explicit
// access level then availability domains access has already been
// diagnosed.
if (auto *ED = dyn_cast_or_null<ExtensionDecl>(DC->getAsDecl()))
return !ED->getAttrs().getAttribute<AccessControlAttr>();
return false;
case ExportabilityReason::PublicVarDecl:
case ExportabilityReason::AssociatedValue:
return false;
case ExportabilityReason::General:
case ExportabilityReason::ResultBuilder:
case ExportabilityReason::PropertyWrapper:
case ExportabilityReason::ImplicitlyPublicVarDecl:
case ExportabilityReason::ImplicitlyPublicVarDeclOpenClass:
case ExportabilityReason::ImplicitlyPublicVarDeclMissingAttribute:
case ExportabilityReason::ImplicitlyPublicVarDeclMissingDeinit:
case ExportabilityReason::ImplicitlyPublicVarDeclMissingAttributeAndDeinit:
case ExportabilityReason::ImplicitlyPublicAssociatedValue:
return isInternalBridgingHeader &&
where.getExportedLevel() == ExportedLevel::ImplicitlyExported;
}
}
static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
const ExportContext &where) {
assert(where.mustOnlyReferenceExportedDecls());
auto definingModule = D->getModuleContext();
auto downgradeToWarning = DowngradeToWarning::No;
auto reason = where.getExportabilityReason();
auto DC = where.getDeclContext();
auto SF = DC->getParentSourceFile();
ASTContext &ctx = DC->getASTContext();
auto originKind = getDisallowedOriginKind(D, where, downgradeToWarning);
// Remember that the module defining the decl must be imported publicly.
recordRequiredImportAccessLevelForDecl(
D, DC, AccessLevel::Public,
[&](AttributedImport<ImportedModule> attributedImport) {
if (where.isExported() && reason != ExportabilityReason::General &&
originKind != DisallowedOriginKind::NonPublicImport &&
originKind != DisallowedOriginKind::InternalBridgingHeaderImport) {
// These may be reported twice, for the Type and for the TypeRepr.
ModuleDecl *importedVia = attributedImport.module.importedModule,
*sourceModule = D->getModuleContext();
ctx.Diags.diagnose(loc, diag::module_api_import, D, importedVia,
sourceModule, importedVia == sourceModule,
/*isImplicit*/ false);
}
});
auto commonBehavior = where.behaviorForReferenceToOrigin(D, originKind);
if (commonBehavior == DiagnosticBehavior::Ignore)
return false;
auto fragileKind = where.getFragileFunctionKind();
switch (originKind) {
case DisallowedOriginKind::None:
// The decl does not come from a source that needs to be checked for
// exportability.
return false;
case DisallowedOriginKind::NonPublicImport:
case DisallowedOriginKind::InternalBridgingHeaderImport:
// With a few exceptions, access levels from imports are diagnosed during
// access checking and should be skipped here.
if (!shouldDiagnoseDeclAccess(D, where,
originKind == DisallowedOriginKind::InternalBridgingHeaderImport))
return false;
break;
case DisallowedOriginKind::MissingImport:
// Some diagnostics emitted with the `MemberImportVisibility` feature
// enabled subsume these diagnostics.
if (ctx.LangOpts.hasFeature(Feature::MemberImportVisibility,
/*allowMigration=*/true) &&
SF)
return false;
break;
case DisallowedOriginKind::SPIOnly:
// Availability attributes referring to availability domains from modules
// that are imported @_spiOnly in a -library-level=api will not be printed
// in the public swiftinterface of the module and should therefore not be
// diagnosed for exportability.
if (reason && reason == ExportabilityReason::AvailableAttribute &&
ctx.LangOpts.LibraryLevel == LibraryLevel::API)
return false;
break;
case DisallowedOriginKind::SPIImported:
case DisallowedOriginKind::SPILocal:
case DisallowedOriginKind::ImplementationOnly:
case DisallowedOriginKind::FragileCxxAPI:
case DisallowedOriginKind::ImplementationOnlyMemoryLayout:
break;
}
if (auto accessor = dyn_cast<AccessorDecl>(D)) {
// Only diagnose accessors if their disallowed origin kind differs from
// that of their storage.
if (getDisallowedOriginKind(accessor->getStorage(), where) == originKind)
return false;
}
if (fragileKind.kind == FragileFunctionKind::None) {
DiagnosticBehavior limit = downgradeToWarning == DowngradeToWarning::Yes
? DiagnosticBehavior::Warning
: DiagnosticBehavior::Unspecified;
ctx.Diags.diagnose(loc, diag::decl_from_hidden_module, D,
static_cast<unsigned>(*reason),
definingModule->getName(),
static_cast<unsigned>(originKind))
.limitBehavior(limit.merge(commonBehavior));
D->diagnose(diag::kind_declared_here, D->getDescriptiveKind());
// Suggest fixes for references from class properties in embedded mode.
if (reason == ExportabilityReason::
ImplicitlyPublicVarDeclMissingAttribute ||
reason == ExportabilityReason::
ImplicitlyPublicVarDeclMissingAttributeAndDeinit) {
// Require @_implementationOnly on the property.
ctx.Diags.diagnose(loc,
diag::embedded_class_property_requires_implementation_only);
}
if (reason == ExportabilityReason::
ImplicitlyPublicVarDeclMissingDeinit ||
reason == ExportabilityReason::
ImplicitlyPublicVarDeclMissingAttributeAndDeinit) {
// Require @export(interface) deinit on the class.
auto *parentClass = dyn_cast_or_null<ClassDecl>(DC->getAsDecl());
if (parentClass) {
auto inFlight = parentClass->diagnose(
diag::embedded_classes_require_export_interface_deinit);
auto insertLoc = parentClass->getBraces().End;
StringRef insertIndent =
Lexer::getIndentationForLine(ctx.SourceMgr, insertLoc);
std::string fixit = (insertIndent + "@export(interface)\n" +
insertIndent + "deinit {}\n").str();
inFlight.fixItInsert(insertLoc, fixit);
}
}
} else {
ctx.Diags.diagnose(loc, diag::inlinable_decl_ref_from_hidden_module, D,
fragileKind.getSelector(), definingModule->getName(),
static_cast<unsigned>(originKind))
.warnUntilLanguageModeIf(downgradeToWarning == DowngradeToWarning::Yes,
LanguageMode::v6)
.limitBehaviorIfMorePermissive(commonBehavior);
if (originKind == DisallowedOriginKind::MissingImport &&
downgradeToWarning == DowngradeToWarning::Yes)
addMissingImport(loc, D, where);
}
// If limited by an import, note which one.
ImportAccessLevel import = D->getImportAccessFrom(DC);
if (originKind == DisallowedOriginKind::NonPublicImport) {
assert(import.has_value() &&
import->accessLevel < AccessLevel::Public &&
"The import should still be non-public");
ctx.Diags.diagnose(import->importLoc,
diag::decl_import_via_here, D,
import->accessLevel,
import->module.importedModule,
import->module.importedModule
->isClangHeaderImportModule());
}
return true;
}
bool TypeChecker::diagnoseDeclRefExportability(SourceLoc loc,
const ValueDecl *D,
const ExportContext &where) {
if (!where.mustOnlyReferenceExportedDecls())
return false;
if (diagnoseValueDeclRefExportability(loc, D, where))
return true;
if (auto *TAD = dyn_cast<TypeAliasDecl>(D))
if (diagnoseTypeAliasDeclRefExportability(loc, TAD, where))
return true;
return false;
}
bool
TypeChecker::diagnoseConformanceExportability(SourceLoc loc,
const RootProtocolConformance *rootConf,
const ExtensionDecl *ext,
const ExportContext &where,
bool warnIfConformanceUnavailablePreSwift6) {
if (!where.mustOnlyReferenceExportedDecls())
return false;
// Skip the special Sendable and Copyable conformances we synthesized in
// ASTContext::getBuiltinTupleDecl().
if (ext->getParentModule()->isBuiltinModule())
return false;
const DeclContext *DC = where.getDeclContext();
ModuleDecl *M = ext->getParentModule();
ASTContext &ctx = M->getASTContext();
// Remember that the module defining the conformance must be imported
// publicly.
recordRequiredImportAccessLevelForDecl(
ext, DC, AccessLevel::Public,
[&](AttributedImport<ImportedModule> attributedImport) {
ModuleDecl *importedVia = attributedImport.module.importedModule,
*sourceModule = ext->getModuleContext();
ctx.Diags.diagnose(loc, diag::module_api_import_conformance,
rootConf->getType(), rootConf->getProtocol(),
importedVia, sourceModule,
importedVia == sourceModule);
});
auto originKind = getDisallowedOriginKind(ext, where);
auto commonBehavior =
where.behaviorForReferenceToOrigin(/*D=*/nullptr, originKind);
if (commonBehavior == DiagnosticBehavior::Ignore)
return false;
auto reason = where.getExportabilityReason();
if (!reason.has_value())
reason = ExportabilityReason::General;
ctx.Diags
.diagnose(loc, diag::conformance_from_implementation_only_module,
rootConf->getType(), rootConf->getProtocol()->getName(),
static_cast<unsigned>(*reason), M->getName(),
static_cast<unsigned>(originKind))
.warnUntilLanguageModeIf(
(warnIfConformanceUnavailablePreSwift6 &&
originKind != DisallowedOriginKind::SPIOnly &&
originKind != DisallowedOriginKind::NonPublicImport) ||
originKind == DisallowedOriginKind::MissingImport,
LanguageMode::v6)
.limitBehaviorIfMorePermissive(commonBehavior);
if (!ctx.LangOpts.hasFeature(Feature::StrictAccessControl) &&
originKind == DisallowedOriginKind::MissingImport &&
!ctx.isLanguageModeAtLeast(LanguageMode::v6))
addMissingImport(loc, ext, where);
// If limited by an import, note which one.
if (originKind == DisallowedOriginKind::NonPublicImport) {
ImportAccessLevel limitImport = ext->getImportAccessFrom(DC);
assert(limitImport.has_value() &&
limitImport->accessLevel < AccessLevel::Public &&
"The import should still be non-public");
ctx.Diags.diagnose(limitImport->importLoc,
diag::decl_import_via_here, ext,
limitImport->accessLevel,
limitImport->module.importedModule,
limitImport->module.importedModule
->isClangHeaderImportModule());
}
return true;
}