//===--- 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
                             ->isClangBridgingHeaderImportModule());
  }

  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
                         ->isClangBridgingHeaderImportModule());
  }

  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
                         ->isClangBridgingHeaderImportModule());
  }

  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
                         ->isClangBridgingHeaderImportModule());
  }

  return true;
}
