Skip to content

Commit 9be6519

Browse files
authored
[SILGen] Show a message when an unexpected enum value is switched on (#15614)
Builds on 36eae9d to emit a message instead of just trapping when a switch over a non-frozen enum ends up not matching anything. If the enum is known to be an @objc enum, the message is unexpected enum case 'MyEnum(rawValue: -42)' and if it's anything else (a Swift enum, a tuple containing enums, whatever), it's a more opaque unexpected enum case while switching on value of type 'MyEnum' The reason for this is to avoid calling String(describing:) or String(reflecting:) an arbitrary value when the enum might conform to CustomStringConvertible and therefore /itself/ have a switch that's going to fall off the end. By handling plain @objc enums (using a bitcast), we've at least covered the 90% case. rdar://problem/37728359
1 parent ce32847 commit 9be6519

File tree

10 files changed

+671
-31
lines changed

10 files changed

+671
-31
lines changed

include/swift/AST/KnownDecls.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ FUNC_DECL(BridgeAnyObjectToAny,
6363
FUNC_DECL(ConvertToAnyHashable, "_convertToAnyHashable")
6464

6565
FUNC_DECL(DiagnoseUnexpectedNilOptional, "_diagnoseUnexpectedNilOptional")
66+
FUNC_DECL(DiagnoseUnexpectedEnumCase, "_diagnoseUnexpectedEnumCase")
67+
FUNC_DECL(DiagnoseUnexpectedEnumCaseValue, "_diagnoseUnexpectedEnumCaseValue")
6668

6769
FUNC_DECL(GetErrorEmbeddedNSError, "_getErrorEmbeddedNSError")
6870

lib/SILGen/SILGenPattern.cpp

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
#include "swift/AST/DiagnosticsSIL.h"
2323
#include "swift/AST/Pattern.h"
2424
#include "swift/AST/SILOptions.h"
25+
#include "swift/AST/SubstitutionMap.h"
2526
#include "swift/AST/Types.h"
27+
#include "swift/Basic/Defer.h"
2628
#include "swift/Basic/ProfileCounter.h"
2729
#include "swift/Basic/STLExtras.h"
2830
#include "swift/SIL/DynamicCasts.h"
@@ -2555,19 +2557,78 @@ void SILGenFunction::usingImplicitVariablesForPattern(Pattern *pattern, CaseStmt
25552557
variableSwapper();
25562558
}
25572559

2560+
static void emitDiagnoseOfUnexpectedEnumCaseValue(SILGenFunction &SGF,
2561+
SILLocation loc,
2562+
ManagedValue value,
2563+
const EnumDecl *enumDecl) {
2564+
ASTContext &ctx = SGF.getASTContext();
2565+
auto diagnoseFailure = ctx.getDiagnoseUnexpectedEnumCaseValue(nullptr);
2566+
if (!diagnoseFailure) {
2567+
SGF.B.createBuiltinTrap(loc);
2568+
return;
2569+
}
2570+
2571+
assert(enumDecl->isObjC());
2572+
assert(enumDecl->hasRawType());
2573+
assert(value.getType().isTrivial(SGF.getModule()));
2574+
2575+
// Get the enum type as an Any.Type value.
2576+
CanType switchedValueSwiftType = value.getType().getSwiftRValueType();
2577+
SILType metatypeType = SGF.getLoweredType(
2578+
CanMetatypeType::get(switchedValueSwiftType,
2579+
MetatypeRepresentation::Thick));
2580+
SILValue metatype = SGF.B.createMetatype(loc, metatypeType);
2581+
2582+
// Bitcast the enum value to its raw type. (This is only safe for @objc
2583+
// enums.)
2584+
SILType loweredRawType = SGF.getLoweredType(enumDecl->getRawType());
2585+
assert(loweredRawType.isTrivial(SGF.getModule()));
2586+
assert(loweredRawType.isObject());
2587+
auto rawValue = SGF.B.createUncheckedTrivialBitCast(loc, value,
2588+
loweredRawType);
2589+
auto materializedRawValue = rawValue.materialize(SGF, loc);
2590+
2591+
Substitution subs[] = {
2592+
{switchedValueSwiftType, /*Conformances*/None},
2593+
{enumDecl->getRawType(), /*Conformances*/None},
2594+
};
2595+
2596+
SGF.emitApplyOfLibraryIntrinsic(loc, diagnoseFailure, subs,
2597+
{ManagedValue::forUnmanaged(metatype),
2598+
materializedRawValue},
2599+
SGFContext());
2600+
}
2601+
2602+
static void emitDiagnoseOfUnexpectedEnumCase(SILGenFunction &SGF,
2603+
SILLocation loc,
2604+
ManagedValue value) {
2605+
ASTContext &ctx = SGF.getASTContext();
2606+
auto diagnoseFailure = ctx.getDiagnoseUnexpectedEnumCase(nullptr);
2607+
if (!diagnoseFailure) {
2608+
SGF.B.createBuiltinTrap(loc);
2609+
return;
2610+
}
2611+
2612+
// Get the switched-upon value's type.
2613+
CanType switchedValueSwiftType = value.getType().getSwiftRValueType();
2614+
SILType metatypeType = SGF.getLoweredType(
2615+
CanMetatypeType::get(switchedValueSwiftType,
2616+
MetatypeRepresentation::Thick));
2617+
ManagedValue metatype = SGF.B.createValueMetatype(loc, metatypeType, value);
2618+
2619+
Substitution sub{switchedValueSwiftType, /*Conformances*/None};
2620+
auto genericArgsMap =
2621+
diagnoseFailure->getGenericSignature()->getSubstitutionMap(sub);
2622+
2623+
SGF.emitApplyOfLibraryIntrinsic(loc, diagnoseFailure, sub,
2624+
metatype,
2625+
SGFContext());
2626+
}
2627+
25582628
void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
25592629
DEBUG(llvm::dbgs() << "emitting switch stmt\n";
25602630
S->print(llvm::dbgs());
25612631
llvm::dbgs() << '\n');
2562-
auto failure = [this](SILLocation location) {
2563-
// If we fail to match anything, we trap. This can happen with a switch
2564-
// over an @objc enum, which may contain any value of its underlying type.
2565-
// FIXME: Even if we can't say what the invalid value was, we should at
2566-
// least mention that this was because of a non-exhaustive enum.
2567-
B.createBuiltinTrap(location);
2568-
B.createUnreachable(location);
2569-
};
2570-
25712632
// If the subject expression is uninhabited, we're already dead.
25722633
// Emit an unreachable in place of the switch statement.
25732634
if (S->getSubjectExpr()->getType()->isStructurallyUninhabited()) {
@@ -2651,10 +2712,7 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
26512712
PatternMatchEmission emission(*this, S, completionHandler);
26522713

26532714
// Add a row for each label of each case.
2654-
//
2655-
// We use std::vector because it supports emplace_back; moving a ClauseRow is
2656-
// expensive.
2657-
std::vector<ClauseRow> clauseRows;
2715+
SmallVector<ClauseRow, 8> clauseRows;
26582716
clauseRows.reserve(S->getRawCases().size());
26592717
bool hasFallthrough = false;
26602718
for (auto caseBlock : S->getCases()) {
@@ -2695,6 +2753,27 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
26952753
ManagedValue subjectMV = emitRValueAsSingleValue(S->getSubjectExpr());
26962754
auto subject = ConsumableManagedValue::forOwned(subjectMV);
26972755

2756+
auto failure = [&](SILLocation location) {
2757+
// If we fail to match anything, we trap. This can happen with a switch
2758+
// over an @objc enum, which may contain any value of its underlying type,
2759+
// or a switch over a non-frozen Swift enum when the user hasn't written a
2760+
// catch-all case.
2761+
SWIFT_DEFER { B.createUnreachable(location); };
2762+
2763+
// Special case: if it's a single @objc enum, we can print the raw value.
2764+
CanType ty = S->getSubjectExpr()->getType()->getCanonicalType();
2765+
if (auto *singleEnumDecl = ty->getEnumOrBoundGenericEnum()) {
2766+
if (singleEnumDecl->isObjC()) {
2767+
emitDiagnoseOfUnexpectedEnumCaseValue(*this, location,
2768+
subject.getFinalManagedValue(),
2769+
singleEnumDecl);
2770+
return;
2771+
}
2772+
}
2773+
emitDiagnoseOfUnexpectedEnumCase(*this, location,
2774+
subject.getFinalManagedValue());
2775+
};
2776+
26982777
// Set up an initial clause matrix.
26992778
ClauseMatrix clauses(clauseRows);
27002779

stdlib/public/core/AssertCommon.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,31 @@ internal func _assertionFailure(
137137
Builtin.int_trap()
138138
}
139139

140+
/// This function should be used only in the implementation of user-level
141+
/// assertions.
142+
///
143+
/// This function should not be inlined because it is cold and inlining just
144+
/// bloats code.
145+
@_versioned // FIXME(sil-serialize-all)
146+
@inline(never)
147+
internal func _assertionFailure(
148+
_ prefix: StaticString, _ message: String,
149+
flags: UInt32
150+
) -> Never {
151+
prefix.withUTF8Buffer {
152+
(prefix) -> Void in
153+
message._withUnsafeBufferPointerToUTF8 {
154+
(messageUTF8) -> Void in
155+
_swift_stdlib_reportFatalError(
156+
prefix.baseAddress!, CInt(prefix.count),
157+
messageUTF8.baseAddress!, CInt(messageUTF8.count),
158+
flags)
159+
}
160+
}
161+
162+
Builtin.int_trap()
163+
}
164+
140165
/// This function should be used only in the implementation of stdlib
141166
/// assertions.
142167
///
@@ -238,3 +263,37 @@ func _undefined<T>(
238263
) -> T {
239264
_assertionFailure("Fatal error", message(), file: file, line: line, flags: 0)
240265
}
266+
267+
/// Called when falling off the end of a switch and the type can be represented
268+
/// as a raw value.
269+
///
270+
/// This function should not be inlined because it is cold and inlining just
271+
/// bloats code. It doesn't take a source location because it's most important
272+
/// in release builds anyway (old apps that are run on new OSs).
273+
@inline(never)
274+
@_versioned // COMPILER_INTRINSIC
275+
internal func _diagnoseUnexpectedEnumCaseValue<SwitchedValue, RawValue>(
276+
type: SwitchedValue.Type,
277+
rawValue: RawValue
278+
) -> Never {
279+
_assertionFailure("Fatal error",
280+
"unexpected enum case '\(type)(rawValue: \(rawValue))'",
281+
flags: _fatalErrorFlags())
282+
}
283+
284+
/// Called when falling off the end of a switch and the value is not safe to
285+
/// print.
286+
///
287+
/// This function should not be inlined because it is cold and inlining just
288+
/// bloats code. It doesn't take a source location because it's most important
289+
/// in release builds anyway (old apps that are run on new OSs).
290+
@inline(never)
291+
@_versioned // COMPILER_INTRINSIC
292+
internal func _diagnoseUnexpectedEnumCase<SwitchedValue>(
293+
type: SwitchedValue.Type
294+
) -> Never {
295+
_assertionFailure(
296+
"Fatal error",
297+
"unexpected enum case while switching on value of type '\(type)'",
298+
flags: _fatalErrorFlags())
299+
}

test/IRGen/objc_enum_multi_file.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func useFoo(_ x: Foo) -> Int32 {
3434
}
3535

3636
// CHECK: <label>:[[DEFAULT]]
37-
// CHECK-NEXT: call void @llvm.trap()
37+
// CHECK: call swiftcc void @"$Ss32_diagnoseUnexpectedEnumCaseValue{{.+}}"(%swift.type* @"$S{{.+}}3FooON", %swift.opaque* noalias nocapture %{{.+}}, %swift.type* @"$Ss5Int32VN")
3838
// CHECK-NEXT: unreachable
3939

4040
// CHECK: <label>:[[FINAL]]
@@ -68,7 +68,7 @@ func useBar(_ x: Bar) -> Int32 {
6868
}
6969

7070
// CHECK: <label>:[[DEFAULT]]
71-
// CHECK-NEXT: call void @llvm.trap()
71+
// CHECK: call swiftcc void @"$Ss32_diagnoseUnexpectedEnumCaseValue{{.+}}"(%swift.type* @"$S{{.+}}3BarON", %swift.opaque* noalias nocapture %{{.+}}, %swift.type* @"$Ss5Int32VN")
7272
// CHECK-NEXT: unreachable
7373

7474
// CHECK: <label>:[[FINAL]]

0 commit comments

Comments
 (0)