Skip to content

Commit 8463e2a

Browse files
authored
Merge pull request swiftlang#79240 from DougGregor/safe-cover-unsafe-arguments
@safe functions, properties, and subscripts "cover" certain unsafe arguments
2 parents 860d68e + 4139430 commit 8463e2a

File tree

3 files changed

+144
-2
lines changed

3 files changed

+144
-2
lines changed

lib/Sema/TypeCheckEffects.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,16 @@ class Classification {
10181018

10191019
return result;
10201020
}
1021+
1022+
/// Return a classification that is the same as this one, but drops the
1023+
/// 'unsafe' effect.
1024+
Classification withoutUnsafe() const {
1025+
Classification result(*this);
1026+
result.UnsafeKind = ConditionalEffectKind::None;
1027+
result.UnsafeUses.clear();
1028+
return result;
1029+
}
1030+
10211031
/// Return a classification that promotes a typed throws effect to an
10221032
/// untyped throws effect.
10231033
Classification promoteToUntypedThrows() const {
@@ -3149,6 +3159,10 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
31493159
llvm::DenseMap<Expr *, std::vector<DiagnosticInfo>> uncoveredAsync;
31503160
llvm::DenseMap<Expr *, Expr *> parentMap;
31513161

3162+
/// Expressions that are assumed to be safe because they are being
3163+
/// passed directly into an explicitly `@safe` function.
3164+
llvm::DenseSet<const Expr *> assumedSafeArguments;
3165+
31523166
/// Tracks all of the uncovered uses of unsafe constructs based on their
31533167
/// anchor expression, so we can emit diagnostics at the end.
31543168
llvm::MapVector<Expr *, std::vector<UnsafeUse>> uncoveredUnsafeUses;
@@ -3526,6 +3540,12 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
35263540
ShouldRecurse_t checkWithSubstitutionMap(Expr *E, SubstitutionMap subs) {
35273541
auto classification = Classification::forSubstitutionMap(
35283542
subs, E->getLoc());
3543+
3544+
// If this expression is covered as a safe argument, drop the unsafe
3545+
// classification.
3546+
if (assumedSafeArguments.contains(E))
3547+
classification = classification.withoutUnsafe();
3548+
35293549
checkEffectSite(E, /*requiresTry=*/false, classification);
35303550
return ShouldRecurse;
35313551
}
@@ -3535,13 +3555,25 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
35353555
ArrayRef<ProtocolConformanceRef> conformances) {
35363556
auto classification = Classification::forConformances(
35373557
conformances, E->getLoc());
3558+
3559+
// If this expression is covered as a safe argument, drop the unsafe
3560+
// classification.
3561+
if (assumedSafeArguments.contains(E))
3562+
classification = classification.withoutUnsafe();
3563+
35383564
checkEffectSite(E, /*requiresTry=*/false, classification);
35393565
return ShouldRecurse;
35403566
}
35413567

35423568
ShouldRecurse_t checkType(Expr *E, TypeRepr *typeRepr, Type type) {
35433569
SourceLoc loc = typeRepr ? typeRepr->getLoc() : E->getLoc();
35443570
auto classification = Classification::forType(type, loc);
3571+
3572+
// If this expression is covered as a safe argument, drop the unsafe
3573+
// classification.
3574+
if (assumedSafeArguments.contains(E))
3575+
classification = classification.withoutUnsafe();
3576+
35453577
checkEffectSite(E, /*requiresTry=*/false, classification);
35463578
return ShouldRecurse;
35473579
}
@@ -3646,6 +3678,36 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
36463678
CurContext = savedContext;
36473679
}
36483680

3681+
/// Mark an argument as safe if it is of a form where a @safe declaration
3682+
/// covers it.
3683+
void markArgumentSafe(Expr *arg) {
3684+
auto argValue = arg->findOriginalValue();
3685+
3686+
// Reference to a local variable or parameter.
3687+
if (auto argDRE = dyn_cast<DeclRefExpr>(argValue)) {
3688+
if (auto argDecl = argDRE->getDecl())
3689+
if (argDecl->getDeclContext()->isLocalContext())
3690+
assumedSafeArguments.insert(argDRE);
3691+
}
3692+
3693+
// Metatype reference.
3694+
if (isa<TypeExpr>(argValue))
3695+
assumedSafeArguments.insert(argValue);
3696+
}
3697+
3698+
/// Mark any of the local variable references within the given argument
3699+
/// list as safe, so we don't diagnose unsafe uses of them.
3700+
void markArgumentsSafe(ArgumentList *argumentList) {
3701+
if (!argumentList)
3702+
return;
3703+
3704+
for (const auto &arg: *argumentList) {
3705+
if (auto argExpr = arg.getExpr()) {
3706+
markArgumentSafe(argExpr);
3707+
}
3708+
}
3709+
}
3710+
36493711
ShouldRecurse_t checkApply(ApplyExpr *E) {
36503712
// An apply expression is a potential throw site if the function throws.
36513713
// But if the expression didn't type-check, suppress diagnostics.
@@ -3673,6 +3735,14 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
36733735
E->setNoAsync(true);
36743736
}
36753737

