Skip to content

Commit 541d0d1

Browse files
committed
Disallow consuming self in a noncopyable deinit again.
The changes to allow for partial consumption unintentionally also allowed for `self` to be consumed as a whole during `deinit`, which we don't yet want to allow because it could lead to accidental "resurrection" and/or accidental infinite recursion if the consuming method lets `deinit` be implicitly run again. This makes it an error again. The experimental feature `ConsumeSelfInDeinit` will allow it for test coverage or experimentation purposes. rdar://132761460
1 parent d9476aa commit 541d0d1

File tree

9 files changed

+145
-5
lines changed

9 files changed

+145
-5
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,8 @@ ERROR(sil_movechecking_discard_missing_consume_self, none,
876876
"must consume 'self' before exiting method that discards self", ())
877877
ERROR(sil_movechecking_reinit_after_discard, none,
878878
"cannot reinitialize 'self' after 'discard self'", ())
879+
ERROR(sil_movechecking_consume_during_deinit, none,
880+
"'self' cannot be consumed during 'deinit'", ())
879881

880882
NOTE(sil_movechecking_discard_self_here, none,
881883
"discarded self here", ())

include/swift/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ EXPERIMENTAL_FEATURE(OldOwnershipOperatorSpellings, true)
246246
EXPERIMENTAL_FEATURE(MoveOnlyEnumDeinits, true)
247247
EXPERIMENTAL_FEATURE(MoveOnlyTuples, true)
248248
EXPERIMENTAL_FEATURE(MoveOnlyPartialReinitialization, true)
249+
EXPERIMENTAL_FEATURE(ConsumeSelfInDeinit, true)
249250

250251
EXPERIMENTAL_FEATURE(OneWayClosureParameters, false)
251252
EXPERIMENTAL_FEATURE(LayoutPrespecialization, true)

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ UNINTERESTING_FEATURE(BorrowingSwitch)
695695

696696
UNINTERESTING_FEATURE(ClosureIsolation)
697697
UNINTERESTING_FEATURE(Extern)
698+
UNINTERESTING_FEATURE(ConsumeSelfInDeinit)
698699

