Skip to content

[SE-0458] Adopt strict memory safety in the standard libraries #78372

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 15 commits into from
Feb 27, 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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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...`
Expand Down Expand Up @@ -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]: <https://github.com/apple/swift/issues/64927>
[#42697]: <https://github.com/apple/swift/issues/42697>
[#42728]: <https://github.com/apple/swift/issues/42728>
Expand Down
2 changes: 1 addition & 1 deletion Runtimes/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ add_compile_options(
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature NoncopyableGenerics2>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature SuppressedAssociatedTypes>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature SE427NoInferenceOnExtension>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature AllowUnsafeAttribute>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-strict-memory-safety>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature NonescapableTypes>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature LifetimeDependence>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature MemberImportVisibility>"
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/ConformanceAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 &
Expand All @@ -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;
}
};
Expand Down
10 changes: 4 additions & 6 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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();
Expand Down
18 changes: 15 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2722,8 +2722,23 @@ 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'", ())
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))
Expand Down Expand Up @@ -8150,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 *))
Expand Down
7 changes: 5 additions & 2 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions include/swift/AST/ProtocolConformanceOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 25 additions & 6 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -450,15 +464,20 @@ 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
#undef BASELINE_LANGUAGE_FEATURE
#undef OPTIONAL_LANGUAGE_FEATURE
#undef CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE
#undef CONDITIONALLY_SUPPRESSIBLE_LANGUAGE_FEATURE
#undef SUPPRESSIBLE_EXPERIMENTAL_FEATURE
Expand Down
5 changes: 5 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
3 changes: 2 additions & 1 deletion lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3214,9 +3214,10 @@ struct ExcludeAttrRAII {
}

static void
suppressingFeatureAllowUnsafeAttribute(PrintOptions &options,
suppressingFeatureMemorySafetyAttributes(PrintOptions &options,
llvm::function_ref<void()> action) {
ExcludeAttrRAII scope(options.ExcludeAttrList, DeclAttrKind::Unsafe);
ExcludeAttrRAII scope2(options.ExcludeAttrList, DeclAttrKind::Safe);
action();
}

Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ConformanceLookupTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
options |= ProtocolConformanceFlags::Preconcurrency;
if (getUnsafeLoc().isValid())
options |= ProtocolConformanceFlags::Unsafe;
if (getIsolatedLoc().isValid())
options |= ProtocolConformanceFlags::Isolated;
return options;
}

Expand Down Expand Up @@ -209,6 +211,11 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
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 {
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
16 changes: 11 additions & 5 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,6 @@ UNINTERESTING_FEATURE(ReinitializeConsumeInMultiBlockDefer)
UNINTERESTING_FEATURE(SE427NoInferenceOnExtension)
UNINTERESTING_FEATURE(TrailingComma)

static bool usesFeatureAllowUnsafeAttribute(Decl *decl) {
return decl->getAttrs().hasAttribute<UnsafeAttr>();
}

static ABIAttr *getABIAttr(Decl *decl) {
if (auto pbd = dyn_cast<PatternBindingDecl>(decl))
for (auto i : range(pbd->getNumPatternEntries()))
Expand All @@ -348,7 +344,17 @@ static bool usesFeatureABIAttribute(Decl *decl) {
return getABIAttr(decl) != nullptr;
}

UNINTERESTING_FEATURE(WarnUnsafe)
static bool usesFeatureIsolatedConformances(Decl *decl) {
// FIXME: Check conformances associated with this decl?
return false;
}

static bool usesFeatureMemorySafetyAttributes(Decl *decl) {
return decl->getAttrs().hasAttribute<SafeAttr>() ||
decl->getAttrs().hasAttribute<UnsafeAttr>();
}

UNINTERESTING_FEATURE(StrictMemorySafety)
UNINTERESTING_FEATURE(SafeInteropWrappers)
UNINTERESTING_FEATURE(AssumeResilientCxxTypes)
UNINTERESTING_FEATURE(CoroutineAccessorsUnwindOnCallerError)
Expand Down
25 changes: 24 additions & 1 deletion lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3144,6 +3144,12 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx,
attributed->getTypeRepr(), dc, options);
}

case TypeReprKind::Isolated: {
auto isolated = cast<IsolatedTypeRepr>(typeRepr);
return directReferencesForTypeRepr(evaluator, ctx,
isolated->getBase(), dc, options);
}

case TypeReprKind::Composition: {
auto composition = cast<CompositionTypeRepr>(typeRepr);
for (auto component : composition->getTypes()) {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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<IsolatedTypeRepr>(typeRepr))
return isolatedTypeRepr->getLoc();

if (auto attrTypeRepr = dyn_cast<AttributedTypeRepr>(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(
Expand Down Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion lib/ASTGen/Sources/ASTGen/SourceFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ extension Parser.ExperimentalFeatures {
mapFeature(.CoroutineAccessors, to: .coroutineAccessors)
mapFeature(.ValueGenerics, to: .valueGenerics)
mapFeature(.ABIAttribute, to: .abiAttribute)
mapFeature(.WarnUnsafe, to: .unsafeExpression)
}
}

Expand Down
Loading