Skip to content

[SE-0458] Bring implementation in line with the latest proposal revision #79424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8149,15 +8149,22 @@ NOTE(note_reference_exclusivity_unchecked,none,
NOTE(note_use_of_unsafe_conformance_is_unsafe,none,
"@unsafe conformance of %0 to %kind1 involves unsafe code",
(Type, const ValueDecl *))
NOTE(note_unsafe_storage,none,
"%kindbase1 involves unsafe type %2", (bool, const ValueDecl *, Type))

GROUPED_WARNING(decl_signature_involves_unsafe,Unsafe,none,
"%kindbase0 has an interface that involves unsafe types",
GROUPED_WARNING(decl_unsafe_storage,Unsafe,none,
"%kindbase0 has storage involving unsafe types",
(const Decl *))
NOTE(decl_signature_mark_unsafe,none,
"add '@unsafe' to indicate that this declaration is unsafe to use",
NOTE(decl_storage_mark_unsafe,none,
"add '@unsafe' if this type is also unsafe to use",
())
NOTE(decl_signature_mark_safe,none,
"add '@safe' to indicate that this declaration is memory-safe to use", ())
NOTE(decl_storage_mark_safe,none,
"add '@safe' if this type encapsulates the unsafe storage in "
"a safe interface", ())

GROUPED_WARNING(unsafe_superclass,Unsafe,none,
"%kindbase0 has superclass involving unsafe type %1",
(const ValueDecl *, Type))

GROUPED_WARNING(conformance_involves_unsafe,Unsafe,none,
"conformance of %0 to %kind1 involves unsafe code; use '@unsafe' to "
Expand Down
15 changes: 15 additions & 0 deletions include/swift/AST/UnsafeUse.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class UnsafeUse {
NonisolatedUnsafe,
/// A reference to an unsafe declaration.
ReferenceToUnsafe,
/// A reference to an unsafe storage.
ReferenceToUnsafeStorage,
/// A reference to a typealias that is not itself unsafe, but has
/// an unsafe underlying type.
ReferenceToUnsafeThroughTypealias,
Expand Down Expand Up @@ -113,6 +115,7 @@ class UnsafeUse {
kind == ExclusivityUnchecked ||
kind == NonisolatedUnsafe ||
kind == ReferenceToUnsafe ||
kind == ReferenceToUnsafeStorage ||
kind == ReferenceToUnsafeThroughTypealias ||
kind == CallToUnsafe);

Expand Down Expand Up @@ -179,6 +182,12 @@ class UnsafeUse {
decl, type, location);
}

static UnsafeUse forReferenceToUnsafeStorage(const Decl *decl,
Type type,
SourceLoc location) {
return forReference(ReferenceToUnsafeStorage, decl, type, location);
}

static UnsafeUse forReferenceToUnsafeThroughTypealias(const Decl *decl,
Type type,
SourceLoc location) {
Expand Down Expand Up @@ -215,6 +224,7 @@ class UnsafeUse {
case ExclusivityUnchecked:
case NonisolatedUnsafe:
case ReferenceToUnsafe:
case ReferenceToUnsafeStorage:
case ReferenceToUnsafeThroughTypealias:
case CallToUnsafe:
return SourceLoc(
Expand Down Expand Up @@ -246,6 +256,7 @@ class UnsafeUse {
case ExclusivityUnchecked:
case NonisolatedUnsafe:
case ReferenceToUnsafe:
case ReferenceToUnsafeStorage:
case ReferenceToUnsafeThroughTypealias:
case CallToUnsafe:
storage.entity.location = loc.getOpaquePointerValue();
Expand All @@ -266,6 +277,7 @@ class UnsafeUse {
case ExclusivityUnchecked:
case NonisolatedUnsafe:
case ReferenceToUnsafe:
case ReferenceToUnsafeStorage:
case ReferenceToUnsafeThroughTypealias:
case CallToUnsafe:
return storage.entity.decl;
Expand Down Expand Up @@ -299,6 +311,7 @@ class UnsafeUse {
case NonisolatedUnsafe:
case ReferenceToUnsafe:
case ReferenceToUnsafeThroughTypealias:
case ReferenceToUnsafeStorage:
case CallToUnsafe:
case UnsafeConformance:
case PreconcurrencyImport:
Expand All @@ -324,6 +337,7 @@ class UnsafeUse {
case ExclusivityUnchecked:
case NonisolatedUnsafe:
case ReferenceToUnsafe:
case ReferenceToUnsafeStorage:
case ReferenceToUnsafeThroughTypealias:
case CallToUnsafe:
return storage.entity.type;
Expand All @@ -348,6 +362,7 @@ class UnsafeUse {
case ExclusivityUnchecked:
case NonisolatedUnsafe:
case ReferenceToUnsafe:
case ReferenceToUnsafeStorage:
case ReferenceToUnsafeThroughTypealias:
case CallToUnsafe:
case PreconcurrencyImport:
Expand Down
20 changes: 20 additions & 0 deletions include/swift/ClangImporter/ClangImporterRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace swift {
class Decl;
class DeclName;
class EnumDecl;
enum class ExplicitSafety;

/// The input type for a clang direct lookup request.
struct ClangDirectLookupDescriptor final {
Expand Down Expand Up @@ -564,6 +565,25 @@ class ClangTypeEscapability
void simple_display(llvm::raw_ostream &out, EscapabilityLookupDescriptor desc);
SourceLoc extractNearestSourceLoc(EscapabilityLookupDescriptor desc);

/// Determine the safety of the given Clang declaration.
class ClangDeclExplicitSafety
: public SimpleRequest<ClangDeclExplicitSafety,
ExplicitSafety(SafeUseOfCxxDeclDescriptor),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

// Source location
SourceLoc getNearestLoc() const { return SourceLoc(); };
bool isCached() const;

private:
friend SimpleRequest;

// Evaluation.
ExplicitSafety evaluate(Evaluator &evaluator, SafeUseOfCxxDeclDescriptor desc) const;
};

#define SWIFT_TYPEID_ZONE ClangImporter
#define SWIFT_TYPEID_HEADER "swift/ClangImporter/ClangImporterTypeIDZone.def"
#include "swift/Basic/DefineTypeIDZone.h"
Expand Down
3 changes: 3 additions & 0 deletions include/swift/ClangImporter/ClangImporterTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ SWIFT_REQUEST(ClangImporter, CustomRefCountingOperation,
SWIFT_REQUEST(ClangImporter, ClangTypeEscapability,
CxxEscapability(EscapabilityLookupDescriptor), Cached,
NoLocationInfo)
SWIFT_REQUEST(ClangImporter, ClangDeclExplicitSafety,
ExplicitSafety(SafeUseOfCxxDeclDescriptor), Cached,
NoLocationInfo)
8 changes: 8 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,14 @@ ExplicitSafety Decl::getExplicitSafety() const {
if (auto safety = getExplicitSafetyFromAttrs(this))
return *safety;

// If this declaration is from C, ask the Clang importer.
if (auto clangDecl = getClangDecl()) {
ASTContext &ctx = getASTContext();
return evaluateOrDefault(
ctx.evaluator, ClangDeclExplicitSafety({clangDecl, ctx}),
ExplicitSafety::Unspecified);
}

// Inference: Check the enclosing context.
if (auto enclosingDC = getDeclContext()) {
// Is this an extension with @safe or @unsafe on it?
Expand Down
9 changes: 8 additions & 1 deletion lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1934,6 +1934,12 @@ unsigned AbstractClosureExpr::getDiscriminator() const {
evaluateOrDefault(
ctx.evaluator, LocalDiscriminatorsRequest{getParent()}, 0);

#if NDEBUG
static constexpr bool useFallbackDiscriminator = true;
#else
static constexpr bool useFallbackDiscriminator = false;
#endif

// If we don't have a discriminator, and either
// 1. We have ill-formed code and we're able to assign a discriminator, or
// 2. We are in a macro expansion buffer
Expand All @@ -1943,7 +1949,8 @@ unsigned AbstractClosureExpr::getDiscriminator() const {
if (getRawDiscriminator() == InvalidDiscriminator &&
(ctx.Diags.hadAnyError() ||
getParentSourceFile()->getFulfilledMacroRole() != std::nullopt ||
getParent()->isModuleScopeContext())) {
getParent()->isModuleScopeContext() ||
useFallbackDiscriminator)) {
auto discriminator = ctx.getNextDiscriminator(getParent());
ctx.setMaxAssignedDiscriminator(getParent(), discriminator + 1);
const_cast<AbstractClosureExpr *>(this)->
Expand Down
100 changes: 100 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8215,6 +8215,106 @@ CustomRefCountingOperationResult CustomRefCountingOperation::evaluate(
return {CustomRefCountingOperationResult::tooManyFound, nullptr, name};
}

/// Check whether the given Clang type involves an unsafe type.
static bool hasUnsafeType(
Evaluator &evaluator, ASTContext &swiftContext, clang::QualType clangType
) {
// Handle pointers.
auto pointeeType = clangType->getPointeeType();
if (!pointeeType.isNull()) {
// Function pointers are okay.
if (pointeeType->isFunctionType())
return false;

// Pointers to record types are okay if they come in as foreign reference
// types.
if (auto recordDecl = pointeeType->getAsRecordDecl()) {
if (hasImportAsRefAttr(recordDecl))
return false;
}

// All other pointers are considered unsafe.
return true;
}

// Handle records recursively.
if (auto recordDecl = clangType->getAsTagDecl()) {
auto safety = evaluateOrDefault(
evaluator,
ClangDeclExplicitSafety({recordDecl, swiftContext}),
ExplicitSafety::Unspecified);
switch (safety) {
case ExplicitSafety::Unsafe:
return true;

case ExplicitSafety::Safe:
case ExplicitSafety::Unspecified:
return false;
}
}

// Everything else is safe.
return false;
}

ExplicitSafety ClangDeclExplicitSafety::evaluate(
Evaluator &evaluator,
SafeUseOfCxxDeclDescriptor desc
) const {
// FIXME: Somewhat duplicative with importAsUnsafe.
// FIXME: Also similar to hasPointerInSubobjects
// FIXME: should probably also subsume IsSafeUseOfCxxDecl

// Explicitly unsafe.
auto decl = desc.decl;
if (hasUnsafeAPIAttr(decl) || hasSwiftAttribute(decl, "unsafe"))
return ExplicitSafety::Unsafe;

// Explicitly safe.
if (hasSwiftAttribute(decl, "safe"))
return ExplicitSafety::Safe;

// Enums are always safe.
if (isa<clang::EnumDecl>(decl))
return ExplicitSafety::Safe;

// If it's not a record, leave it unspecified.
auto recordDecl = dyn_cast<clang::RecordDecl>(decl);
if (!recordDecl)
return ExplicitSafety::Unspecified;

// Escapable and non-escapable annotations imply that the declaration is
// safe.
if (hasNonEscapableAttr(recordDecl) || hasEscapableAttr(recordDecl))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking for the attribute explicitly, we should use the ClangTypeEscapability request.
This will do a couple additional things:

  • Inject escapability annotations for the STL
  • Calculate the correct escapability for template instantiations (e.g., std::vector<non-escapable> is escapable) considering the conditional escapability annotations
  • For aggregate types it attempts to infer the escapability based on the fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened PR: #79434

return ExplicitSafety::Safe;

// If we don't have a definition, leave it unspecified.
recordDecl = recordDecl->getDefinition();
if (!recordDecl)
return ExplicitSafety::Unspecified;

// If this is a C++ class, check its bases.
if (auto cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl)) {
for (auto base : cxxRecordDecl->bases()) {
if (hasUnsafeType(evaluator, desc.ctx, base.getType()))
return ExplicitSafety::Unsafe;
}
}

// Check the fields.
for (auto field : recordDecl->fields()) {
if (hasUnsafeType(evaluator, desc.ctx, field->getType()))
return ExplicitSafety::Unsafe;
}

// Okay, call it safe.
return ExplicitSafety::Safe;
}

bool ClangDeclExplicitSafety::isCached() const {
return isa<clang::RecordDecl>(std::get<0>(getStorage()).decl);
}

void ClangImporter::withSymbolicFeatureEnabled(
llvm::function_ref<void(void)> callback) {
llvm::SaveAndRestore<bool> oldImportSymbolicCXXDecls(
Expand Down
30 changes: 2 additions & 28 deletions lib/Sema/TypeCheckAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1332,7 +1332,7 @@ class AccessControlChecker : public AccessControlCheckerBase,
if (macroDecl) {
diagnoseDeclAvailability(
macroDecl, customAttr->getTypeRepr()->getSourceRange(), nullptr,
ExportContext::forDeclSignature(const_cast<Decl *>(D), nullptr),
ExportContext::forDeclSignature(const_cast<Decl *>(D)),
std::nullopt);
}
}
Expand Down Expand Up @@ -2678,35 +2678,9 @@ void swift::checkAccessControl(Decl *D) {
if (isa<AccessorDecl>(D))
return;

// If we need to gather all unsafe uses from the declaration signature,
// do so now.
llvm::SmallVector<UnsafeUse, 2> unsafeUsesVec;
llvm::SmallVectorImpl<UnsafeUse> *unsafeUses = nullptr;
if (D->getASTContext().LangOpts.hasFeature(Feature::WarnUnsafe) &&
!D->isImplicit() &&
D->getExplicitSafety() == ExplicitSafety::Unspecified) {
unsafeUses = &unsafeUsesVec;
}

auto where = ExportContext::forDeclSignature(D, unsafeUses);
auto where = ExportContext::forDeclSignature(D);
if (where.isImplicit())
return;

DeclAvailabilityChecker(where).visit(D);

// If there were any unsafe uses, this declaration needs "@unsafe".
if (!unsafeUsesVec.empty()) {
D->diagnose(diag::decl_signature_involves_unsafe, D);

ASTContext &ctx = D->getASTContext();
auto insertLoc = D->getAttributeInsertionLoc(/*forModifier=*/false);

ctx.Diags.diagnose(insertLoc, diag::decl_signature_mark_unsafe)
.fixItInsert(insertLoc, "@unsafe ");
ctx.Diags.diagnose(insertLoc, diag::decl_signature_mark_safe)
.fixItInsert(insertLoc, "@safe ");

std::for_each(unsafeUsesVec.begin(), unsafeUsesVec.end(),
diagnoseUnsafeUse);
}
}
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3313,7 +3313,7 @@ SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
return nullptr;
}

auto where = ExportContext::forDeclSignature(D, nullptr);
auto where = ExportContext::forDeclSignature(D);
diagnoseDeclAvailability(mainFunction, attr->getRange(), nullptr, where,
std::nullopt);

Expand Down
10 changes: 4 additions & 6 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,7 @@ static void computeExportContextBits(ASTContext &Ctx, Decl *D, bool *spi,
}
}

ExportContext ExportContext::forDeclSignature(
Decl *D, llvm::SmallVectorImpl<UnsafeUse> *unsafeUses) {
ExportContext ExportContext::forDeclSignature(Decl *D) {
auto &Ctx = D->getASTContext();

auto *DC = D->getInnermostDeclContext();
Expand All @@ -263,7 +262,7 @@ ExportContext ExportContext::forDeclSignature(

bool exported = ::isExported(D);

return ExportContext(DC, availabilityContext, fragileKind, unsafeUses,
return ExportContext(DC, availabilityContext, fragileKind, nullptr,
spi, exported, implicit);
}

Expand All @@ -286,8 +285,7 @@ ExportContext ExportContext::forFunctionBody(DeclContext *DC, SourceLoc loc) {
ExportContext ExportContext::forConformance(DeclContext *DC,
ProtocolDecl *proto) {
assert(isa<ExtensionDecl>(DC) || isa<NominalTypeDecl>(DC));
auto where = forDeclSignature(DC->getInnermostDeclarationDeclContext(),
nullptr);
auto where = forDeclSignature(DC->getInnermostDeclarationDeclContext());

where.Exported &= proto->getFormalAccessScope(
DC, /*usableFromInlineAsPublic*/true).isPublic();
Expand Down Expand Up @@ -2947,7 +2945,7 @@ void swift::diagnoseOverrideOfUnavailableDecl(ValueDecl *override,

// FIXME: [availability] Take an unsatisfied constraint as input instead of
// recomputing it.
ExportContext where = ExportContext::forDeclSignature(override, nullptr);
ExportContext where = ExportContext::forDeclSignature(override);
auto constraint =
getUnsatisfiedAvailabilityConstraint(base, where.getAvailability());
if (!constraint)
Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/TypeCheckAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ class ExportContext {
///
/// If the declaration is exported, the resulting context is restricted to
/// referencing exported types only. Otherwise it can reference anything.
static ExportContext forDeclSignature(
Decl *D, llvm::SmallVectorImpl<UnsafeUse> *unsafeUses);
static ExportContext forDeclSignature(Decl *D);

/// Create an instance describing the declarations that can be referenced
/// from the given function's body.
Expand Down
Loading