Skip to content

Commit 4464744

Browse files
committed
Diagnose unsafe sequences in the for..in loop
The `unsafe` goes on the sequence expression, e.g., for x in unsafe mySequence { ... }
1 parent d787c0f commit 4464744

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

include/swift/AST/UnsafeUse.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,33 @@ class UnsafeUse {
225225
}
226226
}
227227

228+
/// Replace the location, if possible.
229+
void replaceLocation(SourceLoc loc) {
230+
switch (getKind()) {
231+
case Override:
232+
case Witness:
233+
case PreconcurrencyImport:
234+
// Cannot replace location.
235+
return;
236+
237+
case UnsafeConformance:
238+
storage.conformance.location = loc.getOpaquePointerValue();
239+
break;
240+
241+
case TypeWitness:
242+
storage.typeWitness.location = loc.getOpaquePointerValue();
243+
break;
244+
245+
case UnownedUnsafe:
246+
case ExclusivityUnchecked:
247+
case NonisolatedUnsafe:
248+
case ReferenceToUnsafe:
249+
case ReferenceToUnsafeThroughTypealias:
250+
case CallToUnsafe:
251+
storage.entity.location = loc.getOpaquePointerValue();
252+
}
253+
}
254+
228255
/// Get the main declaration, when there is one.
229256
const Decl *getDecl() const {
230257
switch (getKind()) {

lib/Sema/CSGen.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3663,6 +3663,15 @@ generateForEachStmtConstraints(ConstraintSystem &cs, DeclContext *dc,
36633663
ASTContext &ctx = cs.getASTContext();
36643664
bool isAsync = stmt->getAwaitLoc().isValid();
36653665
auto *sequenceExpr = stmt->getParsedSequence();
3666+
3667+
// If we have an unsafe expression for the sequence, lift it out of the
3668+
// sequence expression. We'll put it back after we've introduced the
3669+
// various calls.
3670+
UnsafeExpr *unsafeExpr = dyn_cast<UnsafeExpr>(sequenceExpr);
3671+
if (unsafeExpr) {
3672+
sequenceExpr = unsafeExpr->getSubExpr();
3673+
}
3674+
36663675
auto contextualLocator = cs.getConstraintLocator(
36673676
sequenceExpr, LocatorPathElt::ContextualType(CTP_ForEachSequence));
36683677
auto elementLocator = cs.getConstraintLocator(
@@ -3712,9 +3721,15 @@ generateForEachStmtConstraints(ConstraintSystem &cs, DeclContext *dc,
37123721
ctx, sequenceExpr, makeIterator->getName());
37133722
makeIteratorRef->setFunctionRefInfo(FunctionRefInfo::singleBaseNameApply());
37143723

3715-
auto *makeIteratorCall =
3724+
Expr *makeIteratorCall =
37163725
CallExpr::createImplicitEmpty(ctx, makeIteratorRef);
37173726

3727+
// Swap in the 'unsafe' expression.
3728+
if (unsafeExpr) {
3729+
unsafeExpr->setSubExpr(makeIteratorCall);
3730+
makeIteratorCall = unsafeExpr;
3731+
}
3732+
37183733
Pattern *pattern = NamedPattern::createImplicit(ctx, makeIteratorVar);
37193734
auto *PB = PatternBindingDecl::createImplicit(
37203735
ctx, StaticSpellingKind::None, pattern, makeIteratorCall, dc);

lib/Sema/TypeCheckEffects.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3091,6 +3091,9 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
30913091
llvm::MapVector<Expr *, std::vector<UnsafeUse>> uncoveredUnsafeUses;
30923092

30933093
static bool isEffectAnchor(Expr *e) {
3094+
if (e->getLoc().isInvalid())
3095+
return false;
3096+
30943097
return isa<AbstractClosureExpr>(e) || isa<DiscardAssignmentExpr>(e) ||
30953098
isa<AssignExpr>(e) || (isa<DeclRefExpr>(e) && e->isImplicit());
30963099
}
@@ -3757,11 +3760,23 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
37573760
Expr *expr = E.dyn_cast<Expr*>();
37583761
Expr *anchor = walkToAnchor(expr, parentMap,
37593762
CurContext.isWithinInterpolatedString());
3763+
3764+
// Figure out a location to use if the unsafe use didn't have one.
3765+
SourceLoc replacementLoc;
3766+
if (anchor)
3767+
replacementLoc = anchor->getLoc();
3768+
if (replacementLoc.isInvalid())
3769+
replacementLoc = E.getStartLoc();
3770+
37603771
auto &anchorUncovered = uncoveredUnsafeUses[anchor];
3761-
anchorUncovered.insert(
3762-
anchorUncovered.end(),
3763-
classification.getUnsafeUses().begin(),
3764-
classification.getUnsafeUses().end());
3772+
for (UnsafeUse unsafeUse : classification.getUnsafeUses()) {
3773+
// If we don't have a source location for this use, use the
3774+
// anchor instead.
3775+
if (unsafeUse.getLocation().isInvalid())
3776+
unsafeUse.replaceLocation(replacementLoc);
3777+
3778+
anchorUncovered.push_back(unsafeUse);
3779+
}
37653780
}
37663781
break;
37673782
}
@@ -3940,6 +3955,16 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
39403955
}
39413956

39423957
ShouldRecurse_t checkForEach(ForEachStmt *S) {
3958+
// Reparent the type-checked sequence on the parsed sequence, so we can
3959+
// find an anchor.
3960+
if (auto typeCheckedExpr = S->getTypeCheckedSequence()) {
3961+
parentMap = typeCheckedExpr->getParentMap();
3962+
3963+
if (auto parsedSequence = S->getParsedSequence()) {
3964+
parentMap[typeCheckedExpr] = parsedSequence;
3965+
}
3966+
}
3967+
39433968
if (!S->getAwaitLoc().isValid())
39443969
return ShouldRecurse;
39453970

test/Unsafe/safe.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@ func returnsExistentialP() -> any P {
8181
// expected-note@-1{{@unsafe conformance of 'Int' to protocol 'P' involves unsafe code}}
8282
}
8383

84+
struct UnsafeAsSequence: @unsafe Sequence, IteratorProtocol {
85+
mutating func next() -> Int? { nil }
86+
}
87+
88+
func testUnsafeAsSequenceForEach() {
89+
let uas = UnsafeAsSequence()
90+
91+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}{{12-12=unsafe }}
92+
for _ in uas { } // expected-note{{conformance}}
93+
94+
for _ in unsafe uas { } // okay
95+
}
96+
8497
class MyRange {
8598
@unsafe init(unchecked bounds: Range<Int>) { }
8699

0 commit comments

Comments
 (0)