Skip to content

Commit 37bfa19

Browse files
committed
Tighten up checking for uses of unsafe conformances
Check for unsafe conformances for type erasure and opaque type erasure. This also uncovered an issue where we were making every conformance of an unsafe type to an unsafe protocol @unsafe implicitly, even though that's not really what we want.
1 parent f14baef commit 37bfa19

File tree

7 files changed

+111
-23
lines changed

7 files changed

+111
-23
lines changed

lib/AST/ConformanceLookupTable.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,6 @@ DeclContext *ConformanceLookupTable::ConformanceSource::getDeclContext() const {
5353
llvm_unreachable("Unhandled ConformanceEntryKind in switch.");
5454
}
5555

56-
bool ConformanceLookupTable::ConformanceSource::isUnsafeContext(DeclContext *dc) {
57-
if (auto enclosingNominal = dc->getSelfNominalTypeDecl())
58-
if (enclosingNominal->isUnsafe())
59-
return true;
60-
61-
if (auto ext = dyn_cast<ExtensionDecl>(dc))
62-
if (ext->getAttrs().hasAttribute<UnsafeAttr>())
63-
return true;
64-
65-
return false;
66-
}
67-
6856
ProtocolDecl *ConformanceLookupTable::ConformanceEntry::getProtocol() const {
6957
if (auto protocol = Conformance.dyn_cast<ProtocolDecl *>())
7058
return protocol;

lib/AST/ConformanceLookupTable.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
171171
options |= ProtocolConformanceFlags::Unchecked;
172172
if (getPreconcurrencyLoc().isValid())
173173
options |= ProtocolConformanceFlags::Preconcurrency;
174-
if (getUnsafeLoc().isValid() || isUnsafeContext(getDeclContext()))
174+
if (getUnsafeLoc().isValid())
175175
options |= ProtocolConformanceFlags::Unsafe;
176176
return options;
177177
}
@@ -264,10 +264,6 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
264264
/// Get the declaration context that this conformance will be
265265
/// associated with.
266266
DeclContext *getDeclContext() const;
267-
268-
private:
269-
/// Whether this declaration context is @unsafe.
270-
static bool isUnsafeContext(DeclContext *dc);
271267
};
272268

273269
/// An entry in the conformance table.

lib/Sema/TypeCheckEffects.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,10 @@ class EffectsHandlingWalker : public ASTWalker {
583583
recurse = ShouldRecurse;
584584
} else if (auto *SVE = dyn_cast<SingleValueStmtExpr>(E)) {
585585
recurse = asImpl().checkSingleValueStmtExpr(SVE);
586+
} else if (auto *UTO = dyn_cast<UnderlyingToOpaqueExpr>(E)) {
587+
recurse = asImpl().checkWithSubstitutionMap(E, UTO->substitutions);
588+
} else if (auto *EE = dyn_cast<ErasureExpr>(E)) {
589+
recurse = asImpl().checkWithConformances(E, EE->getConformances());
586590
}
587591
// Error handling validation (via checkTopLevelEffects) happens after
588592
// type checking. If an unchecked expression is still around, the code was
@@ -1103,6 +1107,32 @@ class Classification {
11031107
return result;
11041108
}
11051109

1110+
/// Used when all we have is a substitution map. This can only find
1111+
/// "unsafe" uses.
1112+
static Classification forSubstitutionMap(SubstitutionMap subs,
1113+
SourceLoc loc) {
1114+
Classification result;
1115+
enumerateUnsafeUses(subs, loc, [&](UnsafeUse use) {
1116+
result.recordUnsafeUse(use);
1117+
return false;
1118+
});
1119+
return result;
1120+
}
1121+
1122+
/// Used when all we have is are conformances. This can only find
1123+
/// "unsafe" uses.
1124+
static Classification forConformances(
1125+
ArrayRef<ProtocolConformanceRef> conformances,
1126+
SourceLoc loc
1127+
) {
1128+
Classification result;
1129+
enumerateUnsafeUses(conformances, loc, [&](UnsafeUse use) {
1130+
result.recordUnsafeUse(use);
1131+
return false;
1132+
});
1133+
return result;
1134+
}
1135+
11061136
void merge(Classification other) {
11071137
if (other.isInvalid())
11081138
IsInvalid = true;
@@ -1854,6 +1884,16 @@ class ApplyClassifier {
18541884
return ShouldRecurse;
18551885
}
18561886

1887+
ShouldRecurse_t checkWithSubstitutionMap(Expr *E, SubstitutionMap subs) {
1888+
return ShouldRecurse;
1889+
}
1890+
1891+
ShouldRecurse_t checkWithConformances(
1892+
Expr *E,
1893+
ArrayRef<ProtocolConformanceRef> conformances) {
1894+
return ShouldRecurse;
1895+
}
1896+
18571897
ConditionalEffectKind checkExhaustiveDoBody(DoCatchStmt *S) {
18581898
// All errors thrown by the do body are caught, but any errors thrown
18591899
// by the catch bodies are bounded by the throwing kind of the do body.
@@ -1975,6 +2015,16 @@ class ApplyClassifier {
19752015
return ShouldRecurse;
19762016
}
19772017

2018+
ShouldRecurse_t checkWithSubstitutionMap(Expr *E, SubstitutionMap subs) {
2019+
return ShouldRecurse;
2020+
}
2021+
2022+
ShouldRecurse_t checkWithConformances(
2023+
Expr *E,
2024+
ArrayRef<ProtocolConformanceRef> conformances) {
2025+
return ShouldRecurse;
2026+
}
2027+
19782028
void visitExprPre(Expr *expr) { return; }
19792029
};
19802030

@@ -3380,6 +3430,22 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
33803430
return ShouldNotRecurse;
33813431
}
33823432

