From 52c46f838aaa47e11b8539e92bf2eaa13011cd61 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 25 Feb 2025 16:10:07 -0800 Subject: [PATCH 1/5] Parsing and type checking for the definition of isolated conformances Allow a conformance to be "isolated", meaning that it stays in the same isolation domain as the conforming type. Only allow this for global-actor-isolated types. When a conformance is isolated, a nonisolated requirement can be witnessed by a declaration with the same global actor isolation as the enclosing type. --- include/swift/AST/ConformanceAttributes.h | 5 ++ include/swift/AST/Decl.h | 10 ++- include/swift/AST/DiagnosticsSema.def | 9 +++ include/swift/AST/ProtocolConformance.h | 7 ++- .../swift/AST/ProtocolConformanceOptions.h | 4 +- include/swift/Basic/Features.def | 4 ++ lib/AST/ConformanceLookupTable.h | 7 +++ lib/AST/Decl.cpp | 2 + lib/AST/FeatureSet.cpp | 5 ++ lib/AST/NameLookup.cpp | 25 +++++++- lib/Parse/ParseType.cpp | 4 +- lib/Sema/TypeCheckProtocol.cpp | 63 ++++++++++++++++--- lib/Sema/TypeCheckProtocol.h | 4 +- lib/Sema/TypeCheckType.cpp | 8 ++- test/Concurrency/isolated_conformance.swift | 42 +++++++++++++ 15 files changed, 175 insertions(+), 24 deletions(-) create mode 100644 test/Concurrency/isolated_conformance.swift diff --git a/include/swift/AST/ConformanceAttributes.h b/include/swift/AST/ConformanceAttributes.h index 4f9242c6b3902..aa3ec21dff4dc 100644 --- a/include/swift/AST/ConformanceAttributes.h +++ b/include/swift/AST/ConformanceAttributes.h @@ -27,6 +27,9 @@ struct ConformanceAttributes { /// The location of the "unsafe" attribute if present. SourceLoc unsafeLoc; + + /// The location of the "@isolated" attribute if present. + SourceLoc isolatedLoc; /// Merge other conformance attributes into this set. ConformanceAttributes & @@ -37,6 +40,8 @@ struct ConformanceAttributes { preconcurrencyLoc = other.preconcurrencyLoc; if (other.unsafeLoc.isValid()) unsafeLoc = other.unsafeLoc; + if (other.isolatedLoc.isValid()) + isolatedLoc = other.isolatedLoc; return *this; } }; diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 1de660e903aec..92e7afe8eeb2d 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1836,12 +1836,13 @@ struct InheritedEntry : public TypeLoc { bool isPreconcurrency() const { return getOptions().contains(ProtocolConformanceFlags::Preconcurrency); } + bool isIsolated() const { + return getOptions().contains(ProtocolConformanceFlags::Isolated); + } ExplicitSafety getExplicitSafety() const { if (getOptions().contains(ProtocolConformanceFlags::Unsafe)) return ExplicitSafety::Unsafe; - if (getOptions().contains(ProtocolConformanceFlags::Safe)) - return ExplicitSafety::Safe; return ExplicitSafety::Unspecified; } @@ -1852,13 +1853,10 @@ struct InheritedEntry : public TypeLoc { } void setOption(ExplicitSafety safety) { - RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe - - ProtocolConformanceFlags::Safe).toRaw(); + RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe).toRaw(); switch (safety) { case ExplicitSafety::Unspecified: - break; case ExplicitSafety::Safe: - RawOptions = (getOptions() | ProtocolConformanceFlags::Safe).toRaw(); break; case ExplicitSafety::Unsafe: RawOptions = (getOptions() | ProtocolConformanceFlags::Unsafe).toRaw(); diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 95ad9917b891f..6dda2e4cc5926 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2722,8 +2722,17 @@ WARNING(add_predates_concurrency_import,none, GROUPED_WARNING(remove_predates_concurrency_import,PreconcurrencyImport, DefaultIgnore, "'@preconcurrency' attribute on module %0 has no effect", (Identifier)) +NOTE(add_isolated_to_conformance,none, + "add 'isolated' to the %0 conformance to restrict it to %1 code", + (DeclName, ActorIsolation)) NOTE(add_preconcurrency_to_conformance,none, "add '@preconcurrency' to the %0 conformance to defer isolation checking to run time", (DeclName)) +ERROR(isolated_conformance_not_global_actor_isolated,none, + "isolated conformance is only permitted on global-actor-isolated types", + ()) +ERROR(isolated_conformance_experimental_feature,none, + "isolated conformances require experimental feature " + " 'IsolatedConformances'", ()) WARNING(remove_public_import,none, "public import of %0 was not used in public declarations or inlinable code", (Identifier)) diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 8d464ac19f587..3969f60624915 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -669,6 +669,11 @@ class NormalProtocolConformance : public RootProtocolConformance, return getOptions().contains(ProtocolConformanceFlags::Preconcurrency); } + /// Whether this is an isolated conformance. + bool isIsolated() const { + return getOptions().contains(ProtocolConformanceFlags::Isolated); + } + /// Retrieve the location of `@preconcurrency`, if there is one and it is /// known. SourceLoc getPreconcurrencyLoc() const { return PreconcurrencyLoc; } @@ -678,8 +683,6 @@ class NormalProtocolConformance : public RootProtocolConformance, ExplicitSafety getExplicitSafety() const { if (getOptions().contains(ProtocolConformanceFlags::Unsafe)) return ExplicitSafety::Unsafe; - if (getOptions().contains(ProtocolConformanceFlags::Safe)) - return ExplicitSafety::Safe; return ExplicitSafety::Unspecified; } diff --git a/include/swift/AST/ProtocolConformanceOptions.h b/include/swift/AST/ProtocolConformanceOptions.h index 0fc91203e88bc..002186bf307d2 100644 --- a/include/swift/AST/ProtocolConformanceOptions.h +++ b/include/swift/AST/ProtocolConformanceOptions.h @@ -34,8 +34,8 @@ enum class ProtocolConformanceFlags { /// @retroactive conformance Retroactive = 0x08, - /// @safe conformance - Safe = 0x10, + /// @isolated conformance + Isolated = 0x10, // Note: whenever you add a bit here, update // NumProtocolConformanceOptions below. diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 773e7e38c2fc9..02777db557886 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -450,11 +450,15 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(CustomAvailability, true) /// Be strict about the Sendable conformance of metatypes. EXPERIMENTAL_FEATURE(StrictSendableMetatypes, true) + /// Allow public enumerations to be extensible by default /// regardless of whether the module they are declared in /// is resilient or not. EXPERIMENTAL_FEATURE(ExtensibleEnums, true) +/// Allow isolated conformances. +EXPERIMENTAL_FEATURE(IsolatedConformances, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/ConformanceLookupTable.h b/lib/AST/ConformanceLookupTable.h index 92ed8be8c8e06..f43ab190aefe3 100644 --- a/lib/AST/ConformanceLookupTable.h +++ b/lib/AST/ConformanceLookupTable.h @@ -154,6 +154,8 @@ class ConformanceLookupTable : public ASTAllocated { options |= ProtocolConformanceFlags::Preconcurrency; if (getUnsafeLoc().isValid()) options |= ProtocolConformanceFlags::Unsafe; + if (getIsolatedLoc().isValid()) + options |= ProtocolConformanceFlags::Isolated; return options; } @@ -209,6 +211,11 @@ class ConformanceLookupTable : public ASTAllocated { return attributes.unsafeLoc; } + /// The location of the @isolated attribute, if any. + SourceLoc getIsolatedLoc() const { + return attributes.isolatedLoc; + } + /// For an inherited conformance, retrieve the class declaration /// for the inheriting class. ClassDecl *getInheritingClass() const { diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 40d78a7c9f07f..dd0dea8bd12cf 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1780,6 +1780,8 @@ InheritedEntry::InheritedEntry(const TypeLoc &typeLoc) setOption(ProtocolConformanceFlags::Unsafe); if (typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency).isValid()) setOption(ProtocolConformanceFlags::Preconcurrency); + if (typeRepr->findAttrLoc(TypeAttrKind::Isolated).isValid()) + setOption(ProtocolConformanceFlags::Isolated); } } diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 1eb747ac39d2a..f2ae7d8170821 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -348,6 +348,11 @@ static bool usesFeatureABIAttribute(Decl *decl) { return getABIAttr(decl) != nullptr; } +static bool usesFeatureIsolatedConformances(Decl *decl) { + // FIXME: Check conformances associated with this decl? + return false; +} + UNINTERESTING_FEATURE(WarnUnsafe) UNINTERESTING_FEATURE(SafeInteropWrappers) UNINTERESTING_FEATURE(AssumeResilientCxxTypes) diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index cfc1a96373770..8f692d4900071 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -3144,6 +3144,12 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx, attributed->getTypeRepr(), dc, options); } + case TypeReprKind::Isolated: { + auto isolated = cast(typeRepr); + return directReferencesForTypeRepr(evaluator, ctx, + isolated->getBase(), dc, options); + } + case TypeReprKind::Composition: { auto composition = cast(typeRepr); for (auto component : composition->getTypes()) { @@ -3217,7 +3223,6 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx, case TypeReprKind::Error: case TypeReprKind::Function: case TypeReprKind::Ownership: - case TypeReprKind::Isolated: case TypeReprKind::CompileTimeConst: case TypeReprKind::Metatype: case TypeReprKind::Protocol: @@ -3933,6 +3938,21 @@ CustomAttrNominalRequest::evaluate(Evaluator &evaluator, return nullptr; } +/// Find the location of 'isolated' within this type representation. +static SourceLoc findIsolatedLoc(TypeRepr *typeRepr) { + do { + if (auto isolatedTypeRepr = dyn_cast(typeRepr)) + return isolatedTypeRepr->getLoc(); + + if (auto attrTypeRepr = dyn_cast(typeRepr)) { + typeRepr = attrTypeRepr->getTypeRepr(); + continue; + } + + return SourceLoc(); + } while (true); +} + /// Decompose the ith inheritance clause entry to a list of type declarations, /// inverses, and optional AnyObject member. void swift::getDirectlyInheritedNominalTypeDecls( @@ -3971,6 +3991,9 @@ void swift::getDirectlyInheritedNominalTypeDecls( attributes.uncheckedLoc = typeRepr->findAttrLoc(TypeAttrKind::Unchecked); attributes.preconcurrencyLoc = typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency); attributes.unsafeLoc = typeRepr->findAttrLoc(TypeAttrKind::Unsafe); + + // Look for an IsolatedTypeRepr. + attributes.isolatedLoc = findIsolatedLoc(typeRepr); } // Form the result. diff --git a/lib/Parse/ParseType.cpp b/lib/Parse/ParseType.cpp index 31fd093e3b5a3..212e8cb432c36 100644 --- a/lib/Parse/ParseType.cpp +++ b/lib/Parse/ParseType.cpp @@ -163,7 +163,9 @@ ParserResult Parser::parseTypeSimple( Diag<> MessageID, ParseTypeReason reason) { ParserResult ty; - if (isParameterSpecifier()) { + if (isParameterSpecifier() && + !(!Context.LangOpts.hasFeature(Feature::IsolatedConformances) && + Tok.isContextualKeyword("isolated"))) { // Type specifier should already be parsed before here. This only happens // for construct like 'P1 & inout P2'. diagnose(Tok.getLoc(), diag::attr_only_on_parameters, Tok.getRawText()); diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index b7b58ee0d2953..cd43b6167395d 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -2541,6 +2541,22 @@ checkIndividualConformance(NormalProtocolConformance *conformance) { ComplainLoc, diag::unchecked_conformance_not_special, ProtoType); } + // Complain if the conformance is isolated but the conforming type is + // not global-actor-isolated. + if (conformance->isIsolated()) { + auto enclosingNominal = DC->getSelfNominalTypeDecl(); + if (!enclosingNominal || + !getActorIsolation(enclosingNominal).isGlobalActor()) { + Context.Diags.diagnose( + ComplainLoc, diag::isolated_conformance_not_global_actor_isolated); + } + + if (!Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { + Context.Diags.diagnose( + ComplainLoc, diag::isolated_conformance_experimental_feature); + } + } + bool allowImpliedConditionalConformance = false; if (Proto->isSpecificProtocol(KnownProtocolKind::Sendable)) { // In -swift-version 5 mode, a conditional conformance to a protocol can imply @@ -3313,6 +3329,14 @@ static bool hasExplicitGlobalActorAttr(ValueDecl *decl) { return !globalActorAttr->first->isImplicit(); } +/// Determine whether the given actor isolation matches that of the enclosing +/// type. +static bool isolationMatchesEnclosingType( + ActorIsolation isolation, NominalTypeDecl *nominal) { + auto nominalIsolation = getActorIsolation(nominal); + return isolation == nominalIsolation; +} + std::optional ConformanceChecker::checkActorIsolation(ValueDecl *requirement, ValueDecl *witness, @@ -3342,7 +3366,8 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, Conformance->isPreconcurrency() && !(requirementIsolation.isActorIsolated() || requirement->getAttrs().hasAttribute()); - + bool isIsolatedConformance = false; + switch (refResult) { case ActorReferenceResult::SameConcurrencyDomain: // If the witness has distributed-actor isolation, we have extra @@ -3374,6 +3399,17 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, return std::nullopt; case ActorReferenceResult::EntersActor: + // If the conformance itself is isolated, and the witness isolation + // matches the enclosing type's isolation, treat this as being in the + // same concurrency domain. + if (Conformance->isIsolated() && + refResult.isolation.isGlobalActor() && + isolationMatchesEnclosingType( + refResult.isolation, DC->getSelfNominalTypeDecl())) { + sameConcurrencyDomain = true; + isIsolatedConformance = true; + } + // Handled below. break; } @@ -3446,7 +3482,12 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, // If we aren't missing anything or this is a witness to a `@preconcurrency` // conformance, do a Sendable check and move on. - if (!missingOptions || isPreconcurrency) { + if (!missingOptions || isPreconcurrency || isIsolatedConformance) { + // An isolated conformance won't ever leave the isolation domain in which + // it was created, so there is nothing to check. + if (isIsolatedConformance) + return std::nullopt; + // FIXME: Disable Sendable checking when the witness is an initializer // that is explicitly marked nonisolated. if (isa(witness) && @@ -3546,18 +3587,26 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, witness->diagnose(diag::note_add_nonisolated_to_decl, witness) .fixItInsert(witness->getAttributeInsertionLoc(true), "nonisolated "); } - + // Another way to address the issue is to mark the conformance as - // "preconcurrency". + // "isolated" or "@preconcurrency". if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit && - !Conformance->isPreconcurrency() && - !suggestedPreconcurrency && + !Conformance->isIsolated() && !Conformance->isPreconcurrency() && + !suggestedPreconcurrencyOrIsolated && !requirementIsolation.isActorIsolated()) { + if (Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { + Context.Diags.diagnose(Conformance->getProtocolNameLoc(), + diag::add_isolated_to_conformance, + Proto->getName(), refResult.isolation) + .fixItInsert(Conformance->getProtocolNameLoc(), "isolated "); + } + Context.Diags.diagnose(Conformance->getProtocolNameLoc(), diag::add_preconcurrency_to_conformance, Proto->getName()) .fixItInsert(Conformance->getProtocolNameLoc(), "@preconcurrency "); - suggestedPreconcurrency = true; + + suggestedPreconcurrencyOrIsolated = true; } } diff --git a/lib/Sema/TypeCheckProtocol.h b/lib/Sema/TypeCheckProtocol.h index 07786ce68c42a..0e9360b2d3cdf 100644 --- a/lib/Sema/TypeCheckProtocol.h +++ b/lib/Sema/TypeCheckProtocol.h @@ -112,8 +112,8 @@ enum class ResolveWitnessResult { /// This helper class handles most of the details of checking whether a /// given type (\c Adoptee) conforms to a protocol (\c Proto). class ConformanceChecker : public WitnessChecker { - /// Whether we already suggested adding `@preconcurrency`. - bool suggestedPreconcurrency = false; + /// Whether we already suggested adding `@preconcurrency` or 'isolated'. + bool suggestedPreconcurrencyOrIsolated = false; public: NormalProtocolConformance *Conformance; diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index aa39887ce2279..e7041fd822316 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -5175,8 +5175,9 @@ NeverNullType TypeResolver::resolveIsolatedTypeRepr(IsolatedTypeRepr *repr, TypeResolutionOptions options) { // isolated is only value for non-EnumCaseDecl parameters. - if (!options.is(TypeResolverContext::FunctionInput) || - options.hasBase(TypeResolverContext::EnumElementDecl)) { + if ((!options.is(TypeResolverContext::FunctionInput) || + options.hasBase(TypeResolverContext::EnumElementDecl)) && + !options.is(TypeResolverContext::Inherited)) { diagnoseInvalid( repr, repr->getSpecifierLoc(), diag::attr_only_on_parameters, "isolated"); @@ -5197,7 +5198,8 @@ TypeResolver::resolveIsolatedTypeRepr(IsolatedTypeRepr *repr, unwrappedType = dynamicSelfType->getSelfType(); } - if (inStage(TypeResolutionStage::Interface)) { + if (inStage(TypeResolutionStage::Interface) && + !options.is(TypeResolverContext::Inherited)) { if (auto *env = resolution.getGenericSignature().getGenericEnvironment()) unwrappedType = env->mapTypeIntoContext(unwrappedType); diff --git a/test/Concurrency/isolated_conformance.swift b/test/Concurrency/isolated_conformance.swift new file mode 100644 index 0000000000000..1eb43d2771866 --- /dev/null +++ b/test/Concurrency/isolated_conformance.swift @@ -0,0 +1,42 @@ +// RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-experimental-feature IsolatedConformances %s + +// REQUIRES: swift_feature_IsolatedConformances + +protocol P { + func f() // expected-note 2{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}} +} + +// expected-note@+3{{add '@preconcurrency' to the 'P' conformance to defer isolation checking to run time}}{{25-25=@preconcurrency }} +// expected-note@+2{{add 'isolated' to the 'P' conformance to restrict it to main actor-isolated code}}{{25-25=isolated }} +@MainActor +class CWithNonIsolated: P { + func f() { } // expected-error{{main actor-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} + // expected-note@-1{{add 'nonisolated' to 'f()' to make this instance method not isolated to the actor}} +} + +actor SomeActor { } + +// Isolated conformances need a global-actor-constrained type. +class CNonIsolated: isolated P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} + func f() { } +} + +extension SomeActor: isolated P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} + nonisolated func f() { } +} + +@globalActor +struct SomeGlobalActor { + static let shared = SomeActor() +} + +// Isolation of the function needs to match that of the enclosing type. +@MainActor +class CMismatchedIsolation: isolated P { + @SomeGlobalActor func f() { } // expected-error{{global actor 'SomeGlobalActor'-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} +} + +@MainActor +class C: isolated P { + func f() { } // okay +} From 71e23ac757c2cb923034788a4fa6b9a3b01c90a5 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 25 Feb 2025 23:03:50 -0800 Subject: [PATCH 2/5] Check consistency of isolation of conformances within other conformances When a protocol conformance somehow depends on an isolated conformance, it must itself be isolated to the same global actor as the conformance on which it depends. --- include/swift/AST/DiagnosticsSema.def | 6 +++ lib/Sema/TypeCheckConcurrency.cpp | 54 +++++++++++++++++++++ lib/Sema/TypeCheckConcurrency.h | 13 +++++ lib/Sema/TypeCheckProtocol.cpp | 46 ++++++++++++++++++ test/Concurrency/isolated_conformance.swift | 38 +++++++++++++++ 5 files changed, 157 insertions(+) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 6dda2e4cc5926..a560dad3c6b92 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2733,6 +2733,12 @@ ERROR(isolated_conformance_not_global_actor_isolated,none, ERROR(isolated_conformance_experimental_feature,none, "isolated conformances require experimental feature " " 'IsolatedConformances'", ()) +ERROR(nonisolated_conformance_depends_on_isolated_conformance,none, + "conformance of %0 to %1 depends on %2 conformance of %3 to %4; mark it as 'isolated'", + (Type, DeclName, ActorIsolation, Type, DeclName)) +ERROR(isolated_conformance_mismatch_with_associated_isolation,none, + "%0 conformance of %1 to %2 cannot depend on %3 conformance of %4 to %5", + (ActorIsolation, Type, DeclName, ActorIsolation, Type, DeclName)) WARNING(remove_public_import,none, "public import of %0 was not used in public declarations or inlinable code", (Identifier)) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index b989ce887f1ce..88a16a3a42149 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -30,6 +30,7 @@ #include "swift/AST/ImportCache.h" #include "swift/AST/Initializer.h" #include "swift/AST/NameLookupRequests.h" +#include "swift/AST/PackConformance.h" #include "swift/AST/ParameterList.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/TypeCheckRequests.h" @@ -7669,3 +7670,56 @@ bool swift::diagnoseNonSendableFromDeinit( diag::non_sendable_from_deinit, var->getDescriptiveKind(), var->getName()); } + +bool swift::forEachIsolatedConformance( + ProtocolConformanceRef conformance, + llvm::function_ref body +) { + if (conformance.isInvalid() || conformance.isAbstract()) + return false; + + if (conformance.isPack()) { + auto pack = conformance.getPack()->getPatternConformances(); + for (auto conformance : pack) { + if (forEachIsolatedConformance(conformance, body)) + return true; + } + + return false; + } + + // Is this an isolated conformance? + auto concrete = conformance.getConcrete(); + if (auto normal = + dyn_cast(concrete->getRootConformance())) { + if (normal->isIsolated()) { + if (body(concrete)) + return true; + } + } + + // Check conformances that are part of this conformance. + auto subMap = concrete->getSubstitutionMap(); + for (auto conformance : subMap.getConformances()) { + if (forEachIsolatedConformance(conformance, body)) + return true; + } + + return false; +} + +ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance) { + auto rootNormal = + dyn_cast(conformance->getRootConformance()); + if (!rootNormal) + return ActorIsolation::forNonisolated(false); + + if (!rootNormal->isIsolated()) + return ActorIsolation::forNonisolated(false); + + auto nominal = rootNormal->getDeclContext()->getSelfNominalTypeDecl(); + if (!nominal) + return ActorIsolation::forNonisolated(false); + + return getActorIsolation(nominal); +} diff --git a/lib/Sema/TypeCheckConcurrency.h b/lib/Sema/TypeCheckConcurrency.h index b61c085b16172..d63eb140b4a28 100644 --- a/lib/Sema/TypeCheckConcurrency.h +++ b/lib/Sema/TypeCheckConcurrency.h @@ -699,6 +699,19 @@ void introduceUnsafeInheritExecutorReplacements( void introduceUnsafeInheritExecutorReplacements( const DeclContext *dc, Type base, SourceLoc loc, LookupResult &result); +/// Enumerate all of the isolated conformances in the given conformance. +/// +/// The given `body` will be called on each isolated conformance. If it ever +/// returns `true`, this function will abort the search and return `true`. +bool forEachIsolatedConformance( + ProtocolConformanceRef conformance, + llvm::function_ref body +); + +/// Determine the isolation of the given conformance. This only applies to +/// the immediate conformance, not any conformances on which it depends. +ActorIsolation getConformanceIsolation(ProtocolConformance *conformance); + } // end namespace swift namespace llvm { diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index cd43b6167395d..3d20638599452 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -5207,6 +5207,8 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, if (where.isImplicit()) return; + bool diagnosedIsolatedConformanceIssue = false; + conformance->forEachAssociatedConformance( [&](Type depTy, ProtocolDecl *proto, unsigned index) { auto assocConf = conformance->getAssociatedConformance(depTy, proto); @@ -5230,6 +5232,50 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, where.withRefinedAvailability(availability), depTy, replacementTy); } + if (!diagnosedIsolatedConformanceIssue) { + bool foundIssue = forEachIsolatedConformance( + ProtocolConformanceRef(assocConf), + [&](ProtocolConformance *isolatedConformance) { + // If the conformance we're checking isn't isolated at all, it + // needs "isolated". + if (!conformance->isIsolated()) { + ctx.Diags.diagnose( + conformance->getLoc(), + diag::nonisolated_conformance_depends_on_isolated_conformance, + typeInContext, conformance->getProtocol()->getName(), + getConformanceIsolation(isolatedConformance), + isolatedConformance->getType(), + isolatedConformance->getProtocol()->getName() + ).fixItInsert(conformance->getProtocolNameLoc(), "isolated "); + + return true; + } + + // The conformance is isolated, but we need it to have the same + // isolation as the other isolated conformance we found. + auto outerIsolation = getConformanceIsolation(conformance); + auto innerIsolation = getConformanceIsolation(isolatedConformance); + if (outerIsolation != innerIsolation) { + ctx.Diags.diagnose( + conformance->getLoc(), + diag::isolated_conformance_mismatch_with_associated_isolation, + outerIsolation, + typeInContext, conformance->getProtocol()->getName(), + innerIsolation, + isolatedConformance->getType(), + isolatedConformance->getProtocol()->getName() + ); + + return true; + } + + return false; + } + ); + + diagnosedIsolatedConformanceIssue = foundIssue; + } + return false; }); } diff --git a/test/Concurrency/isolated_conformance.swift b/test/Concurrency/isolated_conformance.swift index 1eb43d2771866..0cea3701f3035 100644 --- a/test/Concurrency/isolated_conformance.swift +++ b/test/Concurrency/isolated_conformance.swift @@ -40,3 +40,41 @@ class CMismatchedIsolation: isolated P { class C: isolated P { func f() { } // okay } + +// Associated conformances with isolation + +protocol Q { + associatedtype A: P +} + +// expected-error@+2{{conformance of 'SMissingIsolation' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as 'isolated'}}{{27-27=isolated }} +@MainActor +struct SMissingIsolation: Q { + typealias A = C +} + +struct PWrapper: P { + func f() { } +} + +// expected-error@+2{{conformance of 'SMissingIsolationViaWrapper' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as 'isolated'}} +@MainActor +struct SMissingIsolationViaWrapper: Q { + typealias A = PWrapper +} + +@SomeGlobalActor +class C2: isolated P { + func f() { } +} + +@MainActor +struct S: isolated Q { + typealias A = C +} + +// expected-error@+2{{main actor-isolated conformance of 'SMismatchedActors' to 'Q' cannot depend on global actor 'SomeGlobalActor'-isolated conformance of 'C2' to 'P'}} +@MainActor +struct SMismatchedActors: isolated Q { + typealias A = C2 +} From b7b5a2a19dd4e5ca9a4c16c5684f2eb4b7f76725 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 26 Feb 2025 12:30:07 -0800 Subject: [PATCH 3/5] [SE-0458] Enable unsafe expressions / attributes / for..in effects by default With the acceptance of SE-0458, allow the use of unsafe expressions, the @safe and @unsafe attributes, and the `unsafe` effect on the for..in loop in all Swift code. Introduce the `-strict-memory-safety` flag detailed in the proposal to enable strict memory safety checking. This enables a new class of feature, an optional feature (that is *not* upcoming or experimental), and which can be detected via `hasFeature(StrictMemorySafety)`. --- include/swift/AST/DiagnosticsSema.def | 3 --- include/swift/Basic/Features.def | 27 ++++++++++++++----- include/swift/Option/Options.td | 5 ++++ lib/AST/ASTPrinter.cpp | 3 ++- lib/AST/FeatureSet.cpp | 11 ++++---- lib/ASTGen/Sources/ASTGen/SourceFile.swift | 1 - lib/Basic/LangOptions.cpp | 7 +++++ lib/ClangImporter/ImportDecl.cpp | 10 +++---- lib/Driver/ToolChains.cpp | 1 + lib/Frontend/CompilerInvocation.cpp | 5 +++- lib/Frontend/Frontend.cpp | 2 +- lib/Parse/ParseExpr.cpp | 3 +-- lib/Parse/ParseStmt.cpp | 3 +-- lib/Sema/TypeCheckAttr.cpp | 18 ++----------- lib/Sema/TypeCheckDeclOverride.cpp | 2 +- lib/Sema/TypeCheckDeclPrimary.cpp | 4 +-- lib/Sema/TypeCheckEffects.cpp | 2 +- lib/Sema/TypeCheckProtocol.cpp | 2 +- lib/Sema/TypeCheckUnsafe.cpp | 4 +-- .../Interop/Cxx/class/safe-interop-mode.swift | 4 +-- .../mutable_span_bounds_check_tests.swift | 3 +-- test/Unsafe/interface_printing.swift | 6 ++--- test/Unsafe/module-interface.swift | 9 +++---- test/Unsafe/module-trace.swift | 9 +++---- test/Unsafe/safe.swift | 9 ++++--- test/Unsafe/safe_argument_suppression.swift | 5 +--- test/Unsafe/unsafe-suppression.swift | 5 +--- test/Unsafe/unsafe.swift | 9 +++---- test/Unsafe/unsafe_c_imports.swift | 5 +--- test/Unsafe/unsafe_command_line.swift | 4 +-- test/Unsafe/unsafe_concurrency.swift | 6 ++--- test/Unsafe/unsafe_disallowed.swift | 5 ---- test/Unsafe/unsafe_feature.swift | 19 +++++++++++++ test/Unsafe/unsafe_imports.swift | 7 ++--- test/Unsafe/unsafe_in_unsafe.swift | 5 +--- test/Unsafe/unsafe_stdlib.swift | 7 ++--- test/lit.swift-features.cfg.inc | 1 + 37 files changed, 112 insertions(+), 119 deletions(-) delete mode 100644 test/Unsafe/unsafe_disallowed.swift create mode 100644 test/Unsafe/unsafe_feature.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index a560dad3c6b92..7f3daf123dbe9 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8165,9 +8165,6 @@ NOTE(sending_function_result_with_sending_param_note, none, //------------------------------------------------------------------------------ // MARK: Strict Safety Diagnostics //------------------------------------------------------------------------------ -ERROR(unsafe_attr_disabled,none, - "attribute requires '-enable-experimental-feature AllowUnsafeAttribute'", ()) - NOTE(note_reference_to_unsafe_decl,none, "%select{reference|call}0 to unsafe %kind1", (bool, const ValueDecl *)) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 02777db557886..f7dcd6e9ce287 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -48,6 +48,12 @@ // for features that can be assumed to be available in any Swift compiler that // will be used to process the textual interface files produced by this // Swift compiler. +// +// OPTIONAL_LANGUAGE_FEATURE is the same as LANGUAGE_FEATURE, but describes +// accepted features that can be enabled independently of language version and +// are not scheduled to be enabled in some specific language version. Examples +// of optional language features include strict memory safety checking (SE-0458) +// and Embedded Swift. //===----------------------------------------------------------------------===// #ifndef LANGUAGE_FEATURE @@ -89,6 +95,11 @@ LANGUAGE_FEATURE(FeatureName, SENumber, Description) #endif +#ifndef OPTIONAL_LANGUAGE_FEATURE +# define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ + LANGUAGE_FEATURE(FeatureName, SENumber, Description) +#endif + // A feature that's both conditionally-suppressible and experimental. // Delegates to whichever the includer defines. #ifndef CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE @@ -203,6 +214,7 @@ LANGUAGE_FEATURE(IsolatedAny2, 431, "@isolated(any) function types") LANGUAGE_FEATURE(ObjCImplementation, 436, "@objc @implementation extensions") LANGUAGE_FEATURE(NonescapableTypes, 446, "Nonescapable types") LANGUAGE_FEATURE(BuiltinEmplaceTypedThrows, 0, "Builtin.emplace typed throws") +SUPPRESSIBLE_LANGUAGE_FEATURE(MemorySafetyAttributes, 458, "@unsafe attribute") // Swift 6 UPCOMING_FEATURE(ConciseMagicFile, 274, 6) @@ -226,6 +238,14 @@ UPCOMING_FEATURE(ExistentialAny, 335, 7) UPCOMING_FEATURE(InternalImportsByDefault, 409, 7) UPCOMING_FEATURE(MemberImportVisibility, 444, 7) +// Optional language features / modes + +/// Diagnose uses of language constructs and APIs that can violate memory +/// safety. +OPTIONAL_LANGUAGE_FEATURE(StrictMemorySafety, 458, "Strict memory safety") + +// Experimental features + EXPERIMENTAL_FEATURE(StaticAssert, false) EXPERIMENTAL_FEATURE(NamedOpaqueTypes, false) EXPERIMENTAL_FEATURE(FlowSensitiveConcurrencyCaptures, false) @@ -396,12 +416,6 @@ EXPERIMENTAL_FEATURE(Extern, true) // Enable trailing comma for comma-separated lists. EXPERIMENTAL_FEATURE(TrailingComma, false) -/// Allow the @unsafe attribute. -SUPPRESSIBLE_EXPERIMENTAL_FEATURE(AllowUnsafeAttribute, true) - -/// Warn on use of unsafe constructs. -EXPERIMENTAL_FEATURE(WarnUnsafe, true) - // Import bounds safety and lifetime attributes from interop headers to // generate Swift wrappers with safe pointer types. EXPERIMENTAL_FEATURE(SafeInteropWrappers, false) @@ -463,6 +477,7 @@ EXPERIMENTAL_FEATURE(IsolatedConformances, true) #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE #undef BASELINE_LANGUAGE_FEATURE +#undef OPTIONAL_LANGUAGE_FEATURE #undef CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE #undef CONDITIONALLY_SUPPRESSIBLE_LANGUAGE_FEATURE #undef SUPPRESSIBLE_EXPERIMENTAL_FEATURE diff --git a/include/swift/Option/Options.td b/include/swift/Option/Options.td index a4e622a58acc2..74fd5731e7269 100644 --- a/include/swift/Option/Options.td +++ b/include/swift/Option/Options.td @@ -1005,6 +1005,11 @@ def disable_upcoming_feature : Separate<["-"], "disable-upcoming-feature">, HelpText<"Disable a feature that will be introduced in an upcoming language " "version">; +def strict_memory_safety : Flag<["-"], "strict-memory-safety">, + Flags<[FrontendOption, ModuleInterfaceOptionIgnorable, + SwiftAPIDigesterOption, SwiftSynthesizeInterfaceOption]>, + HelpText<"Enable strict memory safety checking">; + def Rpass_EQ : Joined<["-"], "Rpass=">, Flags<[FrontendOption]>, HelpText<"Report performed transformations by optimization passes whose " diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 8199a648638b3..87b7927231b6a 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -3214,9 +3214,10 @@ struct ExcludeAttrRAII { } static void -suppressingFeatureAllowUnsafeAttribute(PrintOptions &options, +suppressingFeatureMemorySafetyAttributes(PrintOptions &options, llvm::function_ref action) { ExcludeAttrRAII scope(options.ExcludeAttrList, DeclAttrKind::Unsafe); + ExcludeAttrRAII scope2(options.ExcludeAttrList, DeclAttrKind::Safe); action(); } diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index f2ae7d8170821..d71487ddcbb92 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -330,10 +330,6 @@ UNINTERESTING_FEATURE(ReinitializeConsumeInMultiBlockDefer) UNINTERESTING_FEATURE(SE427NoInferenceOnExtension) UNINTERESTING_FEATURE(TrailingComma) -static bool usesFeatureAllowUnsafeAttribute(Decl *decl) { - return decl->getAttrs().hasAttribute(); -} - static ABIAttr *getABIAttr(Decl *decl) { if (auto pbd = dyn_cast(decl)) for (auto i : range(pbd->getNumPatternEntries())) @@ -353,7 +349,12 @@ static bool usesFeatureIsolatedConformances(Decl *decl) { return false; } -UNINTERESTING_FEATURE(WarnUnsafe) +static bool usesFeatureMemorySafetyAttributes(Decl *decl) { + return decl->getAttrs().hasAttribute() || + decl->getAttrs().hasAttribute(); +} + +UNINTERESTING_FEATURE(StrictMemorySafety) UNINTERESTING_FEATURE(SafeInteropWrappers) UNINTERESTING_FEATURE(AssumeResilientCxxTypes) UNINTERESTING_FEATURE(CoroutineAccessorsUnwindOnCallerError) diff --git a/lib/ASTGen/Sources/ASTGen/SourceFile.swift b/lib/ASTGen/Sources/ASTGen/SourceFile.swift index e9f9a92e45ce2..e74fc16ef7b2e 100644 --- a/lib/ASTGen/Sources/ASTGen/SourceFile.swift +++ b/lib/ASTGen/Sources/ASTGen/SourceFile.swift @@ -77,7 +77,6 @@ extension Parser.ExperimentalFeatures { mapFeature(.CoroutineAccessors, to: .coroutineAccessors) mapFeature(.ValueGenerics, to: .valueGenerics) mapFeature(.ABIAttribute, to: .abiAttribute) - mapFeature(.WarnUnsafe, to: .unsafeExpression) } } diff --git a/lib/Basic/LangOptions.cpp b/lib/Basic/LangOptions.cpp index af79d027df997..2fbc993e0d855 100644 --- a/lib/Basic/LangOptions.cpp +++ b/lib/Basic/LangOptions.cpp @@ -38,6 +38,7 @@ LangOptions::LangOptions() { Features.insert(Feature::FeatureName); #define UPCOMING_FEATURE(FeatureName, SENumber, Version) #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) #include "swift/Basic/Features.def" // Special case: remove macro support if the compiler wasn't built with a @@ -636,6 +637,8 @@ bool swift::isFeatureAvailableInProduction(Feature feature) { return true; #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \ case Feature::FeatureName: return AvailableInProd; +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ + LANGUAGE_FEATURE(FeatureName, SENumber, Description) #include "swift/Basic/Features.def" } llvm_unreachable("covered switch"); @@ -655,6 +658,7 @@ std::optional swift::getExperimentalFeature(llvm::StringRef name) { #define LANGUAGE_FEATURE(FeatureName, SENumber, Description) #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \ .Case(#FeatureName, Feature::FeatureName) +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) #include "swift/Basic/Features.def" .Default(std::nullopt); } @@ -664,6 +668,7 @@ std::optional swift::getFeatureLanguageVersion(Feature feature) { #define LANGUAGE_FEATURE(FeatureName, SENumber, Description) #define UPCOMING_FEATURE(FeatureName, SENumber, Version) \ case Feature::FeatureName: return Version; +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) #include "swift/Basic/Features.def" default: return std::nullopt; @@ -677,6 +682,8 @@ bool swift::includeInModuleInterface(Feature feature) { return true; #define EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE(FeatureName, AvailableInProd) \ case Feature::FeatureName: return false; +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ + LANGUAGE_FEATURE(FeatureName, SENumber, Description) #include "swift/Basic/Features.def" } llvm_unreachable("covered switch"); diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 87b1533c8506e..cce357e46d02c 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2039,7 +2039,7 @@ namespace { fd->getAttrs().add(new (Impl.SwiftContext) UnsafeNonEscapableResultAttr(/*Implicit=*/true)); if (Impl.SwiftContext.LangOpts.hasFeature( - Feature::AllowUnsafeAttribute)) + Feature::StrictMemorySafety)) fd->getAttrs().add(new (Impl.SwiftContext) UnsafeAttr(/*Implicit=*/true)); } @@ -2201,7 +2201,7 @@ namespace { // We have to do this after populating ImportedDecls to avoid importing // the same multiple times. if (Impl.SwiftContext.LangOpts.hasFeature( - Feature::AllowUnsafeAttribute)) { + Feature::StrictMemorySafety)) { if (const auto *ctsd = dyn_cast(decl)) { for (auto arg : ctsd->getTemplateArgs().asArray()) { @@ -4178,13 +4178,13 @@ namespace { LifetimeDependenceInfoRequest{result}, Impl.SwiftContext.AllocateCopy(lifetimeDependencies)); } - if (ASTContext.LangOpts.hasFeature(Feature::AllowUnsafeAttribute)) { + if (ASTContext.LangOpts.hasFeature(Feature::StrictMemorySafety)) { for (auto [idx, param] : llvm::enumerate(decl->parameters())) { if (swiftParams->get(idx)->getInterfaceType()->isEscapable()) continue; if (param->hasAttr() || paramHasAnnotation[idx]) continue; - // We have a nonescapabe parameter that does not have its lifetime + // We have a nonescapable parameter that does not have its lifetime // annotated nor is it marked noescape. auto attr = new (ASTContext) UnsafeAttr(/*implicit=*/true); result->getAttrs().add(attr); @@ -8722,8 +8722,6 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) { } if (swiftAttr->getAttribute() == "unsafe") { - if (!SwiftContext.LangOpts.hasFeature(Feature::AllowUnsafeAttribute)) - continue; seenUnsafe = true; continue; } diff --git a/lib/Driver/ToolChains.cpp b/lib/Driver/ToolChains.cpp index 2cde7c782a78e..6a7ae73498e00 100644 --- a/lib/Driver/ToolChains.cpp +++ b/lib/Driver/ToolChains.cpp @@ -282,6 +282,7 @@ 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_warn_implicit_overrides); inputArgs.AddLastArg(arguments, options::OPT_typo_correction_limit); inputArgs.AddLastArg(arguments, options::OPT_enable_app_extension); diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 7a5850f015c0b..332ec422b5a22 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -878,6 +878,9 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args, Opts.enableFeature(Feature::LayoutPrespecialization); + if (Args.hasArg(OPT_strict_memory_safety)) + Opts.enableFeature(Feature::StrictMemorySafety); + return HadError; } @@ -3909,7 +3912,7 @@ bool CompilerInvocation::parseArgs( } } - if (LangOpts.hasFeature(Feature::WarnUnsafe)) { + if (LangOpts.hasFeature(Feature::StrictMemorySafety)) { if (SILOpts.RemoveRuntimeAsserts || SILOpts.AssertConfig == SILOptions::Unchecked) { Diags.diagnose(SourceLoc(), diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index 7811395f106d4..1e64de754512d 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -1455,7 +1455,7 @@ ModuleDecl *CompilerInstance::getMainModule() const { MainModule->setAllowNonResilientAccess(); if (Invocation.getSILOptions().EnableSerializePackage) MainModule->setSerializePackageEnabled(); - if (Invocation.getLangOptions().hasFeature(Feature::WarnUnsafe)) + if (Invocation.getLangOptions().hasFeature(Feature::StrictMemorySafety)) MainModule->setStrictMemorySafety(true); if (Invocation.getLangOptions().hasFeature(Feature::ExtensibleEnums)) MainModule->setSupportsExtensibleEnums(true); diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 9fb1e94412c0d..acd4883a8bcdb 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -436,8 +436,7 @@ ParserResult Parser::parseExprSequenceElement(Diag<> message, return sub; } - if (Context.LangOpts.hasFeature(Feature::WarnUnsafe) && - Tok.isContextualKeyword("unsafe")) { + if (Tok.isContextualKeyword("unsafe")) { Tok.setKind(tok::contextual_keyword); SourceLoc unsafeLoc = consumeToken(); ParserResult sub = diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 4d564324b3c5d..1c83c8786252f 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -2379,8 +2379,7 @@ ParserResult Parser::parseStmtForEach(LabeledStmtInfo LabelInfo) { } } - if (Context.LangOpts.hasFeature(Feature::WarnUnsafe) && - Tok.isContextualKeyword("unsafe")) { + if (Tok.isContextualKeyword("unsafe")) { UnsafeLoc = consumeToken(); } diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 4218b6880a310..b197f6af0ba80 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -189,6 +189,8 @@ class AttributeChecker : public AttributeVisitor { IGNORED_ATTR(LexicalLifetimes) IGNORED_ATTR(AllowFeatureSuppression) IGNORED_ATTR(PreInverseGenerics) + IGNORED_ATTR(Safe) + IGNORED_ATTR(Unsafe) #undef IGNORED_ATTR private: @@ -564,8 +566,6 @@ class AttributeChecker : public AttributeVisitor { void visitStaticExclusiveOnlyAttr(StaticExclusiveOnlyAttr *attr); void visitWeakLinkedAttr(WeakLinkedAttr *attr); void visitSILGenNameAttr(SILGenNameAttr *attr); - void visitUnsafeAttr(UnsafeAttr *attr); - void visitSafeAttr(SafeAttr *attr); void visitLifetimeAttr(LifetimeAttr *attr); void visitAddressableSelfAttr(AddressableSelfAttr *attr); void visitAddressableForDependenciesAttr(AddressableForDependenciesAttr *attr); @@ -8125,20 +8125,6 @@ void AttributeChecker::visitWeakLinkedAttr(WeakLinkedAttr *attr) { attr->getAttrName(), Ctx.LangOpts.Target.str()); } -void AttributeChecker::visitUnsafeAttr(UnsafeAttr *attr) { - if (Ctx.LangOpts.hasFeature(Feature::AllowUnsafeAttribute)) - return; - - diagnoseAndRemoveAttr(attr, diag::unsafe_attr_disabled); -} - -void AttributeChecker::visitSafeAttr(SafeAttr *attr) { - if (Ctx.LangOpts.hasFeature(Feature::AllowUnsafeAttribute)) - return; - - diagnoseAndRemoveAttr(attr, diag::unsafe_attr_disabled); -} - void AttributeChecker::visitLifetimeAttr(LifetimeAttr *attr) {} void AttributeChecker::visitAddressableSelfAttr(AddressableSelfAttr *attr) { diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 7b89e8cb4f8ba..36b402d1becbe 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -2259,7 +2259,7 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) { diagnoseOverrideForAvailability(override, base); } - if (ctx.LangOpts.hasFeature(Feature::WarnUnsafe)) { + if (ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) { // If the override is unsafe but the base declaration is not, then the // inheritance itself is unsafe. auto subs = SubstitutionMap::getOverrideSubstitutions(base, override); diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index a3fc274b3311e..55134261c09d2 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -2395,7 +2395,7 @@ class DeclChecker : public DeclVisitor { // If strict memory safety checking is enabled, check the storage // of the nominal type. - if (Ctx.LangOpts.hasFeature(Feature::WarnUnsafe) && + if (Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety) && !isa(nominal)) { checkUnsafeStorage(nominal); } @@ -2468,7 +2468,7 @@ class DeclChecker : public DeclVisitor { // concurrency checking enabled. if (ID->preconcurrency() && Ctx.LangOpts.StrictConcurrencyLevel == StrictConcurrency::Complete && - Ctx.LangOpts.hasFeature(Feature::WarnUnsafe)) { + Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) { diagnoseUnsafeUse(UnsafeUse::forPreconcurrencyImport(ID)); } } diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index e638d5e5b343c..23fe8a7f42988 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -1101,7 +1101,7 @@ class Classification { bool considerAsync = !onlyEffect || *onlyEffect == EffectKind::Async; bool considerThrows = !onlyEffect || *onlyEffect == EffectKind::Throws; bool considerUnsafe = (!onlyEffect || *onlyEffect == EffectKind::Unsafe) && - ctx.LangOpts.hasFeature(Feature::WarnUnsafe); + ctx.LangOpts.hasFeature(Feature::StrictMemorySafety); // If we're tracking "unsafe" effects, compute them here. if (considerUnsafe) { diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 3d20638599452..435f8c8f1f7c0 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -2641,7 +2641,7 @@ 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::WarnUnsafe)) { + Context.LangOpts.hasFeature(Feature::StrictMemorySafety)) { // Collect all of the unsafe uses for this conformance. SmallVector unsafeUses; for (auto requirement: Proto->getMembers()) { diff --git a/lib/Sema/TypeCheckUnsafe.cpp b/lib/Sema/TypeCheckUnsafe.cpp index 491458513e3e2..4389638a44c6d 100644 --- a/lib/Sema/TypeCheckUnsafe.cpp +++ b/lib/Sema/TypeCheckUnsafe.cpp @@ -300,7 +300,7 @@ bool swift::enumerateUnsafeUses(ArrayRef conformances, continue; ASTContext &ctx = conformance.getRequirement()->getASTContext(); - if (!ctx.LangOpts.hasFeature(Feature::WarnUnsafe)) + if (!ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) return false; if (!conformance.hasEffect(EffectKind::Unsafe)) @@ -365,7 +365,7 @@ bool swift::isUnsafeInConformance(const ValueDecl *requirement, void swift::diagnoseUnsafeType(ASTContext &ctx, SourceLoc loc, Type type, llvm::function_ref diagnose) { - if (!ctx.LangOpts.hasFeature(Feature::WarnUnsafe)) + if (!ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) return; if (!type->isUnsafe() && !type->getCanonicalType()->isUnsafe()) diff --git a/test/Interop/Cxx/class/safe-interop-mode.swift b/test/Interop/Cxx/class/safe-interop-mode.swift index 4b3a47af0b762..b8ffb5ba68053 100644 --- a/test/Interop/Cxx/class/safe-interop-mode.swift +++ b/test/Interop/Cxx/class/safe-interop-mode.swift @@ -1,11 +1,9 @@ // RUN: rm -rf %t // RUN: split-file %s %t -// RUN: %target-swift-frontend -typecheck -verify -I %swift_src_root/lib/ClangImporter/SwiftBridging -Xcc -std=c++20 -I %t/Inputs %t/test.swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -enable-experimental-feature LifetimeDependence -cxx-interoperability-mode=default -diagnostic-style llvm 2>&1 +// RUN: %target-swift-frontend -typecheck -verify -I %swift_src_root/lib/ClangImporter/SwiftBridging -Xcc -std=c++20 -I %t/Inputs %t/test.swift -strict-memory-safety -enable-experimental-feature LifetimeDependence -cxx-interoperability-mode=default -diagnostic-style llvm 2>&1 // REQUIRES: objc_interop -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe // REQUIRES: swift_feature_LifetimeDependence //--- Inputs/module.modulemap diff --git a/test/SILOptimizer/mutable_span_bounds_check_tests.swift b/test/SILOptimizer/mutable_span_bounds_check_tests.swift index 1f1d85f1fcd56..41dc480bc1b58 100644 --- a/test/SILOptimizer/mutable_span_bounds_check_tests.swift +++ b/test/SILOptimizer/mutable_span_bounds_check_tests.swift @@ -1,11 +1,10 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -emit-module-path %t/SpanExtras.swiftmodule %S/Inputs/SpanExtras.swift -enable-builtin-module -enable-experimental-feature LifetimeDependence -enable-experimental-feature AllowUnsafeAttribute -O +// RUN: %target-swift-frontend -emit-module-path %t/SpanExtras.swiftmodule %S/Inputs/SpanExtras.swift -enable-builtin-module -enable-experimental-feature LifetimeDependence -O // RUN: %target-swift-frontend -I %t -O -emit-sil %s -disable-availability-checking | %FileCheck %s --check-prefix=CHECK-SIL // RUN: %target-swift-frontend -I %t -O -emit-ir %s -disable-availability-checking | %FileCheck %s --check-prefix=CHECK-IR // REQUIRES: swift_in_compiler // REQUIRES: swift_feature_LifetimeDependence -// REQUIRES: swift_feature_AllowUnsafeAttribute // REQUIRES: swift_stdlib_no_asserts, optimized_stdlib diff --git a/test/Unsafe/interface_printing.swift b/test/Unsafe/interface_printing.swift index bdba93169260f..0fd0e8c4de578 100644 --- a/test/Unsafe/interface_printing.swift +++ b/test/Unsafe/interface_printing.swift @@ -1,10 +1,8 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -swift-version 5 -enable-library-evolution -module-name unsafe -emit-module -o %t/unsafe.swiftmodule -emit-module-interface-path - %s -enable-experimental-feature AllowUnsafeAttribute | %FileCheck %s +// RUN: %target-swift-frontend -swift-version 5 -enable-library-evolution -module-name unsafe -emit-module -o %t/unsafe.swiftmodule -emit-module-interface-path - %s | %FileCheck %s -// REQUIRES: swift_feature_AllowUnsafeAttribute - -// CHECK: #if compiler(>=5.3) && $AllowUnsafeAttribute +// CHECK: #if compiler(>=5.3) && $MemorySafetyAttributes // CHECK: @unsafe public func testFunction() // CHECK: #else // CHECK: public func testFunction() diff --git a/test/Unsafe/module-interface.swift b/test/Unsafe/module-interface.swift index 138526a71d517..fe7a78cf7699b 100644 --- a/test/Unsafe/module-interface.swift +++ b/test/Unsafe/module-interface.swift @@ -1,11 +1,8 @@ -// RUN: %target-swift-emit-module-interface(%t.swiftinterface) %s -module-name UserModule -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe +// RUN: %target-swift-emit-module-interface(%t.swiftinterface) %s -module-name UserModule -strict-memory-safety // RUN: %target-swift-typecheck-module-from-interface(%t.swiftinterface) -module-name UserModule // RUN: %FileCheck %s < %t.swiftinterface -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe - -// CHECK: #if compiler(>=5.3) && $AllowUnsafeAttribute +// CHECK: #if compiler(>=5.3) && $MemorySafetyAttributes // CHECK: @unsafe public func getIntUnsafely() -> Swift.Int // CHECK: #else // CHECK: public func getIntUnsafely() -> Swift.Int @@ -25,7 +22,7 @@ public protocol P { // CHECK: public struct X : @unsafe UserModule.P public struct X: @unsafe P { -// CHECK: #if compiler(>=5.3) && $AllowUnsafeAttribute +// CHECK: #if compiler(>=5.3) && $MemorySafetyAttributes // CHECK: @unsafe public func f() // CHECK: #else // CHECK: public func f() diff --git a/test/Unsafe/module-trace.swift b/test/Unsafe/module-trace.swift index 7beea9c766657..89636adf76346 100644 --- a/test/Unsafe/module-trace.swift +++ b/test/Unsafe/module-trace.swift @@ -1,14 +1,11 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -enable-experimental-feature AllowUnsafeAttribute -// RUN: %target-swift-frontend -emit-module-path %t/safe_swift_decls.swiftmodule %S/Inputs/safe_swift_decls.swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe +// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift +// RUN: %target-swift-frontend -emit-module-path %t/safe_swift_decls.swiftmodule %S/Inputs/safe_swift_decls.swift -strict-memory-safety -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -I %S/Inputs -I %t -emit-loaded-module-trace-path %t/unsafe.trace +// RUN: %target-typecheck-verify-swift -strict-memory-safety -I %S/Inputs -I %t -emit-loaded-module-trace-path %t/unsafe.trace // RUN: %FileCheck -check-prefix TRACE %s < %t/unsafe.trace -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe - import unsafe_decls import unsafe_swift_decls import safe_swift_decls diff --git a/test/Unsafe/safe.swift b/test/Unsafe/safe.swift index 6a96154cdcbac..b707471ed2e3b 100644 --- a/test/Unsafe/safe.swift +++ b/test/Unsafe/safe.swift @@ -1,7 +1,10 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -print-diagnostic-groups +// RUN: %target-typecheck-verify-swift -strict-memory-safety -print-diagnostic-groups + +// The feature flag should be enabled. +#if !hasFeature(StrictMemorySafety) +#error("Strict memory safety is not enabled!") +#endif -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe @unsafe func unsafeFunction() { } diff --git a/test/Unsafe/safe_argument_suppression.swift b/test/Unsafe/safe_argument_suppression.swift index 6f3a9b5aef1d7..1a571607a95ef 100644 --- a/test/Unsafe/safe_argument_suppression.swift +++ b/test/Unsafe/safe_argument_suppression.swift @@ -1,7 +1,4 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -print-diagnostic-groups - -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe +// RUN: %target-typecheck-verify-swift -strict-memory-safety -print-diagnostic-groups @unsafe class NotSafe { diff --git a/test/Unsafe/unsafe-suppression.swift b/test/Unsafe/unsafe-suppression.swift index c47dfa2853e4f..eb3c108cb7c1d 100644 --- a/test/Unsafe/unsafe-suppression.swift +++ b/test/Unsafe/unsafe-suppression.swift @@ -1,7 +1,4 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -print-diagnostic-groups - -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe +// RUN: %target-typecheck-verify-swift -strict-memory-safety -print-diagnostic-groups @unsafe func iAmUnsafe() { } diff --git a/test/Unsafe/unsafe.swift b/test/Unsafe/unsafe.swift index 74f503bca0f23..18c644da82366 100644 --- a/test/Unsafe/unsafe.swift +++ b/test/Unsafe/unsafe.swift @@ -1,13 +1,10 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -enable-experimental-feature AllowUnsafeAttribute +// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -I %t -print-diagnostic-groups +// RUN: %target-typecheck-verify-swift -strict-memory-safety -I %t -print-diagnostic-groups // Make sure everything compiles without error when unsafe code is allowed. -// RUN: %target-swift-frontend -typecheck -enable-experimental-feature AllowUnsafeAttribute %s -I %t - -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe +// RUN: %target-swift-frontend -typecheck %s -I %t import unsafe_swift_decls diff --git a/test/Unsafe/unsafe_c_imports.swift b/test/Unsafe/unsafe_c_imports.swift index abcec88068140..4ac9fe64ca452 100644 --- a/test/Unsafe/unsafe_c_imports.swift +++ b/test/Unsafe/unsafe_c_imports.swift @@ -1,7 +1,4 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -I %S/Inputs - -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe +// RUN: %target-typecheck-verify-swift -strict-memory-safety -I %S/Inputs import unsafe_decls diff --git a/test/Unsafe/unsafe_command_line.swift b/test/Unsafe/unsafe_command_line.swift index 881da443ac505..fdb9d2651db76 100644 --- a/test/Unsafe/unsafe_command_line.swift +++ b/test/Unsafe/unsafe_command_line.swift @@ -1,6 +1,4 @@ -// RUN: %target-swift-frontend -typecheck -enable-experimental-feature WarnUnsafe -Ounchecked -disable-access-control %s 2>&1 | %FileCheck %s - -// REQUIRES: swift_feature_WarnUnsafe +// RUN: %target-swift-frontend -typecheck -strict-memory-safety -Ounchecked -disable-access-control %s 2>&1 | %FileCheck %s // CHECK: warning: '-Ounchecked' is not memory-safe // CHECK: warning: '-disable-access-control' is not memory-safe diff --git a/test/Unsafe/unsafe_concurrency.swift b/test/Unsafe/unsafe_concurrency.swift index 64952c6c848b0..0820ab11c5ce3 100644 --- a/test/Unsafe/unsafe_concurrency.swift +++ b/test/Unsafe/unsafe_concurrency.swift @@ -1,12 +1,10 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature AllowUnsafeAttribute +// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -// RUN: %target-typecheck-verify-swift -enable-experimental-feature WarnUnsafe -enable-experimental-feature StrictConcurrency -enable-experimental-feature AllowUnsafeAttribute -I %t +// RUN: %target-typecheck-verify-swift -strict-memory-safety -enable-experimental-feature StrictConcurrency -I %t // REQUIRES: concurrency // REQUIRES: swift_feature_StrictConcurrency -// REQUIRES: swift_feature_WarnUnsafe -// REQUIRES: swift_feature_AllowUnsafeAttribute @preconcurrency import unsafe_swift_decls // expected-warning{{@preconcurrency import is not memory-safe because it can silently introduce data races}} diff --git a/test/Unsafe/unsafe_disallowed.swift b/test/Unsafe/unsafe_disallowed.swift deleted file mode 100644 index 3874de05a11e0..0000000000000 --- a/test/Unsafe/unsafe_disallowed.swift +++ /dev/null @@ -1,5 +0,0 @@ -// RUN: %target-typecheck-verify-swift - -@unsafe func f() { } -// expected-error@-1{{attribute requires '-enable-experimental-feature AllowUnsafeAttribute'}} - diff --git a/test/Unsafe/unsafe_feature.swift b/test/Unsafe/unsafe_feature.swift new file mode 100644 index 0000000000000..1ec94a5fffe51 --- /dev/null +++ b/test/Unsafe/unsafe_feature.swift @@ -0,0 +1,19 @@ +// RUN: %target-typecheck-verify-swift + + +// Can use @unsafe and @safe without strict memory safety being enabled. +@unsafe func f() { } +@safe func g(_: UnsafeRawPointer) { } + +protocol P { + func f() +} + +struct X: @unsafe P { + @unsafe func f() { } +} + +// The feature flag is not enabled, though. +#if hasFeature(StrictMemorySafety) +#error("Strict memory safety is not enabled!") +#endif diff --git a/test/Unsafe/unsafe_imports.swift b/test/Unsafe/unsafe_imports.swift index e113e847529b2..f02db8b56e505 100644 --- a/test/Unsafe/unsafe_imports.swift +++ b/test/Unsafe/unsafe_imports.swift @@ -1,10 +1,7 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -enable-experimental-feature AllowUnsafeAttribute +// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -I %S/Inputs -I %t - -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe +// RUN: %target-typecheck-verify-swift -strict-memory-safety -I %S/Inputs -I %t import unsafe_decls import unsafe_swift_decls diff --git a/test/Unsafe/unsafe_in_unsafe.swift b/test/Unsafe/unsafe_in_unsafe.swift index 63c5f69f151f2..c0e1420082d6d 100644 --- a/test/Unsafe/unsafe_in_unsafe.swift +++ b/test/Unsafe/unsafe_in_unsafe.swift @@ -1,7 +1,4 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -print-diagnostic-groups - -// REQUIRES: swift_feature_AllowUnsafeAttribute - +// RUN: %target-typecheck-verify-swift -print-diagnostic-groups protocol P { } diff --git a/test/Unsafe/unsafe_stdlib.swift b/test/Unsafe/unsafe_stdlib.swift index da7fe3d7d087a..08bd0e26f859e 100644 --- a/test/Unsafe/unsafe_stdlib.swift +++ b/test/Unsafe/unsafe_stdlib.swift @@ -1,10 +1,7 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-feature WarnUnsafe +// RUN: %target-typecheck-verify-swift -strict-memory-safety // Make sure everything compiles without error when unsafe code is allowed. -// RUN: %target-swift-frontend -typecheck -enable-experimental-feature AllowUnsafeAttribute -warnings-as-errors %s - -// REQUIRES: swift_feature_AllowUnsafeAttribute -// REQUIRES: swift_feature_WarnUnsafe +// RUN: %target-swift-frontend -typecheck -warnings-as-errors %s func test( x: OpaquePointer, diff --git a/test/lit.swift-features.cfg.inc b/test/lit.swift-features.cfg.inc index 01ca97e1fb87e..44457af0e0368 100644 --- a/test/lit.swift-features.cfg.inc +++ b/test/lit.swift-features.cfg.inc @@ -31,5 +31,6 @@ def language_feature(feature_name, enabled): #define UPCOMING_FEATURE(FeatureName, SENumber, Version) language_feature(#FeatureName, True) #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) language_feature(#FeatureName, #AvailableInProd == "true") #define LANGUAGE_FEATURE(FeatureName, SENumber, Description) language_feature(#FeatureName, True) +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) language_feature(#FeatureName, True) #include From 939d4d7d91a2bb05f2279f837693cb44813c3cc4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 26 Feb 2025 13:03:55 -0800 Subject: [PATCH 4/5] [SE-0458] Disambiguate "unsafe" expression and for..in effect Port over the disambiguation code we already had in the new Swift parser to the existing C++ parser for the "unsafe" expression and for..in effect. --- lib/Parse/ParseExpr.cpp | 4 +++- lib/Parse/ParseStmt.cpp | 3 ++- test/Unsafe/safe.swift | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index acd4883a8bcdb..acf6dadf587e8 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -436,7 +436,9 @@ ParserResult Parser::parseExprSequenceElement(Diag<> message, return sub; } - if (Tok.isContextualKeyword("unsafe")) { + if (Tok.isContextualKeyword("unsafe") && + !peekToken().isAtStartOfLine() && + !peekToken().is(tok::r_paren)) { Tok.setKind(tok::contextual_keyword); SourceLoc unsafeLoc = consumeToken(); ParserResult sub = diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 1c83c8786252f..f7992bbd6abda 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -2379,7 +2379,8 @@ ParserResult Parser::parseStmtForEach(LabeledStmtInfo LabelInfo) { } } - if (Tok.isContextualKeyword("unsafe")) { + if (Tok.isContextualKeyword("unsafe") && + !peekToken().isAny(tok::colon, tok::kw_in)) { UnsafeLoc = consumeToken(); } diff --git a/test/Unsafe/safe.swift b/test/Unsafe/safe.swift index b707471ed2e3b..37b72454ff523 100644 --- a/test/Unsafe/safe.swift +++ b/test/Unsafe/safe.swift @@ -98,6 +98,15 @@ func testUnsafeAsSequenceForEach() { for unsafe _ in unsafe uas { } // okay } +func testForInUnsafeAmbiguity(_ integers: [Int]) { + for unsafe in integers { + _ = unsafe + } + for unsafe: Int in integers { + _ = unsafe + } +} + struct UnsafeIterator: @unsafe IteratorProtocol { @unsafe mutating func next() -> Int? { nil } } From 7977e4682ce47a0790b5283ddc66a2818f830275 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 26 Feb 2025 13:13:05 -0800 Subject: [PATCH 5/5] Add a ChangeLog entry for SE-0458 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ userdocs/diagnostic_groups/Unsafe.md | 11 +++++++++++ 2 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2542dd5ba6a9c..2ea34536a6f30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ > [!NOTE] > This is in reverse chronological order, so newer entries are added to the top. +## Swift 6.2 + +* [SE-0458][]: + Introduced an opt-in mode for strict checking of memory safety, which can be + enabled with the compiler flag `-strict-memory-safety`. In this mode, + the Swift compiler will produce warnings for uses of memory-unsafe constructs + and APIs. For example, + + ```swift + func evilMalloc(size: Int) -> Int { + // warning: call to global function 'malloc' involves unsafe type 'UnsafeMutableRawPointer' + return Int(bitPattern: malloc(size)) + } + ``` + + These warnings are in their own diagnostic group (`Unsafe`) and can + be suppressed by ackwnowledging the memory-unsafe behavior, for + example with an `unsafe` expression: + + ```swift + func evilMalloc(size: Int) -> Int { + return unsafe Int(bitPattern: malloc(size)) // no warning + } + ``` + ## Swift 6.1 * Previous versions of Swift would incorrectly allow Objective-C `-init...` @@ -10676,6 +10701,7 @@ using the `.dynamicType` member to retrieve the type of an expression should mig [SE-0431]: https://github.com/apple/swift-evolution/blob/main/proposals/0431-isolated-any-functions.md [SE-0442]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0442-allow-taskgroup-childtaskresult-type-to-be-inferred.md [SE-0444]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md +[SE-0458]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md [#64927]: [#42697]: [#42728]: diff --git a/userdocs/diagnostic_groups/Unsafe.md b/userdocs/diagnostic_groups/Unsafe.md index e9ac00f817717..b22e5b7833b82 100644 --- a/userdocs/diagnostic_groups/Unsafe.md +++ b/userdocs/diagnostic_groups/Unsafe.md @@ -23,3 +23,14 @@ This diagnostic group includes warnings that identify the use of unsafe language return Int(bitPattern: malloc(size)) } ``` + +These warnings can be suppressed using an `unsafe` expression, which acknowledges the presence of memory-unsafe code. For example: + +```swift +func evilMalloc(size: Int) -> Int { + return unsafe Int(bitPattern: malloc(size)) +} +``` + +The warnings produced by this diagnostic group can be enabled with the compiler +flag `-strict-memory-safety`. \ No newline at end of file