mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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
This commit is contained in:
@@ -1759,23 +1759,30 @@ static void generateEnumCaseBlocks(
|
||||
|
||||
assert(caseBBs.size() == caseInfos.size());
|
||||
|
||||
// We always need a default block if the enum is resilient.
|
||||
// If the enum is @_fixed_layout, we only need one if the
|
||||
// switch is not exhaustive.
|
||||
bool exhaustive = false;
|
||||
|
||||
if (!enumDecl->isResilient(SGF.SGM.M.getSwiftModule(),
|
||||
SGF.F.getResilienceExpansion())) {
|
||||
exhaustive = true;
|
||||
// Check to see if the enum may have values beyond the cases we can see
|
||||
// at compile-time. This includes future cases (for resilient enums) and
|
||||
// random values crammed into C enums.
|
||||
//
|
||||
// Note: This relies on the fact that there are no "non-resilient" enums that
|
||||
// are still non-exhaustive, except for @objc enums.
|
||||
bool canAssumeExhaustive = !enumDecl->isObjC();
|
||||
if (canAssumeExhaustive) {
|
||||
canAssumeExhaustive =
|
||||
!enumDecl->isResilient(SGF.SGM.SwiftModule,
|
||||
SGF.F.getResilienceExpansion());
|
||||
}
|
||||
if (canAssumeExhaustive) {
|
||||
// Check that Sema didn't let any cases slip through. (This can happen
|
||||
// with @_downgrade_exhaustivity_check.)
|
||||
for (auto elt : enumDecl->getAllElements()) {
|
||||
if (!caseToIndex.count(elt)) {
|
||||
exhaustive = false;
|
||||
canAssumeExhaustive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!exhaustive)
|
||||
if (!canAssumeExhaustive)
|
||||
defaultBB = SGF.createBasicBlock(curBB);
|
||||
}
|
||||
|
||||
@@ -2542,17 +2549,21 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
|
||||
DEBUG(llvm::dbgs() << "emitting switch stmt\n";
|
||||
S->print(llvm::dbgs());
|
||||
llvm::dbgs() << '\n');
|
||||
auto failure = [&](SILLocation location) {
|
||||
// If we fail to match anything, we can just emit unreachable.
|
||||
// This will be a dataflow error if we can reach here.
|
||||
B.createUnreachable(S);
|
||||
auto failure = [this](SILLocation location) {
|
||||
// If we fail to match anything, we trap. This can happen with a switch
|
||||
// over an @objc enum, which may contain any value of its underlying type.
|
||||
// FIXME: Even if we can't say what the invalid value was, we should at
|
||||
// least mention that this was because of a non-exhaustive enum.
|
||||
B.createBuiltinTrap(location);
|
||||
B.createUnreachable(location);
|
||||
};
|
||||
|
||||
// If the subject expression is uninhabited, we're already dead.
|
||||
// Emit an unreachable in place of the switch statement.
|
||||
if (S->getSubjectExpr()->getType()->isStructurallyUninhabited()) {
|
||||
emitIgnoredExpr(S->getSubjectExpr());
|
||||
return failure(SILLocation(S));
|
||||
B.createUnreachable(S);
|
||||
return;
|
||||
}
|
||||
|
||||
auto completionHandler = [&](PatternMatchEmission &emission,
|
||||
|
||||
@@ -16,9 +16,6 @@ func useFoo(_ x: Foo) -> Int32 {
|
||||
// CHECK-DAG: i32 0, label %[[CASE_A:.+]]
|
||||
// CHECK: ]
|
||||
|
||||
// CHECK: <label>:[[DEFAULT]]
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
switch x {
|
||||
// CHECK: <label>:[[CASE_B]]
|
||||
// CHECK-NEXT: br label %[[FINAL:.+]]
|
||||
@@ -36,6 +33,10 @@ func useFoo(_ x: Foo) -> Int32 {
|
||||
return 10
|
||||
}
|
||||
|
||||
// CHECK: <label>:[[DEFAULT]]
|
||||
// CHECK-NEXT: call void @llvm.trap()
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
// CHECK: <label>:[[FINAL]]
|
||||
// CHECK: %[[RETVAL:.+]] = phi i32 [ 10, %[[CASE_A]] ], [ 15, %[[CASE_C]] ], [ 11, %[[CASE_B]] ]
|
||||
// CHECK: ret i32 %[[RETVAL]]
|
||||
@@ -49,9 +50,6 @@ func useBar(_ x: Bar) -> Int32 {
|
||||
// CHECK-DAG: i32 5, label %[[CASE_A:.+]]
|
||||
// CHECK: ]
|
||||
|
||||
// CHECK: <label>:[[DEFAULT]]
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
switch x {
|
||||
// CHECK: <label>:[[CASE_B]]
|
||||
// CHECK-NEXT: br label %[[FINAL:.+]]
|
||||
@@ -69,6 +67,10 @@ func useBar(_ x: Bar) -> Int32 {
|
||||
return 10
|
||||
}
|
||||
|
||||
// CHECK: <label>:[[DEFAULT]]
|
||||
// CHECK-NEXT: call void @llvm.trap()
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
// CHECK: <label>:[[FINAL]]
|
||||
// CHECK: %[[RETVAL:.+]] = phi i32 [ 10, %[[CASE_A]] ], [ 15, %[[CASE_C]] ], [ 11, %[[CASE_B]] ]
|
||||
// CHECK: ret i32 %[[RETVAL]]
|
||||
|
||||
25
test/Interpreter/Inputs/enum-nonexhaustivity.h
Normal file
25
test/Interpreter/Inputs/enum-nonexhaustivity.h
Normal file
@@ -0,0 +1,25 @@
|
||||
enum NonExhaustiveEnum {
|
||||
NonExhaustiveEnumA = 0,
|
||||
NonExhaustiveEnumB = 1,
|
||||
NonExhaustiveEnumC = 2,
|
||||
} __attribute__((enum_extensibility(open)));
|
||||
|
||||
enum NonExhaustiveEnum getExpectedValue(void) {
|
||||
return NonExhaustiveEnumB;
|
||||
}
|
||||
enum NonExhaustiveEnum getUnexpectedValue(void) {
|
||||
return (enum NonExhaustiveEnum)3;
|
||||
}
|
||||
|
||||
enum LyingExhaustiveEnum {
|
||||
LyingExhaustiveEnumA = 0,
|
||||
LyingExhaustiveEnumB = 1,
|
||||
LyingExhaustiveEnumC = 2,
|
||||
} __attribute__((enum_extensibility(closed)));
|
||||
|
||||
enum LyingExhaustiveEnum getExpectedLiarValue(void) {
|
||||
return LyingExhaustiveEnumB;
|
||||
}
|
||||
enum LyingExhaustiveEnum getUnexpectedLiarValue(void) {
|
||||
return (enum LyingExhaustiveEnum)3;
|
||||
}
|
||||
250
test/Interpreter/enum-nonexhaustivity.swift
Normal file
250
test/Interpreter/enum-nonexhaustivity.swift
Normal file
@@ -0,0 +1,250 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift %s -Onone -o %t/main -import-objc-header %S/Inputs/enum-nonexhaustivity.h -Xfrontend -disable-objc-attr-requires-foundation-module
|
||||
// RUN: %target-run %t/main
|
||||
// RUN: %target-build-swift %s -O -o %t/main -import-objc-header %S/Inputs/enum-nonexhaustivity.h -Xfrontend -disable-objc-attr-requires-foundation-module
|
||||
// RUN: %target-run %t/main
|
||||
// RUN: %target-build-swift %s -Ounchecked -o %t/main -import-objc-header %S/Inputs/enum-nonexhaustivity.h -Xfrontend -disable-objc-attr-requires-foundation-module
|
||||
// RUN: %target-run %t/main
|
||||
// REQUIRES: executable_test
|
||||
|
||||
import StdlibUnittest
|
||||
|
||||
var EnumTestSuite = TestSuite("Enums")
|
||||
|
||||
EnumTestSuite.test("PlainOldSwitch/NonExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch getExpectedValue() {
|
||||
case .A, .C:
|
||||
expectUnreachable()
|
||||
case .B:
|
||||
gotCorrectValue = true
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpected/NonExhaustive") {
|
||||
expectCrashLater()
|
||||
switch getUnexpectedValue() {
|
||||
case .A, .C:
|
||||
expectUnreachable()
|
||||
case .B:
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpectedNested/NonExhaustive") {
|
||||
expectCrashLater()
|
||||
switch (getExpectedValue(), getUnexpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (_, .B):
|
||||
expectUnreachable()
|
||||
case (_, .A), (_, .C):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpectedNested2/NonExhaustive") {
|
||||
expectCrashLater()
|
||||
switch (getUnexpectedValue(), getExpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (.B, _):
|
||||
expectUnreachable()
|
||||
case (.A, _), (.C, _):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("UnexpectedOkayNested/NonExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch (getExpectedValue(), getUnexpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (.B, _):
|
||||
gotCorrectValue = true
|
||||
case (.A, _), (.C, _):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
EnumTestSuite.test("UnexpectedOkayNested2/NonExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch (getUnexpectedValue(), getExpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (_, .B):
|
||||
gotCorrectValue = true
|
||||
case (_, .A), (_, .C):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
|
||||
EnumTestSuite.test("PlainOldSwitch/LyingExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch getExpectedLiarValue() {
|
||||
case .A, .C:
|
||||
expectUnreachable()
|
||||
case .B:
|
||||
gotCorrectValue = true
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpected/LyingExhaustive") {
|
||||
expectCrashLater()
|
||||
switch getUnexpectedLiarValue() {
|
||||
case .A, .C:
|
||||
expectUnreachable()
|
||||
case .B:
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpectedNested/LyingExhaustive") {
|
||||
expectCrashLater()
|
||||
switch (getExpectedLiarValue(), getUnexpectedLiarValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (_, .B):
|
||||
expectUnreachable()
|
||||
case (_, .A), (_, .C):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpectedNested2/LyingExhaustive") {
|
||||
expectCrashLater()
|
||||
switch (getUnexpectedLiarValue(), getExpectedLiarValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (.B, _):
|
||||
expectUnreachable()
|
||||
case (.A, _), (.C, _):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("UnexpectedOkayNested/LyingExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch (getExpectedLiarValue(), getUnexpectedLiarValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (.B, _):
|
||||
gotCorrectValue = true
|
||||
case (.A, _), (.C, _):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
EnumTestSuite.test("UnexpectedOkayNested2/LyingExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch (getUnexpectedLiarValue(), getExpectedLiarValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (_, .B):
|
||||
gotCorrectValue = true
|
||||
case (_, .A), (_, .C):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
|
||||
@objc enum SwiftEnum : Int32 {
|
||||
case A, B, C
|
||||
|
||||
@inline(never) static func getExpectedValue() -> SwiftEnum {
|
||||
return .B
|
||||
}
|
||||
@inline(never) static func getUnexpectedValue() -> SwiftEnum {
|
||||
return unsafeBitCast(42 as Int32, to: SwiftEnum.self)
|
||||
}
|
||||
}
|
||||
|
||||
EnumTestSuite.test("PlainOldSwitch/SwiftExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch SwiftEnum.getExpectedValue() {
|
||||
case .A, .C:
|
||||
expectUnreachable()
|
||||
case .B:
|
||||
gotCorrectValue = true
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpected/SwiftExhaustive") {
|
||||
expectCrashLater()
|
||||
switch SwiftEnum.getUnexpectedValue() {
|
||||
case .A, .C:
|
||||
expectUnreachable()
|
||||
case .B:
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpectedNested/SwiftExhaustive") {
|
||||
expectCrashLater()
|
||||
switch (SwiftEnum.getExpectedValue(), SwiftEnum.getUnexpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (_, .B):
|
||||
expectUnreachable()
|
||||
case (_, .A), (_, .C):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("TrapOnUnexpectedNested2/SwiftExhaustive") {
|
||||
expectCrashLater()
|
||||
switch (SwiftEnum.getUnexpectedValue(), SwiftEnum.getExpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (.B, _):
|
||||
expectUnreachable()
|
||||
case (.A, _), (.C, _):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectUnreachable()
|
||||
}
|
||||
|
||||
EnumTestSuite.test("UnexpectedOkayNested/SwiftExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch (SwiftEnum.getExpectedValue(), SwiftEnum.getUnexpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (.B, _):
|
||||
gotCorrectValue = true
|
||||
case (.A, _), (.C, _):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
EnumTestSuite.test("UnexpectedOkayNested2/SwiftExhaustive") {
|
||||
var gotCorrectValue = false
|
||||
switch (SwiftEnum.getUnexpectedValue(), SwiftEnum.getExpectedValue()) {
|
||||
case (.A, .A), (.C, .C):
|
||||
expectUnreachable()
|
||||
case (_, .B):
|
||||
gotCorrectValue = true
|
||||
case (_, .A), (_, .C):
|
||||
expectUnreachable()
|
||||
}
|
||||
expectTrue(gotCorrectValue)
|
||||
}
|
||||
|
||||
|
||||
runAllTests()
|
||||
@@ -18,6 +18,7 @@ func testDowngradableOmittedPatternIsUnreachable(pat : Downgradable?) {
|
||||
case .hat:
|
||||
break
|
||||
// CHECK: [[DEFAULT_CASE]]({{%.*}} : @trivial $Downgradable):
|
||||
// CHECK-NEXT: builtin "int_trap"()
|
||||
// CHECK-NEXT: unreachable
|
||||
}
|
||||
|
||||
@@ -33,14 +34,17 @@ func testDowngradableOmittedPatternIsUnreachable(pat : Downgradable?) {
|
||||
case (.hat, .hat):
|
||||
break
|
||||
// CHECK: [[TUPLE_DEFAULT_CASE_2]]({{%.*}} : @trivial $Downgradable):
|
||||
// CHECK-NEXT: builtin "int_trap"()
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
// CHECK: switch_enum [[Y]] : $Downgradable, case #Downgradable.spoon!enumelt: {{bb[0-9]+}}, case #Downgradable.hat!enumelt: {{bb[0-9]+}}, default [[TUPLE_DEFAULT_CASE_3:bb[0-9]+]]
|
||||
|
||||
// CHECK: [[TUPLE_DEFAULT_CASE_3]]({{%.*}} : @trivial $Downgradable):
|
||||
// CHECK-NEXT: builtin "int_trap"()
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
// CHECK: [[TUPLE_DEFAULT_CASE_1]]({{%.*}} : @trivial $Downgradable):
|
||||
// CHECK-NEXT: builtin "int_trap"()
|
||||
// CHECK-NEXT: unreachable
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import resilient_enum
|
||||
// CHECK-NEXT: dealloc_stack [[BOX]]
|
||||
// CHECK-NEXT: br bb6
|
||||
// CHECK: bb5:
|
||||
// CHECK-NEXT: builtin "int_trap"()
|
||||
// CHECK-NEXT: unreachable
|
||||
// CHECK: bb6:
|
||||
// CHECK-NEXT: destroy_addr %0
|
||||
|
||||
Reference in New Issue
Block a user