Skip to content

Commit ea8f801

Browse files
committed
[Sema] Prevent fallthrough jumping out of if expressions
Impose the same limit we impose on other forms of control flow statements (e.g `break`, `continue`, `return`), where it cannot transfer control out of the expression. This fixes a crash where we'd fail to find a fallthrough nested in an `if` expression. It is technically source breaking, as we would have allowed the case where the `if` expression is a directly nested result of an outer `switch` expression, but I would be very surprised if anyone is relying on that. rdar://133845101
1 parent 769805e commit ea8f801

File tree

5 files changed

+151
-9
lines changed

5 files changed

+151
-9
lines changed

lib/Sema/TypeCheckStmt.cpp

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,11 +3022,14 @@ namespace {
30223022
/// An ASTWalker that searches for any break/continue/return statements that
30233023
/// jump out of the context the walker starts at.
30243024
class JumpOutOfContextFinder : public ASTWalker {
3025+
const Stmt *ParentStmt;
30253026
TinyPtrVector<Stmt *> &Jumps;
30263027
SmallPtrSet<Stmt *, 4> ParentLabeledStmts;
3028+
SmallPtrSet<CaseStmt *, 4> ParentCaseStmts;
30273029

30283030
public:
3029-
JumpOutOfContextFinder(TinyPtrVector<Stmt *> &jumps) : Jumps(jumps) {}
3031+
JumpOutOfContextFinder(const Stmt *parentStmt, TinyPtrVector<Stmt *> &jumps)
3032+
: ParentStmt(parentStmt), Jumps(jumps) {}
30303033

30313034
MacroWalking getMacroWalkingBehavior() const override {
30323035
return MacroWalking::Expansion;
@@ -3035,9 +3038,11 @@ class JumpOutOfContextFinder : public ASTWalker {
30353038
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
30363039
if (auto *LS = dyn_cast<LabeledStmt>(S))
30373040
ParentLabeledStmts.insert(LS);
3041+
if (auto *CS = dyn_cast<CaseStmt>(S))
3042+
ParentCaseStmts.insert(CS);
30383043

3039-
// Cannot 'break', 'continue', or 'return' out of the statement. A jump to
3040-
// a statement within a branch however is fine.
3044+
// Cannot 'break', 'continue', 'fallthrough' or 'return' out of the
3045+
// statement. A jump to a statement within a branch however is fine.
30413046
if (auto *BS = dyn_cast<BreakStmt>(S)) {
30423047
if (!ParentLabeledStmts.contains(BS->getTarget()))
30433048
Jumps.push_back(BS);
@@ -3046,6 +3051,17 @@ class JumpOutOfContextFinder : public ASTWalker {
30463051
if (!ParentLabeledStmts.contains(CS->getTarget()))
30473052
Jumps.push_back(CS);
30483053
}
3054+
if (auto *FS = dyn_cast<FallthroughStmt>(S)) {
3055+
// The source must either be in the parent statement, or must be a
3056+
// nested CaseStmt we've seen. If there's no source, we will have
3057+
// already diagnosed.
3058+
if (auto *source = FS->getFallthroughSource()) {
3059+
if (source->getParentStmt() != ParentStmt &&
3060+
!ParentCaseStmts.contains(source)) {
3061+
Jumps.push_back(FS);
3062+
}
3063+
}
3064+
}
30493065
if (isa<ReturnStmt>(S) || isa<FailStmt>(S))
30503066
Jumps.push_back(S);
30513067

@@ -3057,6 +3073,11 @@ class JumpOutOfContextFinder : public ASTWalker {
30573073
assert(removed);
30583074
(void)removed;
30593075
}
3076+
if (auto *CS = dyn_cast<CaseStmt>(S)) {
3077+
auto removed = ParentCaseStmts.erase(CS);
3078+
assert(removed);
3079+
(void)removed;
3080+
}
30603081
return Action::Continue(S);
30613082
}
30623083

@@ -3072,10 +3093,11 @@ class JumpOutOfContextFinder : public ASTWalker {
30723093
} // end anonymous namespace
30733094

30743095
IsSingleValueStmtResult
3075-
areBranchesValidForSingleValueStmt(ASTContext &ctx, ArrayRef<Stmt *> branches) {
3096+
areBranchesValidForSingleValueStmt(ASTContext &ctx, const Stmt *parentStmt,
3097+
ArrayRef<Stmt *> branches) {
30763098
TinyPtrVector<Stmt *> invalidJumps;
30773099
TinyPtrVector<Stmt *> unterminatedBranches;
3078-
JumpOutOfContextFinder jumpFinder(invalidJumps);
3100+
JumpOutOfContextFinder jumpFinder(parentStmt, invalidJumps);
30793101

30803102
// Must have a single expression brace, and non-single-expression branches
30813103
// must end with a throw.
@@ -3145,17 +3167,19 @@ IsSingleValueStmtRequest::evaluate(Evaluator &eval, const Stmt *S,
31453167
return IsSingleValueStmtResult::nonExhaustiveIf();
31463168

31473169
SmallVector<Stmt *, 4> scratch;
3148-
return areBranchesValidForSingleValueStmt(ctx, IS->getBranches(scratch));
3170+
return areBranchesValidForSingleValueStmt(ctx, IS,
3171+
IS->getBranches(scratch));
31493172
}
31503173
if (auto *SS = dyn_cast<SwitchStmt>(S)) {
31513174
SmallVector<Stmt *, 4> scratch;
3152-
return areBranchesValidForSingleValueStmt(ctx, SS->getBranches(scratch));
3175+
return areBranchesValidForSingleValueStmt(ctx, SS,
3176+
SS->getBranches(scratch));
31533177
}
31543178
if (auto *DS = dyn_cast<DoStmt>(S)) {
31553179
if (!ctx.LangOpts.hasFeature(Feature::DoExpressions))
31563180
return IsSingleValueStmtResult::unhandledStmt();
31573181

3158-
return areBranchesValidForSingleValueStmt(ctx, DS->getBody());
3182+
return areBranchesValidForSingleValueStmt(ctx, DS, DS->getBody());
31593183
}
31603184
if (auto *DCS = dyn_cast<DoCatchStmt>(S)) {
31613185
if (!ctx.LangOpts.hasFeature(Feature::DoExpressions))
@@ -3165,7 +3189,8 @@ IsSingleValueStmtRequest::evaluate(Evaluator &eval, const Stmt *S,
31653189
return IsSingleValueStmtResult::nonExhaustiveDoCatch();
31663190

31673191
SmallVector<Stmt *, 4> scratch;
3168-
return areBranchesValidForSingleValueStmt(ctx, DCS->getBranches(scratch));
3192+
return areBranchesValidForSingleValueStmt(ctx, DCS,
3193+
DCS->getBranches(scratch));
31693194
}
31703195
return IsSingleValueStmtResult::unhandledStmt();
31713196
}

test/SILGen/if_expr.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,3 +659,36 @@ struct LazyProp {
659659
0
660660
}
661661
}
662+
663+
func testNestedFallthrough1() throws -> Int {
664+
let x = if .random() {
665+
switch Bool.random() {
666+
case true:
667+
fallthrough
668+
case false:
669+
break
670+
}
671+
throw Err()
672+
} else {
673+
0
674+
}
675+
return x
676+
}
677+
678+
func testNestedFallthrough2() throws -> Int {
679+
let x = if .random() {
680+
switch Bool.random() {
681+
case true:
682+
if .random() {
683+
fallthrough
684+
}
685+
break
686+
case false:
687+
break
688+
}
689+
throw Err()
690+
} else {
691+
0
692+
}
693+
return x
694+
}

test/SILGen/switch_expr.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,23 @@ func testFallthrough2() -> Int {
197197
// CHECK: dealloc_stack [[RESULT]] : $*Int
198198
// CHECK: return [[VAL_RESULT:[%0-9]+]] : $Int
199199

200+
func testFallthrough3() throws -> Int {
201+
switch Bool.random() {
202+
case true:
203+
switch Bool.random() {
204+
case true:
205+
if .random() {
206+
fallthrough
207+
}
208+
throw Err()
209+
case false:
210+
1
211+
}
212+
case false:
213+
0
214+
}
215+
}
216+
200217
func testClosure() throws -> Int {
201218
let fn = {
202219
switch Bool.random() {

test/expr/unary/if_expr.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,63 @@ func return4() throws -> Int {
987987
return i
988988
}
989989

990+
// https://github.com/swiftlang/swift/issues/75880
991+
func fallthrough1() throws {
992+
switch Bool.random() {
993+
case true:
994+
let _ = if .random() {
995+
if .random () {
996+
fallthrough // expected-error {{cannot use 'fallthrough' to transfer control out of 'if' expression}}
997+
}
998+
throw Err()
999+
} else {
1000+
0
1001+
}
1002+
case false:
1003+
break
1004+
}
1005+
}
1006+
1007+
func fallthrough2() throws -> Int {
1008+
let x = switch Bool.random() {
1009+
case true:
1010+
if .random() {
1011+
if .random () {
1012+
fallthrough // expected-error {{cannot use 'fallthrough' to transfer control out of 'if' expression}}
1013+
}
1014+
throw Err()
1015+
} else {
1016+
0
1017+
}
1018+
case false:
1019+
1
1020+
}
1021+
return x
1022+
}
1023+
1024+
func fallthrough3() -> Int {
1025+
let x = switch Bool.random() {
1026+
case true:
1027+
if .random() {
1028+
fallthrough // expected-error {{cannot use 'fallthrough' to transfer control out of 'if' expression}}
1029+
} else {
1030+
0
1031+
}
1032+
case false:
1033+
1
1034+
}
1035+
return x
1036+
}
1037+
1038+
func fallthrough4() -> Int {
1039+
let x = if .random() {
1040+
fallthrough // expected-error {{'fallthrough' is only allowed inside a switch}}
1041+
} else {
1042+
0
1043+
}
1044+
return x
1045+
}
1046+
9901047
// MARK: Effect specifiers
9911048

9921049
func tryIf1() -> Int {

test/expr/unary/switch_expr.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,16 @@ func fallthrough5() -> Int {
12121212
return x
12131213
}
12141214

1215+
func fallthrough6() -> Int {
1216+
let x = switch true {
1217+
case true:
1218+
0
1219+
case false:
1220+
fallthrough // expected-error {{'fallthrough' without a following 'case' or 'default' block}}
1221+
}
1222+
return x
1223+
}
1224+
12151225
func breakAfterNeverExpr() -> String {
12161226
// We avoid turning this into a switch expression because of the 'break'.
12171227
switch Bool.random() {

0 commit comments

Comments
 (0)