Skip to content

Commit 36eae9d

Browse files
committed
[SILGen] Generate a trap for unexpected cases in all @objc enums
(both C enums and Swift enums declared @objc), because of the "feature" in C of treating a value not declared as a case as a valid value of an enum. No more undefined behavior here! This bit can go in separately from all the work on exhaustive/frozen enums, which is still being discussed and will come later. rdar://problem/20420436
1 parent d782f46 commit 36eae9d

File tree

6 files changed

+314
-21
lines changed

6 files changed

+314
-21
lines changed

lib/SILGen/SILGenPattern.cpp

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,23 +1759,30 @@ static void generateEnumCaseBlocks(
17591759

17601760
assert(caseBBs.size() == caseInfos.size());
17611761

1762-
// We always need a default block if the enum is resilient.
1763-
// If the enum is @_fixed_layout, we only need one if the
1764-
// switch is not exhaustive.
1765-
bool exhaustive = false;
1766-
1767-
if (!enumDecl->isResilient(SGF.SGM.M.getSwiftModule(),
1768-
SGF.F.getResilienceExpansion())) {
1769-
exhaustive = true;
1762+
// Check to see if the enum may have values beyond the cases we can see
1763+
// at compile-time. This includes future cases (for resilient enums) and
1764+
// random values crammed into C enums.
1765+
//
1766+
// Note: This relies on the fact that there are no "non-resilient" enums that
1767+
// are still non-exhaustive, except for @objc enums.
1768+
bool canAssumeExhaustive = !enumDecl->isObjC();
1769+
if (canAssumeExhaustive) {
1770+
canAssumeExhaustive =
1771+
!enumDecl->isResilient(SGF.SGM.SwiftModule,
1772+
SGF.F.getResilienceExpansion());
1773+
}
1774+
if (canAssumeExhaustive) {
1775+
// Check that Sema didn't let any cases slip through. (This can happen
1776+
// with @_downgrade_exhaustivity_check.)
17701777
for (auto elt : enumDecl->getAllElements()) {
17711778
if (!caseToIndex.count(elt)) {
1772-
exhaustive = false;
1779+
canAssumeExhaustive = false;
17731780
break;
17741781
}
17751782
}
17761783
}
17771784

1778-
if (!exhaustive)
1785+
if (!canAssumeExhaustive)
17791786
defaultBB = SGF.createBasicBlock(curBB);
17801787
}
17811788

@@ -2542,17 +2549,21 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
25422549
DEBUG(llvm::dbgs() << "emitting switch stmt\n";
25432550
S->print(llvm::dbgs());
25442551
llvm::dbgs() << '\n');
2545-
auto failure = [&](SILLocation location) {
2546-
// If we fail to match anything, we can just emit unreachable.
2547-
// This will be a dataflow error if we can reach here.
2548-
B.createUnreachable(S);
2552+
auto failure = [this](SILLocation location) {
2553+
// If we fail to match anything, we trap. This can happen with a switch
2554+
// over an @objc enum, which may contain any value of its underlying type.
2555+
// FIXME: Even if we can't say what the invalid value was, we should at
2556+
// least mention that this was because of a non-exhaustive enum.
2557+
B.createBuiltinTrap(location);
2558+
B.createUnreachable(location);
25492559
};
25502560

25512561
// If the subject expression is uninhabited, we're already dead.
25522562
// Emit an unreachable in place of the switch statement.
25532563
if (S->getSubjectExpr()->getType()->isStructurallyUninhabited()) {
25542564
emitIgnoredExpr(S->getSubjectExpr());
2555-
return failure(SILLocation(S));
2565+
B.createUnreachable(S);
2566+
return;
25562567
}
25572568

25582569
auto completionHandler = [&](PatternMatchEmission &emission,

test/IRGen/objc_enum_multi_file.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ func useFoo(_ x: Foo) -> Int32 {
1616
// CHECK-DAG: i32 0, label %[[CASE_A:.+]]
1717
// CHECK: ]
1818

19-
// CHECK: <label>:[[DEFAULT]]
20-
// CHECK-NEXT: unreachable
21-
2219
switch x {
2320
// CHECK: <label>:[[CASE_B]]
2421
// CHECK-NEXT: br label %[[FINAL:.+]]
@@ -36,6 +33,10 @@ func useFoo(_ x: Foo) -> Int32 {
3633
return 10
3734
}
3835

36+
// CHECK: <label>:[[DEFAULT]]
37+
// CHECK-NEXT: call void @llvm.trap()
38+
// CHECK-NEXT: unreachable
39+
3940
// CHECK: <label>:[[FINAL]]
4041
// CHECK: %[[RETVAL:.+]] = phi i32 [ 10, %[[CASE_A]] ], [ 15, %[[CASE_C]] ], [ 11, %[[CASE_B]] ]
4142
// CHECK: ret i32 %[[RETVAL]]
@@ -49,9 +50,6 @@ func useBar(_ x: Bar) -> Int32 {
4950
// CHECK-DAG: i32 5, label %[[CASE_A:.+]]
5051
// CHECK: ]
5152

52-
// CHECK: <label>:[[DEFAULT]]
53-
// CHECK-NEXT: unreachable
54-
5553
switch x {
5654
// CHECK: <label>:[[CASE_B]]
5755
// CHECK-NEXT: br label %[[FINAL:.+]]
@@ -69,6 +67,10 @@ func useBar(_ x: Bar) -> Int32 {
6967
return 10
7068
}
7169

70+
// CHECK: <label>:[[DEFAULT]]
71+
// CHECK-NEXT: call void @llvm.trap()
72+
// CHECK-NEXT: unreachable
73+
7274
// CHECK: <label>:[[FINAL]]
7375
// CHECK: %[[RETVAL:.+]] = phi i32 [ 10, %[[CASE_A]] ], [ 15, %[[CASE_C]] ], [ 11, %[[CASE_B]] ]
7476
// CHECK: ret i32 %[[RETVAL]]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
enum NonExhaustiveEnum {
2+
NonExhaustiveEnumA = 0,
3+
NonExhaustiveEnumB = 1,
4+
NonExhaustiveEnumC = 2,
5+
} __attribute__((enum_extensibility(open)));
6+
7+
enum NonExhaustiveEnum getExpectedValue(void) {
8+
return NonExhaustiveEnumB;
9+
}
10+
enum NonExhaustiveEnum getUnexpectedValue(void) {
11+
return (enum NonExhaustiveEnum)3;
12+
}
13+
14+
enum LyingExhaustiveEnum {
15+
LyingExhaustiveEnumA = 0,
16+
LyingExhaustiveEnumB = 1,
17+
LyingExhaustiveEnumC = 2,
18+
} __attribute__((enum_extensibility(closed)));
19+
20+
enum LyingExhaustiveEnum getExpectedLiarValue(void) {
21+
return LyingExhaustiveEnumB;
22+
}
23+
enum LyingExhaustiveEnum getUnexpectedLiarValue(void) {
24+
return (enum LyingExhaustiveEnum)3;
25+
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -Onone -o %t/main -import-objc-header %S/Inputs/enum-nonexhaustivity.h -Xfrontend -disable-objc-attr-requires-foundation-module
3+
// RUN: %target-run %t/main
4+
// RUN: %target-build-swift %s -O -o %t/main -import-objc-header %S/Inputs/enum-nonexhaustivity.h -Xfrontend -disable-objc-attr-requires-foundation-module
5+
// RUN: %target-run %t/main
6+
// RUN: %target-build-swift %s -Ounchecked -o %t/main -import-objc-header %S/Inputs/enum-nonexhaustivity.h -Xfrontend -disable-objc-attr-requires-foundation-module
7+
// RUN: %target-run %t/main
8+
// REQUIRES: executable_test
9+
10+
import StdlibUnittest
11+
12+
var EnumTestSuite = TestSuite("Enums")
13+
14+
EnumTestSuite.test("PlainOldSwitch/NonExhaustive") {
15+
var gotCorrectValue = false
16+
switch getExpectedValue() {
17+
case .A, .C:
18+
expectUnreachable()
19+
case .B:
20+
gotCorrectValue = true
21+
}
22+
expectTrue(gotCorrectValue)
23+
}
24+
25+
EnumTestSuite.test("TrapOnUnexpected/NonExhaustive") {
26+
expectCrashLater()
27+
switch getUnexpectedValue() {
28+
case .A, .C:
29+
expectUnreachable()
30+
case .B:
31+
expectUnreachable()
32+
}
33+
expectUnreachable()
34+
}
35+
36+
EnumTestSuite.test("TrapOnUnexpectedNested/NonExhaustive") {
37+
expectCrashLater()
38+
switch (getExpectedValue(), getUnexpectedValue()) {
39+
case (.A, .A), (.C, .C):
40+
expectUnreachable()
41+
case (_, .B):
42+
expectUnreachable()
43+
case (_, .A), (_, .C):
44+
expectUnreachable()
45+
}
46+
expectUnreachable()
47+
}
48+
49+
EnumTestSuite.test("TrapOnUnexpectedNested2/NonExhaustive") {
50+
expectCrashLater()
51+
switch (getUnexpectedValue(), getExpectedValue()) {
52+
case (.A, .A), (.C, .C):
53+
expectUnreachable()
54+
case (.B, _):
55+
expectUnreachable()
56+
case (.A, _), (.C, _):
57+
expectUnreachable()
58+
}
59+
expectUnreachable()
60+
}
61+
62+
EnumTestSuite.test("UnexpectedOkayNested/NonExhaustive") {
63+
var gotCorrectValue = false
64+
switch (getExpectedValue(), getUnexpectedValue()) {
65+
case (.A, .A), (.C, .C):
66+
expectUnreachable()
67+
case (.B, _):
68+
gotCorrectValue = true
69+
case (.A, _), (.C, _):
70+
expectUnreachable()
71+
}
72+
expectTrue(gotCorrectValue)
73+
}
74+
75+
EnumTestSuite.test("UnexpectedOkayNested2/NonExhaustive") {
76+
var gotCorrectValue = false
77+
switch (getUnexpectedValue(), getExpectedValue()) {
78+
case (.A, .A), (.C, .C):
79+
expectUnreachable()
80+
case (_, .B):
81+
gotCorrectValue = true
82+
case (_, .A), (_, .C):
83+
expectUnreachable()
84+
}
85+
expectTrue(gotCorrectValue)
86+
}
87+
88+
89+
EnumTestSuite.test("PlainOldSwitch/LyingExhaustive") {
90+
var gotCorrectValue = false
91+
switch getExpectedLiarValue() {
92+
case .A, .C:
93+
expectUnreachable()
94+
case .B:
95+
gotCorrectValue = true
96+
}
97+
expectTrue(gotCorrectValue)
98+
}
99+
100+
EnumTestSuite.test("TrapOnUnexpected/LyingExhaustive") {
101+
expectCrashLater()
102+
switch getUnexpectedLiarValue() {
103+
case .A, .C:
104+
expectUnreachable()
105+
case .B:
106+
expectUnreachable()
107+
}
108+
expectUnreachable()
109+
}
110+
111+
EnumTestSuite.test("TrapOnUnexpectedNested/LyingExhaustive") {
112+
expectCrashLater()
113+
switch (getExpectedLiarValue(), getUnexpectedLiarValue()) {
114+
case (.A, .A), (.C, .C):
115+
expectUnreachable()
116+
case (_, .B):
117+
expectUnreachable()
118+
case (_, .A), (_, .C):
119+
expectUnreachable()
120+
}
121+
expectUnreachable()
122+
}
123+
124+
EnumTestSuite.test("TrapOnUnexpectedNested2/LyingExhaustive") {
125+
expectCrashLater()
126+
switch (getUnexpectedLiarValue(), getExpectedLiarValue()) {
127+
case (.A, .A), (.C, .C):
128+
expectUnreachable()
129+
case (.B, _):
130+
expectUnreachable()
131+
case (.A, _), (.C, _):
132+
expectUnreachable()
133+
}
134+
expectUnreachable()
135+
}
136+
137+
EnumTestSuite.test("UnexpectedOkayNested/LyingExhaustive") {
138+
var gotCorrectValue = false
139+
switch (getExpectedLiarValue(), getUnexpectedLiarValue()) {
140+
case (.A, .A), (.C, .C):
141+
expectUnreachable()
142+
case (.B, _):
143+
gotCorrectValue = true
144+
case (.A, _), (.C, _):
145+
expectUnreachable()
146+
}
147+
expectTrue(gotCorrectValue)
148+
}
149+
150+
EnumTestSuite.test("UnexpectedOkayNested2/LyingExhaustive") {
151+
var gotCorrectValue = false
152+
switch (getUnexpectedLiarValue(), getExpectedLiarValue()) {
153+
case (.A, .A), (.C, .C):
154+
expectUnreachable()
155+
case (_, .B):
156+
gotCorrectValue = true
157+
case (_, .A), (_, .C):
158+
expectUnreachable()
159+
}
160+
expectTrue(gotCorrectValue)
161+
}
162+
163+
164+
@objc enum SwiftEnum : Int32 {
165+
case A, B, C
166+
167+
@inline(never) static func getExpectedValue() -> SwiftEnum {
168+
return .B
169+
}
170+
@inline(never) static func getUnexpectedValue() -> SwiftEnum {
171+
return unsafeBitCast(42 as Int32, to: SwiftEnum.self)
172+
}
173+
}
174+
175+
EnumTestSuite.test("PlainOldSwitch/SwiftExhaustive") {
176+
var gotCorrectValue = false
177+
switch SwiftEnum.getExpectedValue() {
178+
case .A, .C:
179+
expectUnreachable()
180+
case .B:
181+
gotCorrectValue = true
182+
}
183+
expectTrue(gotCorrectValue)
184+
}
185+
186+
EnumTestSuite.test("TrapOnUnexpected/SwiftExhaustive") {
187+
expectCrashLater()
188+
switch SwiftEnum.getUnexpectedValue() {
189+
case .A, .C:
190+
expectUnreachable()
191+
case .B:
192+
expectUnreachable()
193+
}
194+
expectUnreachable()
195+
}
196+
197+
EnumTestSuite.test("TrapOnUnexpectedNested/SwiftExhaustive") {
198+
expectCrashLater()
199+
switch (SwiftEnum.getExpectedValue(), SwiftEnum.getUnexpectedValue()) {
200+
case (.A, .A), (.C, .C):
201+
expectUnreachable()
202+
case (_, .B):
203+
expectUnreachable()
204+
case (_, .A), (_, .C):
205+
expectUnreachable()
206+
}
207+
expectUnreachable()
208+
}
209+
210+
EnumTestSuite.test("TrapOnUnexpectedNested2/SwiftExhaustive") {
211+
expectCrashLater()
212+
switch (SwiftEnum.getUnexpectedValue(), SwiftEnum.getExpectedValue()) {
213+
case (.A, .A), (.C, .C):
214+
expectUnreachable()
215+
case (.B, _):
216+
expectUnreachable()
217+
case (.A, _), (.C, _):
218+
expectUnreachable()
219+
}
220+
expectUnreachable()
221+
}
222+
223+
EnumTestSuite.test("UnexpectedOkayNested/SwiftExhaustive") {
224+
var gotCorrectValue = false
225+
switch (SwiftEnum.getExpectedValue(), SwiftEnum.getUnexpectedValue()) {
226+
case (.A, .A), (.C, .C):
227+
expectUnreachable()
228+
case (.B, _):
229+
gotCorrectValue = true
230+
case (.A, _), (.C, _):
231+
expectUnreachable()
232+
}
233+
expectTrue(gotCorrectValue)
234+
}
235+
236+
EnumTestSuite.test("UnexpectedOkayNested2/SwiftExhaustive") {
237+
var gotCorrectValue = false
238+
switch (SwiftEnum.getUnexpectedValue(), SwiftEnum.getExpectedValue()) {
239+
case (.A, .A), (.C, .C):
240+
expectUnreachable()
241+
case (_, .B):
242+
gotCorrectValue = true
243+
case (_, .A), (_, .C):
244+
expectUnreachable()
245+
}
246+
expectTrue(gotCorrectValue)
247+
}
248+
249+
250+
runAllTests()

0 commit comments

Comments
 (0)