Make the optional feature StrictMemorySafety migratable

This feature is essentially self-migrating, but fit it into the
migration flow by marking it as migratable, adding
`-strict-memory-safety:migrate`, and introducing a test.
This commit is contained in:
Doug Gregor
2025-05-20 17:06:18 +01:00
parent a32782bcbc
commit abad2fae0f
14 changed files with 58 additions and 12 deletions

View File

@@ -155,6 +155,11 @@
#endif
#endif
#ifndef MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE
#define MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Name) \
OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, #Name)
#endif
#ifndef UPCOMING_FEATURE
#define UPCOMING_FEATURE(FeatureName, SENumber, Version) \
LANGUAGE_FEATURE(FeatureName, SENumber, #FeatureName)
@@ -290,7 +295,7 @@ MIGRATABLE_UPCOMING_FEATURE(NonisolatedNonsendingByDefault, 461, 7)
/// Diagnose uses of language constructs and APIs that can violate memory
/// safety.
OPTIONAL_LANGUAGE_FEATURE(StrictMemorySafety, 458, "Strict memory safety")
MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(StrictMemorySafety, 458, "Strict memory safety")
// Experimental features
@@ -521,6 +526,7 @@ EXPERIMENTAL_FEATURE(ModuleSelector, false)
#undef UPCOMING_FEATURE
#undef MIGRATABLE_UPCOMING_FEATURE
#undef MIGRATABLE_EXPERIMENTAL_FEATURE
#undef MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE
#undef BASELINE_LANGUAGE_FEATURE
#undef OPTIONAL_LANGUAGE_FEATURE
#undef CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE

View File

@@ -872,7 +872,9 @@ namespace swift {
FeatureState getFeatureState(Feature feature) const;
/// Returns whether the given feature is enabled.
bool hasFeature(Feature feature) const;
///
/// If allowMigration is set, also returns true when the feature has been enabled for migration.
bool hasFeature(Feature feature, bool allowMigration = false) const;
/// Returns whether a feature with the given name is enabled. Returns
/// `false` if a feature by this name is not known.

View File

@@ -1018,6 +1018,10 @@ def strict_memory_safety : Flag<["-"], "strict-memory-safety">,
Flags<[FrontendOption, ModuleInterfaceOptionIgnorable,
SwiftAPIDigesterOption, SwiftSynthesizeInterfaceOption]>,
HelpText<"Enable strict memory safety checking">;
def strict_memory_safety_migrate : Flag<["-"], "strict-memory-safety:migrate">,
Flags<[FrontendOption, ModuleInterfaceOptionIgnorable,
SwiftAPIDigesterOption, SwiftSynthesizeInterfaceOption]>,
HelpText<"Enable migration to strict memory safety checking">;
def Rpass_EQ : Joined<["-"], "Rpass=">,
Flags<[FrontendOption]>,

View File

@@ -73,6 +73,7 @@ bool Feature::isMigratable() const {
switch (kind) {
#define MIGRATABLE_UPCOMING_FEATURE(FeatureName, SENumber, Version)
#define MIGRATABLE_EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd)
#define MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Name)
#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \
case Feature::FeatureName:
#include "swift/Basic/Features.def"
@@ -82,6 +83,8 @@ bool Feature::isMigratable() const {
case Feature::FeatureName:
#define MIGRATABLE_EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \
case Feature::FeatureName:
#define MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Name) \
case Feature::FeatureName:
#include "swift/Basic/Features.def"
return true;
}

View File

@@ -335,13 +335,17 @@ LangOptions::FeatureState LangOptions::getFeatureState(Feature feature) const {
return state;
}
bool LangOptions::hasFeature(Feature feature) const {
if (featureStates.getState(feature).isEnabled())
bool LangOptions::hasFeature(Feature feature, bool allowMigration) const {
auto state = featureStates.getState(feature);
if (state.isEnabled())
return true;
if (auto version = feature.getLanguageVersion())
return isSwiftVersionAtLeast(*version);
if (allowMigration && state.isEnabledForMigration())
return true;
return false;
}

View File

@@ -284,7 +284,8 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
options::OPT_disable_experimental_feature,
options::OPT_enable_upcoming_feature,
options::OPT_disable_upcoming_feature});
inputArgs.AddLastArg(arguments, options::OPT_strict_memory_safety);
inputArgs.AddLastArg(arguments, options::OPT_strict_memory_safety,
options::OPT_strict_memory_safety_migrate);
inputArgs.AddLastArg(arguments, options::OPT_warn_implicit_overrides);
inputArgs.AddLastArg(arguments, options::OPT_typo_correction_limit);
inputArgs.AddLastArg(arguments, options::OPT_enable_app_extension);

View File

@@ -996,6 +996,8 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args,
if (Args.hasArg(OPT_strict_memory_safety))
Opts.enableFeature(Feature::StrictMemorySafety);
else if (Args.hasArg(OPT_strict_memory_safety_migrate))
Opts.enableFeature(Feature::StrictMemorySafety, /*forMigration=*/true);
return HadError;
}

View File

@@ -171,7 +171,7 @@ deriveBodyDistributed_doInvokeOnReturn(AbstractFunctionDecl *afd, void *arg) {
new (C) DeclRefExpr(ConcreteDeclRef(returnTypeParam),
dloc, implicit))}));
if (C.LangOpts.hasFeature(Feature::StrictMemorySafety))
if (C.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true))
resultLoadCall = new (C) UnsafeExpr(sloc, resultLoadCall, Type(), true);
auto resultPattern = NamedPattern::createImplicit(C, resultVar);

