Skip to content

Commit 35628cb

Browse files
committed
[Strict memory safety] Fix "unsafe" checking for the for..in loop
The `$generator` variable we create for the async for..in loop is `nonisolated(unsafe)`, so ensure that we generate an `unsafe` expression when we use it. This uncovered some inconsistencies in how we do `unsafe` checking for for..in loops, so fix those. Fixes rdar://154775389.
1 parent 1e87656 commit 35628cb

File tree

4 files changed

+23
-10
lines changed

4 files changed

+23
-10
lines changed

lib/Sema/CSGen.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4728,10 +4728,15 @@ generateForEachStmtConstraints(ConstraintSystem &cs, DeclContext *dc,
47284728
}
47294729

47304730
// Wrap the 'next' call in 'unsafe', if the for..in loop has that
4731-
// effect.
4732-
if (stmt->getUnsafeLoc().isValid()) {
4733-
nextCall = new (ctx) UnsafeExpr(
4734-
stmt->getUnsafeLoc(), nextCall, Type(), /*implicit=*/true);
4731+
// effect or if the loop is async (in which case the iterator variable
4732+
// is nonisolated(unsafe).
4733+
if (stmt->getUnsafeLoc().isValid() ||
4734+
(isAsync &&
4735+
ctx.LangOpts.StrictConcurrencyLevel == StrictConcurrency::Complete)) {
4736+
SourceLoc loc = stmt->getUnsafeLoc();
4737+
if (loc.isInvalid())
4738+
loc = stmt->getForLoc();
4739+
nextCall = new (ctx) UnsafeExpr(loc, nextCall, Type(), /*implicit=*/true);
47354740
}
47364741

47374742
// The iterator type must conform to IteratorProtocol.

lib/Sema/TypeCheckEffects.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2457,7 +2457,7 @@ class ApplyClassifier {
24572457
return ShouldRecurse;
24582458
}
24592459
ShouldRecurse_t checkUnsafe(UnsafeExpr *E) {
2460-
return E->isImplicit() ? ShouldRecurse : ShouldNotRecurse;
2460+
return ShouldNotRecurse;
24612461
}
24622462
ShouldRecurse_t checkTry(TryExpr *E) {
24632463
return ShouldRecurse;
@@ -4626,10 +4626,6 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
46264626
diagnoseUnsafeUse(unsafeUse);
46274627
}
46284628
}
4629-
} else if (S->getUnsafeLoc().isValid()) {
4630-
// Extraneous "unsafe" on the sequence.
4631-
Ctx.Diags.diagnose(S->getUnsafeLoc(), diag::no_unsafe_in_unsafe_for)
4632-
.fixItRemove(S->getUnsafeLoc());
46334629
}
46344630

46354631
return ShouldRecurse;
@@ -4689,7 +4685,10 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
46894685
return;
46904686
}
46914687

4692-
Ctx.Diags.diagnose(E->getUnsafeLoc(), diag::no_unsafe_in_unsafe)
4688+
Ctx.Diags.diagnose(E->getUnsafeLoc(),
4689+
forEachNextCallExprs.contains(E)
4690+
? diag::no_unsafe_in_unsafe_for
4691+
: diag::no_unsafe_in_unsafe)
46934692
.fixItRemove(E->getUnsafeLoc());
46944693
}
46954694

test/Unsafe/safe.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ func testUnsafeAsSequenceForEach() {
9898
for _ in unsafe uas { } // expected-warning{{for-in loop uses unsafe constructs but is not marked with 'unsafe'}}{{documentation-file=strict-memory-safety}}{{7-7=unsafe }}
9999

100100
for unsafe _ in unsafe uas { } // okay
101+
102+
for unsafe _ in [1, 2, 3] { } // expected-warning{{no unsafe operations occur within 'unsafe' for-in loop}}
101103
}
102104

103105
func testForInUnsafeAmbiguity(_ integers: [Int]) {

test/Unsafe/unsafe_concurrency.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,10 @@ open class SyntaxVisitor {
5555
open class SyntaxAnyVisitor: SyntaxVisitor {
5656
override open func visit(_ token: TokenSyntax) { }
5757
}
58+
59+
@available(SwiftStdlib 5.1, *)
60+
func testMemorySafetyWithForLoop() async {
61+
let (stream, continuation) = AsyncStream<Int>.makeStream()
62+
for await _ in stream {}
63+
_ = continuation
64+
}

0 commit comments

Comments
 (0)