3738+
// If this is calling a @safe function, treat local variables as being
3739+
// safe.
3740+
if (auto calleeDecl = E->getCalledValue()) {
3741+
if (calleeDecl->getExplicitSafety() == ExplicitSafety::Safe) {
3742+
markArgumentsSafe(E->getArgs());
3743+
}
3744+
}
3745+
36763746
// If current apply expression did not type-check, don't attempt
36773747
// walking inside of it. This accounts for the fact that we don't
36783748
// erase types without type variables to enable better code complication,
@@ -3698,6 +3768,22 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
36983768
E->setThrows(throwDest);
36993769
}
37003770

3771+
// If the declaration we're referencing is explicitly @safe, mark arguments
3772+
// as potentially being safe.
3773+
if (auto decl = E->getMember().getDecl()) {
3774+
if (decl->getExplicitSafety() == ExplicitSafety::Safe) {
3775+
// The base can be considered safe.
3776+
markArgumentSafe(E->getBase());
3777+
3778+
// Mark subscript arguments safe.
3779+
if (auto subscript = dyn_cast<SubscriptExpr>(E)) {
3780+
markArgumentsSafe(subscript->getArgs());
3781+
} else if (auto dynSubscript = dyn_cast<DynamicSubscriptExpr>(E)) {
3782+
markArgumentsSafe(dynSubscript->getArgs());
3783+
}
3784+
}
3785+
}
3786+
37013787
return ShouldRecurse;
37023788
}
37033789

@@ -3712,6 +3798,10 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
37123798
if (!isEvaluated)
37133799
classification = classification.onlyUnsafe();
37143800

3801+
// If we're treating this expression as a safe argument, drop 'unsafe'.
3802+
if (assumedSafeArguments.contains(expr))
3803+
classification = classification.withoutUnsafe();
3804+
37153805
auto throwDest = checkEffectSite(
37163806
expr, classification.hasThrows(), classification);
37173807

test/Unsafe/safe.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,7 @@ func testMyArray(ints: MyArray<Int>) {
171171
let bufferCopy = unsafe buffer
172172
_ = unsafe bufferCopy
173173

174-
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
175-
print(buffer.safeCount) // expected-note{{reference to parameter 'buffer' involves unsafe type 'UnsafeBufferPointer<Int>'}}
174+
print(buffer.safeCount)
176175
unsafe print(buffer.baseAddress!)
177176
}
178177
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -print-diagnostic-groups
2+
3+
// REQUIRES: swift_feature_AllowUnsafeAttribute
4+
// REQUIRES: swift_feature_WarnUnsafe
5+
6+
@unsafe
7+
class NotSafe {
8+
@safe var okay: Int { 0 }
9+
10+
@safe var safeSelf: NotSafe { unsafe self }
11+
12+
@safe func memberFunc(_: NotSafe) { }
13+
14+
@safe subscript(ns: NotSafe) -> Int { 5 }
15+
16+
@safe static func doStatically(_: NotSafe.Type) { }
17+
18+
@safe static subscript(ns: NotSafe) -> Int { 5 }
19+
20+
@safe init(_: NotSafe) { }
21+
22+
func stillUnsafe() { }
23+
}
24+
25+
@unsafe
26+
class NotSafeSubclass: NotSafe {
27+
}
28+
29+
@safe func okayFunc(_ ns: NotSafe) { }
30+
31+
@safe func testImpliedSafety(ns: NotSafe) {
32+
_ = ns.okay
33+
_ = ns.safeSelf.okay
34+
ns.memberFunc(ns)
35+
okayFunc(ns)
36+
_ = ns[ns]
37+
38+
_ = NotSafe(ns)
39+
_ = NotSafe[ns]
40+
NotSafe.doStatically(NotSafe.self)
41+
42+
ns.stillUnsafe() // expected-warning{{expression uses unsafe constructs but is not marked with 'unsafe' [Unsafe]}}
43+
// expected-note@-1{{reference to parameter 'ns' involves unsafe type 'NotSafe'}}
44+
// expected-note@-2{{reference to unsafe instance method 'stillUnsafe()'}}
45+
}
46+
47+
@safe func testImpliedSafetySubclass(ns: NotSafeSubclass) {
48+
_ = ns.okay
49+
_ = ns.safeSelf.okay
50+
ns.memberFunc(ns)
51+
okayFunc(ns)
52+
_ = ns[ns]
53+
}

0 commit comments

Comments
 (0)