699700
static bool usesFeatureConformanceSuppression(Decl *decl) {
700701
auto *nominal = dyn_cast<NominalTypeDecl>(decl);

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1816,7 +1816,21 @@ shouldEmitPartialMutationError(UseState &useState, PartialMutation::Kind kind,
18161816
LLVM_DEBUG(llvm::dbgs() << " Iter Type: " << iterType << '\n'
18171817
<< " Target Type: " << targetType << '\n');
18181818

1819+
// Allowing full object consumption in a deinit is still not allowed.
18191820
if (iterType == targetType && !isa<DropDeinitInst>(user)) {
1821+
// Don't allow whole-value consumption of `self` from a `deinit`.
1822+
if (!fn->getModule().getASTContext().LangOpts
1823+
.hasFeature(Feature::ConsumeSelfInDeinit)
1824+
&& kind == PartialMutation::Kind::Consume
1825+
&& useState.sawDropDeinit
1826+
// TODO: Revisit this when we introduce deinits on enums.
1827+
&& !targetType.getEnumOrBoundGenericEnum()) {
1828+
LLVM_DEBUG(llvm::dbgs() << " IterType is TargetType in deinit! "
1829+
"Not allowed yet");
1830+
1831+
return {PartialMutationError::consumeDuringDeinit(iterType)};
1832+
}
1833+
18201834
LLVM_DEBUG(llvm::dbgs() << " IterType is TargetType! Exiting early "
18211835
"without emitting error!\n");
18221836
return {};

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,5 +889,11 @@ void DiagnosticEmitter::emitCannotPartiallyMutateError(
889889
registerDiagnosticEmitted(address);
890890
return;
891891
}
892+
case PartialMutationError::Kind::ConsumeDuringDeinit: {
893+
astContext.Diags.diagnose(user->getLoc().getSourceLoc(),
894+
diag::sil_movechecking_consume_during_deinit);
895+
return;
896+
}
892897
}
898+
llvm_unreachable("unhandled case");
893899
}

lib/SILOptimizer/Mandatory/MoveOnlyTypeUtils.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,10 @@ class PartialMutationError {
164164
struct NonfrozenUsableFromInlineType {
165165
NominalTypeDecl &nominal;
166166
};
167+
struct ConsumeDuringDeinit {
168+
};
167169
using Payload = TaggedUnion<FeatureDisabled, HasDeinit, NonfrozenImportedType,
168-
NonfrozenUsableFromInlineType>;
170+
NonfrozenUsableFromInlineType, ConsumeDuringDeinit>;
169171
Payload payload;
170172

171173
PartialMutationError(SILType type, Payload payload)
@@ -212,6 +214,8 @@ class PartialMutationError {
212214
/// with library evolution, prevent the aggregate from being partially
213215
/// mutated.
214216
NonfrozenUsableFromInlineType,
217+
/// The value was fully consumed in a `deinit`.
218+
ConsumeDuringDeinit,
215219
};
216220

217221
operator Kind() {
@@ -221,8 +225,12 @@ class PartialMutationError {
221225
return Kind::HasDeinit;
222226
else if (payload.isa<NonfrozenImportedType>())
223227
return Kind::NonfrozenImportedType;
224-
assert(payload.isa<NonfrozenUsableFromInlineType>());
225-
return Kind::NonfrozenUsableFromInlineType;
228+
else if (payload.isa<NonfrozenUsableFromInlineType>())
229+
return Kind::NonfrozenUsableFromInlineType;
230+
else if (payload.isa<ConsumeDuringDeinit>())
231+
return Kind::ConsumeDuringDeinit;
232+
233+
llvm_unreachable("unhandled tag");
226234
}
227235

228236
static PartialMutationError featureDisabled(SILType type,
@@ -245,6 +253,10 @@ class PartialMutationError {
245253
return PartialMutationError(type, NonfrozenUsableFromInlineType{nominal});
246254
}
247255

256+
static PartialMutationError consumeDuringDeinit(SILType type) {
257+
return PartialMutationError(type, ConsumeDuringDeinit{});
258+
}
259+
248260
PartialMutation::Kind getKind() {
249261
return payload.get<FeatureDisabled>().kind;
250262
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// RUN: %target-swift-frontend -DEMPTY -emit-sil -verify %s
2+
// RUN: %target-swift-frontend -DTRIVIAL -emit-sil -verify %s
3+
// RUN: %target-swift-frontend -DLOADABLE -emit-sil -verify %s
4+
// RUN: %target-swift-frontend -DADDRESS_ONLY -emit-sil -verify %s
5+
6+
// Don't allow whole-value consumption or mutation of a value in its own
7+
// `deinit`, but allow consumption of its individual fields.
8+
9+
struct T1: ~Copyable {
10+
#if EMPTY
11+
#elseif TRIVIAL
12+
var _x: Int
13+
#elseif LOADABLE
14+
var _x: AnyObject
15+
#elseif ADDRESS_ONLY
16+
var _x: Any
17+
#else
18+
#error("pick one")
19+
#endif
20+
deinit {
21+
self.foo() // expected-error{{'self' cannot be consumed during 'deinit'}}
22+
}
23+
24+
consuming func foo() {}
25+
}
26+
27+
struct T2: ~Copyable {
28+
#if EMPTY
29+
#elseif TRIVIAL
30+
var _x: Int
31+
#elseif LOADABLE
32+
var _x: AnyObject
33+
#elseif ADDRESS_ONLY
34+
var _x: Any
35+
#else
36+
#error("pick one")
37+
#endif
38+
39+
deinit {
40+
let myself = self // expected-error{{'self' cannot be consumed during 'deinit'}}
41+
_ = myself
42+
}
43+
}
44+
45+
func consume<T: ~Copyable>(_: consuming T) {}
46+
47+
struct NC<T: ~Copyable>: ~Copyable {
48+
var x: T
49+
}
50+
51+
struct T3: ~Copyable {
52+
#if EMPTY
53+
var _x: NC<()>
54+
#elseif TRIVIAL
55+
var _x: NC<Int>
56+
#elseif LOADABLE
57+
var _x: NC<AnyObject>
58+
#elseif ADDRESS_ONLY
59+
var _x: NC<Any>
60+
#else
61+
#error("pick one")
62+
#endif
63+
64+
// consuming the single field of a one-field type is OK
65+
deinit {
66+
consume(_x)
67+
}
68+
}
69+
70+
struct T4: ~Copyable {
71+
#if EMPTY
72+
#elseif TRIVIAL
73+
var _x: Int
74+
#elseif LOADABLE
75+
var _x: AnyObject
76+
#elseif ADDRESS_ONLY
77+
var _x: Any
78+
#else
79+
#error("pick one")
80+
#endif
81+
82+
// the implicit cleanup of the value is OK
83+
deinit {
84+
}
85+
}
86+
87+
struct T5: ~Copyable {
88+
#if EMPTY
89+
var _x: NC<()>
90+
#elseif TRIVIAL
91+
var _x: NC<Int>
92+
#elseif LOADABLE
93+
var _x: NC<AnyObject>
94+
#elseif ADDRESS_ONLY
95+
var _x: NC<Any>
96+
#else
97+
#error("pick one")
98+
#endif
99+
100+
// the implicit cleanup of the value is OK
101+
deinit {
102+
}
103+
}
104+

test/SILOptimizer/moveonly_deinits.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -sil-verify-all -verify -emit-sil -enable-experimental-feature MoveOnlyEnumDeinits %s
1+
// RUN: %target-swift-frontend -sil-verify-all -verify -emit-sil -enable-experimental-feature MoveOnlyEnumDeinits -enable-experimental-feature ConsumeSelfInDeinit %s
22

33
class Klass {}
44

test/SILOptimizer/moveonly_discard.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -sil-verify-all -verify -emit-sil -enable-experimental-feature MoveOnlyEnumDeinits %s
1+
// RUN: %target-swift-frontend -sil-verify-all -verify -emit-sil -enable-experimental-feature MoveOnlyEnumDeinits -enable-experimental-feature ConsumeSelfInDeinit %s
22

33

44
func posix_close(_ t: Int) {}

0 commit comments

Comments
 (0)