Skip to content

Commit 4139430

Browse files
committed
@safe functions, properties, and subscripts "cover" certain unsafe arguments
When calling an explicitly-@safe function or subscript, or accessing an explicitly-@safe property, the direct arguments to that operation can be considered safe if they are references to local variables or are references to types. This brings the implementation in line with the recent adjustments to the proposal within the review.
1 parent d276006 commit 4139430

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)