Files
swift-mirror/lib/AST/AvailabilityContext.cpp
Doug Gregor 268d5ccbde Suppress strict safety diagnostics in @unsafe declarations
When a declaration is `@unsafe`, don't emit strict safety diagnostics
for uses of unsafe entities, constructs, or types within it. This
allows one to account for all unsafe behavior in a module using strict
memory safety by marking the appropriate declarations `@unsafe`.

Enhance the strict-safety diagnostics to suggest the addition of
`@unsafe` where it is needed to suppress them, with a Fix-It. Ensure
that all such diagnostics can be suppressed via `@unsafe` so it's
possible to get to the above state.

Also includes a drive-by bug fix where we weren't diagnosing unsafe
methods overriding safe ones in some cases.

Fixes rdar://139467327.
2024-12-12 21:22:41 -08:00

247 lines
8.3 KiB
C++

//===--- AvailabilityContext.cpp - Swift Availability Structures ----------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "swift/AST/AvailabilityContext.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/AvailabilityContextStorage.h"
#include "swift/AST/Decl.h"
#include "swift/Basic/Assertions.h"
using namespace swift;
// Defined as a macro because you can't take the reference of a bitfield.
#define CONSTRAIN_BOOL(_old, _new) \
[&]() { \
if (_old || !_new) \
return false; \
_old = true; \
return true; \
}()
static bool constrainRange(AvailabilityRange &existing,
const AvailabilityRange &other) {
if (!other.isContainedIn(existing))
return false;
existing = other;
return true;
}
bool AvailabilityContext::PlatformInfo::constrainWith(
const PlatformInfo &other) {
bool isConstrained = false;
isConstrained |= constrainRange(Range, other.Range);
if (other.IsUnavailable) {
isConstrained |= constrainUnavailability(other.UnavailablePlatform);
isConstrained |=
CONSTRAIN_BOOL(IsUnavailableInEmbedded, other.IsUnavailableInEmbedded);
}
isConstrained |= CONSTRAIN_BOOL(IsDeprecated, other.IsDeprecated);
isConstrained |= CONSTRAIN_BOOL(AllowsUnsafe, other.AllowsUnsafe);
return isConstrained;
}
bool AvailabilityContext::PlatformInfo::constrainWith(const Decl *decl) {
bool isConstrained = false;
if (auto range = AvailabilityInference::annotatedAvailableRange(decl))
isConstrained |= constrainRange(Range, *range);
if (auto *attr = decl->getUnavailableAttr()) {
isConstrained |= constrainUnavailability(attr->getPlatform());
isConstrained |=
CONSTRAIN_BOOL(IsUnavailableInEmbedded, attr->isForEmbedded());
}
isConstrained |= CONSTRAIN_BOOL(IsDeprecated, decl->isDeprecated());
isConstrained |= CONSTRAIN_BOOL(AllowsUnsafe, decl->allowsUnsafe());
return isConstrained;
}
bool AvailabilityContext::PlatformInfo::constrainUnavailability(
std::optional<PlatformKind> unavailablePlatform) {
if (!unavailablePlatform)
return false;
if (IsUnavailable) {
// Universal unavailability cannot be refined.
if (UnavailablePlatform == PlatformKind::none)
return false;
// There's nothing to do if the platforms already match.
if (UnavailablePlatform == *unavailablePlatform)
return false;
// The new platform must be more restrictive.
if (*unavailablePlatform != PlatformKind::none &&
inheritsAvailabilityFromPlatform(*unavailablePlatform,
UnavailablePlatform))
return false;
}
IsUnavailable = true;
UnavailablePlatform = *unavailablePlatform;
return true;
}
bool AvailabilityContext::PlatformInfo::isContainedIn(
const PlatformInfo &other) const {
if (!Range.isContainedIn(other.Range))
return false;
if (!IsUnavailable && other.IsUnavailable)
return false;
if (IsUnavailable && other.IsUnavailable) {
if (UnavailablePlatform != other.UnavailablePlatform &&
UnavailablePlatform != PlatformKind::none &&
inheritsAvailabilityFromPlatform(UnavailablePlatform,
other.UnavailablePlatform))
return false;
if (IsUnavailableInEmbedded && !other.IsUnavailableInEmbedded)
return false;
}
if (!IsDeprecated && other.IsDeprecated)
return false;
return true;
}
void AvailabilityContext::Storage::Profile(llvm::FoldingSetNodeID &id) const {
Platform.Profile(id);
}
AvailabilityContext
AvailabilityContext::forPlatformRange(const AvailabilityRange &range,
ASTContext &ctx) {
PlatformInfo platformInfo{range, PlatformKind::none,
/*IsUnavailable*/ false,
/*IsUnavailableInEmbedded*/ false,
/*IsDeprecated*/ false,
/*AllowsUnsafe*/ false};
return AvailabilityContext(Storage::get(platformInfo, ctx));
}
AvailabilityContext AvailabilityContext::forInliningTarget(ASTContext &ctx) {
return AvailabilityContext::forPlatformRange(
AvailabilityRange::forInliningTarget(ctx), ctx);
}
AvailabilityContext AvailabilityContext::forDeploymentTarget(ASTContext &ctx) {
return AvailabilityContext::forPlatformRange(
AvailabilityRange::forDeploymentTarget(ctx), ctx);
}
AvailabilityContext
AvailabilityContext::get(const AvailabilityRange &platformAvailability,
std::optional<PlatformKind> unavailablePlatform,
bool deprecated, ASTContext &ctx) {
PlatformInfo platformInfo{platformAvailability,
unavailablePlatform.has_value()
? *unavailablePlatform
: PlatformKind::none,
unavailablePlatform.has_value(),
/*IsUnavailableInEmbedded*/ false, deprecated,
/*AllowsUnsafe*/ false};
return AvailabilityContext(Storage::get(platformInfo, ctx));
}
AvailabilityRange AvailabilityContext::getPlatformRange() const {
return Info->Platform.Range;
}
std::optional<PlatformKind>
AvailabilityContext::getUnavailablePlatformKind() const {
if (Info->Platform.IsUnavailable)
return Info->Platform.UnavailablePlatform;
return std::nullopt;
}
bool AvailabilityContext::isUnavailableInEmbedded() const {
return Info->Platform.IsUnavailableInEmbedded;
}
bool AvailabilityContext::allowsUnsafe() const {
return Info->Platform.AllowsUnsafe;
}
bool AvailabilityContext::isDeprecated() const {
return Info->Platform.IsDeprecated;
}
void AvailabilityContext::constrainWithContext(const AvailabilityContext &other,
ASTContext &ctx) {
PlatformInfo platformAvailability{Info->Platform};
if (platformAvailability.constrainWith(other.Info->Platform)) {
Info = Storage::get(platformAvailability, ctx);
}
}
void AvailabilityContext::constrainWithDecl(const Decl *decl) {
constrainWithDeclAndPlatformRange(decl, AvailabilityRange::alwaysAvailable());
}
void AvailabilityContext::constrainWithPlatformRange(
const AvailabilityRange &platformRange, ASTContext &ctx) {
PlatformInfo platformAvailability{Info->Platform};
if (!constrainRange(platformAvailability.Range, platformRange))
return;
Info = Storage::get(platformAvailability, ctx);
}
void AvailabilityContext::constrainWithDeclAndPlatformRange(
const Decl *decl, const AvailabilityRange &platformRange) {
PlatformInfo platformAvailability{Info->Platform};
bool isConstrained = false;
isConstrained |= platformAvailability.constrainWith(decl);
isConstrained |= constrainRange(platformAvailability.Range, platformRange);
if (!isConstrained)
return;
Info = Storage::get(platformAvailability, decl->getASTContext());
}
bool AvailabilityContext::isContainedIn(const AvailabilityContext other) const {
if (!Info->Platform.isContainedIn(other.Info->Platform))
return false;
return true;
}
static std::string
stringForAvailability(const AvailabilityRange &availability) {
if (availability.isAlwaysAvailable())
return "all";
if (availability.isKnownUnreachable())
return "none";
return availability.getVersionString();
}
void AvailabilityContext::print(llvm::raw_ostream &os) const {
os << "version=" << stringForAvailability(getPlatformRange());
if (auto unavailablePlatform = getUnavailablePlatformKind())
os << " unavailable=" << platformString(*unavailablePlatform);
if (isDeprecated())
os << " deprecated";
}
void AvailabilityContext::dump() const { print(llvm::errs()); }