View File

@@ -106,7 +106,7 @@ deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl, void *) {
auto *argList = ArgumentList::forImplicitCallTo(functionRef->getName(),
{selfRef, typeExpr}, C);
Expr *call = CallExpr::createImplicit(C, functionRef, argList);
if (C.LangOpts.hasFeature(Feature::StrictMemorySafety))
if (C.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true))
call = UnsafeExpr::createImplicit(C, SourceLoc(), call);
auto *returnStmt = ReturnStmt::createImplicit(C, call);
auto body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt),

View File

@@ -2257,7 +2257,7 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
diagnoseOverrideForAvailability(override, base);
}
if (ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) {
if (ctx.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true)) {
// If the override is unsafe but the base declaration is not, then the
// inheritance itself is unsafe.
auto subs = SubstitutionMap::getOverrideSubstitutions(base, override);

View File

@@ -2413,7 +2413,8 @@ public:
// If strict memory safety checking is enabled, check the storage
// of the nominal type.
if (Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety) &&
if (Ctx.LangOpts.hasFeature(
Feature::StrictMemorySafety, /*allowMigration=*/true) &&
!isa<ProtocolDecl>(nominal)) {
checkUnsafeStorage(nominal);
}

View File

@@ -4554,7 +4554,8 @@ private:
if (classification.hasUnsafe()) {
// If there is no such effect, complain.
if (S->getUnsafeLoc().isInvalid() &&
Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) {
Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety,
/*allowMigration=*/true)) {
auto insertionLoc = S->getPattern()->getStartLoc();
Ctx.Diags.diagnose(S->getForLoc(), diag::for_unsafe_without_unsafe)
.fixItInsert(insertionLoc, "unsafe ");
@@ -4801,7 +4802,7 @@ private:
void diagnoseUncoveredUnsafeSite(
const Expr *anchor, ArrayRef<UnsafeUse> unsafeUses) {
if (!Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety))
if (!Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true))
return;
const auto &[loc, insertText] = getFixItForUncoveredSite(anchor, "unsafe");

View File

@@ -2646,7 +2646,8 @@ checkIndividualConformance(NormalProtocolConformance *conformance) {
// If we're enforcing strict memory safety and this conformance hasn't
// opted out, look for safe/unsafe witness mismatches.
if (conformance->getExplicitSafety() == ExplicitSafety::Unspecified &&
Context.LangOpts.hasFeature(Feature::StrictMemorySafety)) {
Context.LangOpts.hasFeature(Feature::StrictMemorySafety,
/*allowMigration=*/true)) {
// Collect all of the unsafe uses for this conformance.
SmallVector<UnsafeUse, 2> unsafeUses;
for (auto requirement: Proto->getMembers()) {

21
test/Unsafe/migrate.swift Normal file
View File

@@ -0,0 +1,21 @@
// RUN: %target-swift-frontend -typecheck -verify -swift-version 6 -strict-memory-safety:migrate %s
// REQUIRES: concurrency
@preconcurrency import _Concurrency
@unsafe func f() { }
func g() {
f() // expected-warning{{expression uses unsafe constructs but is not marked with 'unsafe'}}{{3-3=unsafe }}
// expected-note@-1{{reference to unsafe global function 'f()'}}
}
protocol P {
func f()
}
struct Conforming: P {
// expected-warning@-1{{conformance of 'Conforming' to protocol 'P' involves unsafe code; use '@unsafe' to indicate that the conformance is not memory-safe}}{{20-20=@unsafe }}
@unsafe func f() { } // expected-note{{unsafe instance method 'f()' cannot satisfy safe requirement}}
}