Skip to content

Commit 2d388c9

Browse files
Implement fix for "if constexpr" incorrectly handling non-brace statements [GH #229]
1 parent 773d310 commit 2d388c9

5 files changed

Lines changed: 333 additions & 39 deletions

File tree

‎src/Changes‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
Version 7.1
22

3+
10/8/26 [GH #229]
4+
Improper handling of "if constexpr" substatements
5+
6+
Previously, when a substatement of an "if constexpr" was discarded during
7+
instantiation and was not enclosed in braces, statements following the
8+
discarded statement could additionally be discarded, e.g.:
9+
10+
template <bool B> constexpr int f() {
11+
return [] {
12+
if constexpr (B) while (false) {}
13+
return 1; // Previously erroneously discarded when B was false.
14+
return 2;
15+
}();
16+
}
17+
18+
This is now fixed.
19+
20+
321
10/7/26 [GH #176]
422
C-generating back end: always_inline attribute and inlined setjmp calls
523

‎src/lexical.c‎

Lines changed: 227 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19599,56 +19599,244 @@ when suppress_warning is TRUE.
1959919599
} /* flush_to_end_of_source */
1960019600

1960119601

19602-
void flush_if_or_else_statement(void)
19602+
static INLINE void flush_statement_header()
1960319603
/*
19604-
Flush tokens of a dependent statement of an "if" or "else" statement.
19605-
The current token is the token after the "if" or "else" keyword.
19604+
Flush the parenthesized header of a statement such as "while", "for",
19605+
"switch" or "catch". The current token is the token after the keyword (and
19606+
after any "co_await"), which should be the opening parenthesis. On return,
19607+
the current token is the token following the closing parenthesis (e.g., the
19608+
start of the controlled substatement).
19609+
19610+
Note: The caller is responsible for having set flushing_tokens to TRUE.
1960619611
*/
1960719612
{
19608-
/* Special handling is needed for nested "if" statements so that
19609-
substatements that are "else if" statements are handled properly. */
19610-
if (curr_token == tok_if) {
19611-
(void)get_token();
19612-
if (curr_token == tok_not && next_token() == tok_consteval) {
19613-
/* Skip the "not" token only if it is followed by "consteval". */
19613+
if (curr_token == tok_lparen) {
19614+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19615+
if (curr_token != tok_end_of_source) {
19616+
/* Skip the closing parenthesis. */
1961419617
(void)get_token();
1961519618
} /* if */
19616-
if (curr_token == tok_consteval) {
19619+
} else {
19620+
pos_error(ec_exp_lparen, &error_position);
19621+
} /* if */
19622+
} /* flush_statement_header */
19623+
19624+
19625+
static void flush_one_statement()
19626+
/*
19627+
Flush the tokens of exactly one statement. The current token is the first
19628+
token of the statement.
19629+
19630+
On return, the current token is the last token of the statement. It is left
19631+
unconsumed so that the caller can fetch the next token after leaving the
19632+
flushing lexical state, which preserves pragma state (see
19633+
flush_if_or_else_statement).
19634+
19635+
Note: The caller is responsible for having set flushing_tokens to TRUE.
19636+
*/
19637+
{
19638+
/* Note this routine is recursive because many statements have substatements,
19639+
e.g., "while (a) while (b) { }" is a single statement. */
19640+
/* Skip a leading attribute-specifier-seq. */
19641+
while (curr_token == tok_lbracket && next_token() == tok_lbracket) {
19642+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19643+
(void)get_token();
19644+
} /* while */
19645+
/* Skip the "template" of a C++26 "template for" expansion statement. The
19646+
rest of the statement is scanned like an ordinary "for" statement. */
19647+
if (curr_token == tok_template && next_token() == tok_for) {
19648+
(void)get_token();
19649+
} /* if */
19650+
switch (curr_token) {
19651+
case tok_lbrace:
19652+
/* Compound statement. */
19653+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19654+
break;
19655+
case tok_if:
19656+
/* Special handling is needed for nested "if" statements so that
19657+
substatements that are "else if" statements are handled properly. */
1961719658
(void)get_token();
19618-
} else {
19619-
if (curr_token == tok_constexpr) (void)get_token();
19620-
if (curr_token == tok_lparen) {
19621-
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19659+
if (curr_token == tok_not && next_token() == tok_consteval) {
19660+
/* Skip the "not" token only if it is followed by "consteval". */
19661+
(void)get_token();
19662+
} /* if */
19663+
if (curr_token == tok_consteval) {
1962219664
(void)get_token();
1962319665
} else {
19624-
pos_error(ec_exp_lparen, &error_position);
19666+
if (curr_token == tok_constexpr) (void)get_token();
19667+
if (curr_token == tok_lparen) {
19668+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19669+
(void)get_token();
19670+
} else {
19671+
pos_error(ec_exp_lparen, &error_position);
19672+
} /* if */
1962519673
} /* if */
19626-
} /* if */
19627-
flush_if_or_else_statement();
19628-
if (curr_token == tok_else) {
19674+
flush_one_statement();
19675+
if (next_token() == tok_else) {
19676+
/* Consume the final token of the statement skipped by
19677+
flush_one_statement. */
19678+
(void)get_token();
19679+
/* Consume the "else". */
19680+
(void)get_token();
19681+
flush_one_statement();
19682+
} /* if */
19683+
break;
19684+
case tok_while:
19685+
case tok_switch:
19686+
#if MICROSOFT_EXTENSIONS_ALLOWED
19687+
case tok_for_each:
19688+
#endif /* MICROSOFT_EXTENSIONS_ALLOWED */
19689+
/* The controlled substatement is a statement in its own right. */
1962919690
(void)get_token();
19630-
flush_if_or_else_statement();
19631-
} /* if */
19632-
} else {
19633-
push_lexical_state_stack();
19634-
curr_lexical_state_stack_entry->flushing_tokens = TRUE;
19635-
/* If we found a left brace, skip the compound statement. */
19636-
if (curr_token == tok_lbrace) {
19637-
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19638-
} else {
19639-
/* Initialize a local stop token set. Also stop on right brace and end
19640-
of source for error cases. */
19641-
a_token_set_array stop_tokens;
19642-
clear_token_set_array(stop_tokens);
19643-
incr_token_set_array_element(stop_tokens, tok_end_of_source);
19644-
incr_token_set_array_element(stop_tokens, tok_rbrace);
19645-
incr_token_set_array_element(stop_tokens, tok_semicolon);
19646-
flush_tokens_with_stop_tokens_and_warning_flag(stop_tokens,
19691+
flush_statement_header();
19692+
flush_one_statement();
19693+
break;
19694+
#if UPC_EXTENSIONS_ALLOWED
19695+
case tok_upc_forall:
19696+
/* The upc_forall statement is similar to the standard for statement. */
19697+
FALLTHROUGH
19698+
#endif /* UPC_EXTENSIONS_ALLOWED */
19699+
case tok_for:
19700+
/* For statement and range-based for statement, which may have a
19701+
"co_await" between the "for" and the opening parenthesis. */
19702+
(void)get_token();
19703+
if (curr_token == tok_coroutine_await) (void)get_token();
19704+
flush_statement_header();
19705+
flush_one_statement();
19706+
break;
19707+
case tok_do:
19708+
/* "do statement while ( expression ) ;" */
19709+
(void)get_token();
19710+
flush_one_statement();
19711+
if (next_token() == tok_while) {
19712+
/* Consume the final token of the statement skipped by
19713+
flush_one_statement. */
19714+
(void)get_token();
19715+
/* Consume the "while". */
19716+
(void)get_token();
19717+
/* Leaves the current token on the ";" following the ")". */
19718+
flush_statement_header();
19719+
} /* if */
19720+
break;
19721+
case tok_try:
19722+
/* "try compound-statement handler-seq" */
19723+
(void)get_token();
19724+
if (curr_token == tok_lbrace) {
19725+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19726+
} /* if */
19727+
while (next_token() == tok_catch) {
19728+
/* Consume the final token of the preceding compound statement (the
19729+
"try" block or the previous handler). */
19730+
(void)get_token();
19731+
/* Consume the "catch". */
19732+
(void)get_token();
19733+
flush_statement_header();
19734+
if (curr_token == tok_lbrace) {
19735+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19736+
} /* if */
19737+
} /* while */
19738+
break;
19739+
#if MICROSOFT_EXTENSIONS_ALLOWED
19740+
case tok_microsoft_try:
19741+
/* "__try compound-statement __except ( expression ) compound-statement"
19742+
or "__try compound-statement __finally compound-statement". */
19743+
(void)get_token();
19744+
if (curr_token == tok_lbrace) {
19745+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19746+
} /* if */
19747+
if (next_token() == tok_except) {
19748+
/* Consume the final token of the "__try" compound statement. */
19749+
(void)get_token();
19750+
/* Consume the "__except". */
19751+
(void)get_token();
19752+
flush_statement_header();
19753+
} else if (next_token() == tok_identifier) {
19754+
/* Consume the final token of the "__try" compound statement. */
19755+
(void)get_token();
19756+
/* Consume the "__finally". */
19757+
(void)get_token();
19758+
} /* if */
19759+
if (curr_token == tok_lbrace) {
19760+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19761+
} /* if */
19762+
break;
19763+
#endif /* MICROSOFT_EXTENSIONS_ALLOWED */
19764+
case tok_case: {
19765+
/* A case label labels the statement that follows it. Skip to the
19766+
":", allowing for conditional expressions in the case value. */
19767+
unsigned long quest_mark_count = 0;
19768+
(void)get_token();
19769+
while (curr_token != tok_end_of_source && curr_token != tok_semicolon &&
19770+
curr_token != tok_rbrace &&
19771+
(curr_token != tok_colon || quest_mark_count != 0)) {
19772+
if (curr_token == tok_quest_mark) {
19773+
quest_mark_count++;
19774+
} else if (curr_token == tok_colon) {
19775+
quest_mark_count--;
19776+
} else if (curr_token == tok_lparen || curr_token == tok_lbracket ||
19777+
curr_token == tok_lbrace) {
19778+
flush_until_matching_token_full(/*limit_flush=*/FALSE);
19779+
} /* if */
19780+
(void)get_token();
19781+
} /* while */
19782+
if (curr_token == tok_colon) {
19783+
(void)get_token();
19784+
flush_one_statement();
19785+
} /* if */
19786+
break;
19787+
}
19788+
case tok_default:
19789+
/* "default :" labels the statement that follows it. */
19790+
if (next_token() == tok_colon) {
19791+
(void)get_token();
19792+
(void)get_token();
19793+
flush_one_statement();
19794+
} /* if */
19795+
break;
19796+
case tok_identifier:
19797+
if (next_token() == tok_colon) {
19798+
/* A label definition labels the statement that follows it. */
19799+
(void)get_token();
19800+
(void)get_token();
19801+
flush_one_statement();
19802+
break;
19803+
} /* if */
19804+
/* Otherwise this is an expression or declaration statement. */
19805+
FALLTHROUGH
19806+
default:
19807+
{ /* An expression statement, declaration, jump statement, empty
19808+
statement, etc. Such statements end at the first ";" that isn't
19809+
nested within parentheses, brackets or braces. Initialize a local
19810+
stop token set. Also stop on right brace and end of source for
19811+
error cases. */
19812+
a_token_set_array stop_tokens;
19813+
clear_token_set_array(stop_tokens);
19814+
incr_token_set_array_element(stop_tokens, tok_end_of_source);
19815+
incr_token_set_array_element(stop_tokens, tok_rbrace);
19816+
incr_token_set_array_element(stop_tokens, tok_semicolon);
19817+
flush_tokens_with_stop_tokens_and_warning_flag(
19818+
stop_tokens,
1964719819
/*suppress_warning=*/TRUE);
19648-
} /* if */
19649-
pop_lexical_state_stack();
19650-
if (curr_token != tok_end_of_source) (void)get_token();
19651-
} /* if */
19820+
}
19821+
break;
19822+
} /* switch */
19823+
} /* flush_one_statement */
19824+
19825+
19826+
void flush_if_or_else_statement(void)
19827+
/*
19828+
Flush tokens of a dependent statement of an "if" or "else" statement.
19829+
The current token is the token after the "if" or "else" keyword. Exactly one
19830+
statement is flushed, so for an unbraced dependent statement such as
19831+
"if constexpr (c) while (a) while (b) { } return;", the "return" is not
19832+
flushed. On return the current token is the first token after the statement.
19833+
*/
19834+
{
19835+
push_lexical_state_stack();
19836+
curr_lexical_state_stack_entry->flushing_tokens = TRUE;
19837+
flush_one_statement();
19838+
pop_lexical_state_stack();
19839+
if (curr_token != tok_end_of_source) (void)get_token();
1965219840
} /* flush_if_or_else_statement */
1965319841

1965419842

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
cpfe -DTEST_NUMBER=1 --c++17 Test_name.c
2+
"Test_name.c", line 5: warning: statement is unreachable
3+
return [] { if constexpr (B) while (false) {} return 1; return 2; }();
4+
^
5+
6+
"Test_name.c", line 38: warning: label "L" was declared but never referenced
7+
if constexpr (B) L: while (B) { }
8+
^
9+
10+
"Test_name.c", line 40: warning: label "x" was declared but never referenced
11+
if constexpr (B) x: if (B) while (B) {} else for (;;) {}
12+
^
13+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
cpfe -DTEST_NUMBER=2 --c++17 --gnu_version 130300 Test_name.c
2+
"Test_name.c", line 5: warning: statement is unreachable
3+
return [] { if constexpr (B) while (false) {} return 1; return 2; }();
4+
^
5+
6+
"Test_name.c", line 38: warning: label "L" was declared but never referenced
7+
if constexpr (B) L: while (B) { }
8+
^
9+
10+
"Test_name.c", line 40: warning: label "x" was declared but never referenced
11+
if constexpr (B) x: if (B) while (B) {} else for (;;) {}
12+
^
13+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//options::--gnu_version 130300
2+
//options_all:--c++17
3+
4+
template <bool B> constexpr int h() {
5+
return [] { if constexpr (B) while (false) {} return 1; return 2; }();
6+
}
7+
8+
static_assert(h<false>() == 1, "h<false>() must be 1");
9+
10+
// Nested unbraced substatements: only one statement may be discarded.
11+
template <bool B> constexpr int nested() {
12+
return [] {
13+
if constexpr (B) while (B) while (!B) { };
14+
return 1;
15+
}();
16+
}
17+
static_assert(nested<false>() == 1, "nested");
18+
19+
int arr[2];
20+
21+
template <bool B> constexpr int others() {
22+
return [] {
23+
int n = 0;
24+
if constexpr (B) for (;;) {}
25+
n += 1;
26+
if constexpr (B) for (int i : arr) for (int j : arr) { }
27+
n += 1;
28+
#ifdef __cpp_expansion_statements
29+
if constexpr (B) template for (auto x : arr) { }
30+
#endif
31+
n += 1;
32+
if constexpr (B) do {} while (0);
33+
n += 1;
34+
if constexpr (B) switch (n) { case 1: break; }
35+
n += 1;
36+
if constexpr (B) try {} catch (int) {} catch (...) {}
37+
n += 1;
38+
if constexpr (B) L: while (B) { }
39+
n += 1;
40+
if constexpr (B) x: if (B) while (B) {} else for (;;) {}
41+
n += 1;
42+
if constexpr (B) [] {}();
43+
n += 1;
44+
if constexpr (B) ; else while (B) {}
45+
n += 1;
46+
// Expression statements containing lambdas: everything after the
47+
// lambda's closing brace must also be discarded.
48+
if constexpr (B) n = [] { return 100; }() + 1000, n = 999999;
49+
n += 1;
50+
if constexpr (B) n = [&] { if (n) { n = 5; } return n; }() * 7 +
51+
[] { return 1; }() + arr[0], n = 999999;
52+
n += 1;
53+
if constexpr (B) auto l = [] { return 1; }, m = [] { return 2; },
54+
o = (n = 999999);
55+
n += 1;
56+
if constexpr (!B) n += 1; else n = [] { return 100; }() + 1000,
57+
n = 999999;
58+
if constexpr (B) n = 100; else while (B) {}
59+
return n;
60+
}();
61+
}
62+
static_assert(others<false>() == 14, "others");

0 commit comments

Comments
 (0)