Skip to content

Commit 92d9444

Browse files
break in a statement expression in a switch condition [GH #434]
1 parent b85cc47 commit 92d9444

8 files changed

Lines changed: 114 additions & 6 deletions

File tree

‎src/interpret.c‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9704,6 +9704,7 @@ transfer, or perform their cleanup and let it propagate further.
97049704
(see the handling of ips->switch_entry above). */
97059705
ips->switch_entry = case_stmt;
97069706
push_stmt_work(ips, stmt->variant.switch_stmt.body_statement);
9707+
item->phase = iwp_2nd_resume;
97079708
pop_when_done = FALSE;
97089709
goto done;
97099710
} /* if */
@@ -9928,10 +9929,11 @@ transfer, or perform their cleanup and let it propagate further.
99289929
do_constexpr_fail(result);
99299930
} /* if */
99309931
} /* if */
9931-
if (!result && !ips->failed &&
9932+
if (!result && !ips->failed && item->phase == iwp_2nd_resume &&
99329933
ips->transfer.kind == ctk_switch_break) {
99339934
/* Break out of the switch statement (which completes the execution of
9934-
the break statement). */
9935+
the break statement). A break statement in the condition (in a GNU
9936+
statement expression) applies to an enclosing statement instead. */
99359937
ips->transfer.kind = ctk_none;
99369938
result = TRUE;
99379939
} /* if */
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[GH #434]
2+
GNU statement expressions and switch statements
3+
4+
In GNU modes, a break statement inside a statement expression that appears as
5+
the value a switch statement switches on was bound to that switch statement
6+
instead of (as with GCC and Clang) to the enclosing loop or switch statement.
7+
Similarly, a case or default label in such a statement expression was
8+
associated with that switch statement. For example, with --g++ --c++20:
9+
10+
int f(int y, bool c) {
11+
switch (y) {
12+
case 0:
13+
switch (({ if (c) break; 1; })) {
14+
case 1:
15+
return 10;
16+
}
17+
return 1;
18+
}
19+
return 2;
20+
}
21+
22+
f(0, true) returned 1 instead of 2. Constant evaluation had the same
23+
problem, and also let a switch statement complete a break statement executed
24+
while computing the value it switches on. This is now fixed. Such a break
25+
statement is now diagnosed if there is no enclosing loop or switch
26+
statement, and such a case or default label now applies to an enclosing
27+
switch statement (and is diagnosed as a transfer of control into a statement
28+
expression).

‎src/statements.c‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,6 +2995,7 @@ statement is the top block of a GNU statement expression ({ ... }).
29952995
} /* if */
29962996
sssep->switch_has_dependent_case
29972997
= FALSE;
2998+
sssep->in_switch_condition = FALSE;
29982999
sssep->contains_user_label = FALSE;
29993000
sssep->contains_active_switch_case = FALSE;
30003001
sssep->record_declared_entities = FALSE;
@@ -3538,8 +3539,9 @@ static a_struct_stmt_stack_entry_ptr find_enclosing_struct_stmt(
35383539
a_boolean find_loop)
35393540
/*
35403541
Search the structured statement stack from the current entry outward,
3541-
looking for a switch statement (if find_switch is TRUE) or a loop
3542-
statement (while, do, or for; if find_loop is TRUE). Return a pointer
3542+
looking for a switch statement whose condition is not being scanned (if
3543+
find_switch is TRUE) or a loop statement (while, do, or for; if find_loop is
3544+
TRUE). Return a pointer
35433545
to the first structured statement stack entry found, or NULL if none
35443546
was found.
35453547
*/
@@ -3552,7 +3554,7 @@ was found.
35523554
the compound statement that defines the function. */
35533555
while (sssep != &struct_stmt_stack[0]) {
35543556
kind = sssep->kind;
3555-
if (find_switch && kind == ssk_switch) {
3557+
if (find_switch && kind == ssk_switch && !sssep->in_switch_condition) {
35563558
goto found;
35573559
} else if (find_loop &&
35583560
(kind == ssk_while || kind == ssk_do || kind == ssk_for ||
@@ -4589,7 +4591,9 @@ See also 3.6.4.2.
45894591
(void)required_token(tok_lparen, ec_exp_lparen);
45904592
add_stop_token(tok_rparen);
45914593
/* Scan the "condition", which in C++ may be a condition declaration. */
4594+
struct_stmt_stack[depth_stmt_stack].in_switch_condition = TRUE;
45924595
scan_condition(sp, &is_condition_decl);
4596+
struct_stmt_stack[depth_stmt_stack].in_switch_condition = FALSE;
45934597
if (!is_error_node(sp->expr)) {
45944598
/* Issue a remark if the selector is constant. */
45954599
if (is_constant_node(skip_parens(sp->expr))) {

‎src/statements.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,12 @@ typedef struct a_struct_stmt_stack_entry {
331331
/* Set only in entries with kind == ssk_switch.
332332
Indicates that at least one case contains a
333333
template-dependent constant. */
334+
a_bit_field in_switch_condition:1;
335+
/* Set only in entries with kind == ssk_switch. TRUE
336+
while the condition of the switch statement is
337+
being scanned. A break statement or a case label in
338+
the condition (in a GNU statement expression) does
339+
not apply to that switch statement. */
334340
a_bit_field contains_user_label:1;
335341
/* TRUE if the structured statement contains a user-
336342
declared label (used to avoid spurious reachability
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eccp -DTEST_NUMBER=1 --g++ --c++20 Test_name.c
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
./a.out

‎tests/tests/changes/gh-434.sft.cpp‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//type:rp
2+
//remark:A break in a GNU statement expression in a switch condition
3+
//options_all:--g++ --c++20
4+
5+
constexpr int f(int y, bool c) {
6+
switch (y) {
7+
case 0:
8+
switch (({ if (c) break; 1; })) {
9+
case 1:
10+
return 10;
11+
}
12+
return 1;
13+
}
14+
return 2;
15+
}
16+
static_assert(f(0, false) == 10);
17+
static_assert(f(0, true) == 2);
18+
static_assert(f(5, true) == 2);
19+
20+
constexpr int g(bool c) {
21+
int r = 0;
22+
for (int i = 0; i < 3; ++i) {
23+
switch (({ if (c) break; i; })) {
24+
default:
25+
r += 1;
26+
}
27+
r += 100;
28+
}
29+
return r;
30+
}
31+
static_assert(g(false) == 303);
32+
static_assert(g(true) == 0);
33+
34+
constexpr int h(int c) {
35+
int r = 0;
36+
do {
37+
switch (({ int v = 0;
38+
switch (c) {
39+
case 1:
40+
v = 5;
41+
break;
42+
}
43+
v; })) {
44+
case 5:
45+
r = 50;
46+
break;
47+
default:
48+
r = 1;
49+
}
50+
r += 2;
51+
} while (false);
52+
return r;
53+
}
54+
static_assert(h(1) == 52);
55+
static_assert(h(0) == 3);
56+
57+
int main() {
58+
volatile bool t = true, f0 = false;
59+
volatile int zero = 0, one = 1, five = 5;
60+
return !(f(zero, f0) == 10 && f(zero, t) == 2 && f(five, t) == 2 &&
61+
g(f0) == 303 && g(t) == 0 && h(one) == 52 && h(zero) == 3);
62+
}

‎tests/tests/imported/clang/c/Parser/.bad-control.rto/default.1.1.txt‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ fe_only -DTEST_NUMBER=1 --clang -e 200 --no_wrap_diagnostics --clang_version 999
77
continue; /* expected-error {{'continue' statement not in loop statement}} */
88
^
99

10-
2 errors detected in the compilation of "Test_name.c".
10+
"Test_name.c", line 14: error: a break statement may only be used within a loop or switch
11+
switch(({ if (first) { first = 0; break; } 1; })) { // expected-error {{'break' statement not in loop or switch statement}}
12+
^
13+
14+
3 errors detected in the compilation of "Test_name.c".

0 commit comments

Comments
 (0)