3433+
ShouldRecurse_t checkWithSubstitutionMap(Expr *E, SubstitutionMap subs) {
3434+
auto classification = Classification::forSubstitutionMap(
3435+
subs, E->getLoc());
3436+
checkEffectSite(E, /*requiresTry=*/false, classification);
3437+
return ShouldRecurse;
3438+
}
3439+
3440+
ShouldRecurse_t checkWithConformances(
3441+
Expr *E,
3442+
ArrayRef<ProtocolConformanceRef> conformances) {
3443+
auto classification = Classification::forConformances(
3444+
conformances, E->getLoc());
3445+
checkEffectSite(E, /*requiresTry=*/false, classification);
3446+
return ShouldRecurse;
3447+
}
3448+
33833449
ConditionalEffectKind checkExhaustiveDoBody(DoCatchStmt *S) {
33843450
// This is a context where errors are handled.
33853451
ContextScope scope(*this, CurContext.withHandlesErrors());

lib/Sema/TypeCheckUnsafe.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,10 @@ static bool forEachUnsafeConformance(
273273
return false;
274274
}
275275

276-
bool swift::enumerateUnsafeUses(SubstitutionMap subs,
276+
bool swift::enumerateUnsafeUses(ArrayRef<ProtocolConformanceRef> conformances,
277277
SourceLoc loc,
278278
llvm::function_ref<bool(UnsafeUse)> fn) {
279-
// FIXME: Check replacement types?
280-
for (auto conformance : subs.getConformances()) {
279+
for (auto conformance : conformances) {
281280
if (!conformance.hasEffect(EffectKind::Unsafe))
282281
continue;
283282

@@ -295,6 +294,18 @@ bool swift::enumerateUnsafeUses(SubstitutionMap subs,
295294
return false;
296295
}
297296

297+
bool swift::enumerateUnsafeUses(SubstitutionMap subs,
298+
SourceLoc loc,
299+
llvm::function_ref<bool(UnsafeUse)> fn) {
300+
// FIXME: Check replacement types?
301+
302+
// Check conformances.
303+
if (enumerateUnsafeUses(subs.getConformances(), loc, fn))
304+
return true;
305+
306+
return false;
307+
}
308+
298309
bool swift::isUnsafe(ConcreteDeclRef declRef) {
299310
return enumerateUnsafeUses(
300311
declRef, SourceLoc(), /*isCall=*/false, [&](UnsafeUse) {

lib/Sema/TypeCheckUnsafe.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,17 @@ bool enumerateUnsafeUses(ConcreteDeclRef declRef,
3636
bool isCall,
3737
llvm::function_ref<bool(UnsafeUse)> fn);
3838

39+
/// Enumerate all of the unsafe uses that occur within this array of protocol
40+
/// conformances.
41+
///
42+
/// The given `fn` will be called with each unsafe use. If it returns `true`
43+
/// for any use, this function will return `true` immediately. Otherwise,
44+
/// it will return `false` once all unsafe uses have been emitted.
45+
bool enumerateUnsafeUses(ArrayRef<ProtocolConformanceRef> conformances,
46+
SourceLoc loc,
47+
llvm::function_ref<bool(UnsafeUse)> fn);
48+
3949
/// Enumerate all of the unsafe uses that occur within this substitution map.
40-
/// reference.
4150
///
4251
/// The given `fn` will be called with each unsafe use. If it returns `true`
4352
/// for any use, this function will return `true` immediately. Otherwise,

test/Unsafe/safe.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ func testConformance(i: Int) {
7171
acceptP(i) // expected-note{{@unsafe conformance of 'Int' to protocol 'P' involves unsafe code}}
7272
}
7373

74+
func returnsOpaqueP() -> some P {
75+
5 // expected-warning{{expression uses unsafe constructs but is not marked with 'unsafe'}}
76+
// expected-note@-1{{@unsafe conformance of 'Int' to protocol 'P' involves unsafe code}}
77+
}
78+
79+
func returnsExistentialP() -> any P {
80+
5 // expected-warning{{expression uses unsafe constructs but is not marked with 'unsafe'}}
81+
// expected-note@-1{{@unsafe conformance of 'Int' to protocol 'P' involves unsafe code}}
82+
}
7483

7584
// Parsing of `unsafe` expressions.
7685
func testUnsafePositionError() -> Int {

test/Unsafe/unsafe-suppression.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,21 @@ extension S3: P {
6060

6161
struct S4 { }
6262

63-
@unsafe
64-
extension S4: P {
63+
extension S4: @unsafe P {
6564
@unsafe
6665
func protoMethod() { } // okay
6766
}
6867

68+
protocol P2 {
69+
func proto2Method()
70+
}
71+
72+
@unsafe
73+
extension S4: P2 { // expected-warning{{conformance of 'S4' to protocol 'P2' involves unsafe code; use '@unsafe' to indicate that the conformance is not memory-safe}}
74+
@unsafe
75+
func proto2Method() { } // expected-note{{unsafe instance method}}
76+
}
77+
6978

7079
@unsafe
7180
class SendableC1: @unchecked Sendable { }

0 commit comments

Comments
 (0)