Files
swift-mirror/lib/Sema/ResilienceDiagnostics.cpp
Alexis Laferrière ed85a12b59 Merge pull request #85317 from xymus/non-neic-funcs-and-typealiases
Sema: Allow more embedded code to reference non-public imports
2025-11-07 15:19:52 -08:00

485 lines
20 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);
// Public declarations are OK, even if they're SPI or came from an
// implementation-only import. We'll diagnose exportability violations
// from diagnoseDeclRefExportability().
if (declAccessScope.isPublic())
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.isSwiftVersionAtLeast(5) &&
!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.isSwiftVersionAtLeast(4, 2))
return false;
if (!Context.isSwiftVersionAtLeast(5))
downgradeToWarning = DowngradeToWarning::Yes;
}
// Swift 4.2 did not check accessor accessibility.
if (auto accessor = dyn_cast<AccessorDecl>(D)) {
if (!accessor->isInitAccessor() && !Context.isSwiftVersionAtLeast(5))
downgradeToWarning = DowngradeToWarning::Yes;
}
// Swift 5.0 did not check the underlying types of local typealiases.
if (isa<TypeAliasDecl>(DC) && !Context.isSwiftVersionAtLeast(6))
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->getTopLevelModule() == sourceModule);
});
auto ignoredDowngradeToWarning = DowngradeToWarning::No;
auto originKind =
getDisallowedOriginKind(D, where, ignoredDowngradeToWarning);
if (where.canReferenceOrigin(originKind))
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))
.warnUntilSwiftVersionIf(warnPreSwift6, 6);
} 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))
.warnUntilSwiftVersionIf(warnPreSwift6, 6);
}
D->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
if (originKind == DisallowedOriginKind::MissingImport &&
!ctx.LangOpts.isSwiftVersionAtLeast(6))
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) {
auto reason = where.getExportabilityReason();
auto DC = where.getDeclContext();
if (!reason)
return false;
switch (*reason) {
case ExportabilityReason::ExtensionWithPublicMembers:
case ExportabilityReason::ExtensionWithConditionalConformances:
return true;
case ExportabilityReason::Inheritance:
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::General:
case ExportabilityReason::ResultBuilder:
case ExportabilityReason::PropertyWrapper:
case ExportabilityReason::PublicVarDecl:
return false;
}
}
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->getTopLevelModule() == sourceModule,
/*isImplicit*/ 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))
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;
LLVM_FALLTHROUGH;
case DisallowedOriginKind::SPIImported:
case DisallowedOriginKind::SPILocal:
if (fragileKind.kind == FragileFunctionKind::EmbeddedAlwaysEmitIntoClient)
return false;
break;
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);
D->diagnose(diag::kind_declared_here, D->getDescriptiveKind());
} else {
// Only implicitly imported decls should be reported as a warning,
// and only for language versions below Swift 6.
assert(downgradeToWarning == DowngradeToWarning::No ||
originKind == DisallowedOriginKind::MissingImport &&
"Only implicitly imported decls should be reported as a warning.");
ctx.Diags.diagnose(loc, diag::inlinable_decl_ref_from_hidden_module, D,
fragileKind.getSelector(), definingModule->getName(),
static_cast<unsigned>(originKind))
.warnUntilSwiftVersionIf(downgradeToWarning == DowngradeToWarning::Yes,
6);
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->getTopLevelModule() == sourceModule);
});
auto originKind = getDisallowedOriginKind(ext, where);
if (where.canReferenceOrigin(originKind))
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))
.warnUntilSwiftVersionIf((warnIfConformanceUnavailablePreSwift6 &&
originKind != DisallowedOriginKind::SPIOnly &&
originKind != DisallowedOriginKind::NonPublicImport) ||
originKind == DisallowedOriginKind::MissingImport,
6);
if (originKind == DisallowedOriginKind::MissingImport &&
!ctx.LangOpts.isSwiftVersionAtLeast(6))
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;
}