aboutsummaryrefslogtreecommitdiff
path: root/gcc/cp/semantics.cc
diff options
context:
space:
mode:
authorJakub Jelinek <jakub@redhat.com>2025-02-07 17:07:23 +0100
committerJakub Jelinek <jakub@gcc.gnu.org>2025-02-07 17:07:23 +0100
commit35d40b56eb6e7ac63c790a799d3b367742d58a5e (patch)
tree65dca5061054d0cabe6f11a1de79afa042964257 /gcc/cp/semantics.cc
parente22962538f64bb6e5ac87977ec8a5d86f4ef21cb (diff)
downloadgcc-35d40b56eb6e7ac63c790a799d3b367742d58a5e.zip
gcc-35d40b56eb6e7ac63c790a799d3b367742d58a5e.tar.gz
gcc-35d40b56eb6e7ac63c790a799d3b367742d58a5e.tar.bz2
c++: Fix up handling of for/while loops with declarations in condition [PR86769]
As the following testcase show (note, just for-{3,4,6,7,8}.C, constexpr-86769.C and stmtexpr27.C FAIL without the patch, the rest is just that I couldn't find coverage for some details and so added tests we don't regress or for5.C is from Marek's attempt in the PR), we weren't correctly handling for/while loops with declarations as conditions. The C++ FE has the simplify_loop_decl_cond function which transforms such loops as mentioned in the comment: while (A x = 42) { } for (; A x = 42;) { } becomes while (true) { A x = 42; if (!x) break; } for (;;) { A x = 42; if (!x) break; } For for loops this is not enough, as the x declaration should be still in scope when expr (if any) is executed, and injecting the expr expression into the body in the FE needs to have the continue label in between, something normally added by the c-family genericization. One of my thoughts was to just add there an artificial label plus the expr expression in the FE and tell c-family about that label, so that it doesn't create it but uses what has been emitted. Unfortunately break/continue are resolved to labels only at c-family genericization time and by moving the condition (and its preparation statements such as the DECL_EXPR) into the body (and perhaps by also moving there the (increment) expr as well) we resolve incorrectly any break/continue statement appearing in cond (or newly perhaps also expr) expression(s). While in standard C++ one can't have something like that there, with statement expressions they are possible there, and we actually have testsuite coverage that when they appear outside of the body of the loop they bind to an outer loop rather than the inner one. When the FE moves everything into the body, c-family can't distinguish any more between the user body vs. the condition/preparation statements vs. expr expression. So, the following patch instead keeps them separate and does the merging only at the c-family loop genericization time. For that the patch introduces two new operands of FOR_STMT and WHILE_STMT, *_COND_PREP which is forced to be a BIND_EXPR which contains the preparation statements like DECL_EXPR, and the initialization of that variable, so basically what {FOR,WHILE}_BODY has when we encounter the function dealing with this, except one optional CLEANUP_STMT at the end which holds cleanups for the variable if it needs to be destructed. This CLEANUP_STMT is removed and the actual cleanup saved into another new operand, *_COND_CLEANUP. The c-family loop genericization handles such loops roughly the way https://eel.is/c++draft/stmt.for and https://eel.is/c++draft/stmt.while specifies, so the body is (if *_COND_CLEANUP is non-NULL) { A x = 42; try { if (!x) break; body; cont_label: expr; } finally { cleanup; } } and otherwise { A x = 42; if (!x) break; body; cont_label: expr; } i.e. the *_COND, *_BODY, optional continue label, FOR_EXPR are appended into the body of the *_COND_PREP BIND_EXPR. And when doing constexpr evaluation of such FOR/WHILE loops, we treat it similarly, first evaluate *_COND_PREP except the for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl)) destroy_value_checked (ctx, decl, non_constant_p); part of BIND_EXPR handling for it, then evaluate *_COND (and decide based on whether it was false or true like before), then *_BODY, then FOR_EXPR, then *_COND_CLEANUP (similarly to the way how CLEANUP_STMT handling handles that) and finally do those destroy_value_checked. Note, the constexpr-86769.C testcase FAILs with both clang++ and MSVC (note, the rest of tests PASS with clang++) but I believe it must be just a bug in those compilers, new int is done in all the constructors and delete is done in the destructor, so when clang++ reports one of the new int weren't deallocated during constexpr evaluation I don't see how that would be possible. When the same test has all the constexpr stuff, all the new int are properly deleted at runtime when compiled by both compilers and valgrind is happy about it, no leaks. 2025-02-07 Jakub Jelinek <jakub@redhat.com> Jason Merrill <jason@redhat.com> PR c++/86769 gcc/c-family/ * c-common.def (FOR_STMT): Add 2 operands and document them. (WHILE_STMT): Likewise. * c-common.h (WHILE_COND_PREP, WHILE_COND_CLEANUP): Define. (FOR_COND_PREP, FOR_COND_CLEANUP): Define. * c-gimplify.cc (genericize_c_loop): Add COND_PREP and COND_CLEANUP arguments, handle them if they are non-NULL. (genericize_for_stmt, genericize_while_stmt, genericize_do_stmt): Adjust callers. gcc/c/ * c-parser.cc (c_parser_while_statement): Add 2 further NULL_TREE operands to build_stmt. (c_parser_for_statement): Likewise. gcc/cp/ * semantics.cc (set_one_cleanup_loc): New function. (set_cleanup_locs): Use it. (simplify_loop_decl_cond): Remove. (adjust_loop_decl_cond): New function. (begin_while_stmt): Add 2 further NULL_TREE operands to build_stmt. (finish_while_stmt_cond): Call adjust_loop_decl_cond instead of simplify_loop_decl_cond. (finish_while_stmt): Call do_poplevel also on WHILE_COND_PREP if non-NULL and also use pop_stmt_list rather than do_poplevel for WHILE_BODY in that case. Call set_one_cleanup_loc. (begin_for_stmt): Add 2 further NULL_TREE operands to build_stmt. (finish_for_cond): Call adjust_loop_decl_cond instead of simplify_loop_decl_cond. (finish_for_stmt): Call do_poplevel also on FOR_COND_PREP if non-NULL and also use pop_stmt_list rather than do_poplevel for FOR_BODY in that case. Call set_one_cleanup_loc. * constexpr.cc (cxx_eval_loop_expr): Handle {WHILE,FOR}_COND_{PREP,CLEANUP}. (check_for_return_continue): Handle {WHILE,FOR}_COND_PREP. (potential_constant_expression_1): RECUR on {WHILE,FOR}_COND_{PREP,CLEANUP}. gcc/testsuite/ * g++.dg/diagnostic/redeclaration-7.C: New test. * g++.dg/expr/for3.C: New test. * g++.dg/expr/for4.C: New test. * g++.dg/expr/for5.C: New test. * g++.dg/expr/for6.C: New test. * g++.dg/expr/for7.C: New test. * g++.dg/expr/for8.C: New test. * g++.dg/ext/stmtexpr27.C: New test. * g++.dg/cpp2a/constexpr-86769.C: New test. * g++.dg/cpp26/name-independent-decl7.C: New test. * g++.dg/cpp26/name-independent-decl8.C: New test.
Diffstat (limited to 'gcc/cp/semantics.cc')
-rw-r--r--gcc/cp/semantics.cc118
1 files changed, 77 insertions, 41 deletions
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 73b4917..a2ee3a3 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -601,6 +601,25 @@ add_decl_expr (tree decl)
add_stmt (r);
}
+/* Set EXPR_LOCATION on one cleanup T to LOC. */
+
+static void
+set_one_cleanup_loc (tree t, location_t loc)
+{
+ if (!t)
+ return;
+ if (TREE_CODE (t) != POSTCONDITION_STMT)
+ protected_set_expr_location (t, loc);
+ /* Avoid locus differences for C++ cdtor calls depending on whether
+ cdtor_returns_this: a conversion to void is added to discard the return
+ value, and this conversion ends up carrying the location, and when it
+ gets discarded, the location is lost. So hold it in the call as well. */
+ if (TREE_CODE (t) == NOP_EXPR
+ && TREE_TYPE (t) == void_type_node
+ && TREE_CODE (TREE_OPERAND (t, 0)) == CALL_EXPR)
+ protected_set_expr_location (TREE_OPERAND (t, 0), loc);
+}
+
/* Set EXPR_LOCATION of the cleanups of any CLEANUP_STMT in STMTS to LOC. */
static void
@@ -608,18 +627,7 @@ set_cleanup_locs (tree stmts, location_t loc)
{
if (TREE_CODE (stmts) == CLEANUP_STMT)
{
- tree t = CLEANUP_EXPR (stmts);
- if (t && TREE_CODE (t) != POSTCONDITION_STMT)
- protected_set_expr_location (t, loc);
- /* Avoid locus differences for C++ cdtor calls depending on whether
- cdtor_returns_this: a conversion to void is added to discard the return
- value, and this conversion ends up carrying the location, and when it
- gets discarded, the location is lost. So hold it in the call as
- well. */
- if (TREE_CODE (t) == NOP_EXPR
- && TREE_TYPE (t) == void_type_node
- && TREE_CODE (TREE_OPERAND (t, 0)) == CALL_EXPR)
- protected_set_expr_location (TREE_OPERAND (t, 0), loc);
+ set_one_cleanup_loc (CLEANUP_EXPR (stmts), loc);
set_cleanup_locs (CLEANUP_BODY (stmts), loc);
}
else if (TREE_CODE (stmts) == STATEMENT_LIST)
@@ -777,37 +785,48 @@ finish_cond (tree *cond_p, tree expr)
*cond_p = expr;
}
-/* If *COND_P specifies a conditional with a declaration, transform the
- loop such that
+/* If loop condition specifies a conditional with a declaration,
+ such as
while (A x = 42) { }
for (; A x = 42;) { }
- becomes
- while (true) { A x = 42; if (!x) break; }
- for (;;) { A x = 42; if (!x) break; }
- The statement list for BODY will be empty if the conditional did
+ move the *BODY_P statements as a BIND_EXPR into {FOR,WHILE}_COND_PREP
+ and if there is any CLEANUP_STMT at the end, remove that and
+ put the cleanup into {FOR,WHILE}_COND_CLEANUP.
+ genericize_c_loop will then handle it appropriately. In particular,
+ the {FOR,WHILE}_COND, {FOR,WHILE}_BODY, if used continue label and
+ FOR_EXPR will be appended into the {FOR,WHILE}_COND_PREP BIND_EXPR,
+ but it can't be done too early because only the actual body should
+ bind BREAK_STMT and CONTINUE_STMT to the inner loop.
+ The statement list for *BODY will be empty if the conditional did
not declare anything. */
static void
-simplify_loop_decl_cond (tree *cond_p, tree body)
+adjust_loop_decl_cond (tree *body_p, tree *prep_p, tree *cleanup_p)
{
- tree cond, if_stmt;
-
- if (!TREE_SIDE_EFFECTS (body))
+ if (!TREE_SIDE_EFFECTS (*body_p))
return;
- cond = *cond_p;
- *cond_p = boolean_true_node;
-
- if_stmt = begin_if_stmt ();
- cond_p = &cond;
- while (TREE_CODE (*cond_p) == ANNOTATE_EXPR)
- cond_p = &TREE_OPERAND (*cond_p, 0);
- *cond_p = cp_build_unary_op (TRUTH_NOT_EXPR, *cond_p, false,
- tf_warning_or_error);
- finish_if_stmt_cond (cond, if_stmt);
- finish_break_stmt ();
- finish_then_clause (if_stmt);
- finish_if_stmt (if_stmt);
+ gcc_assert (!processing_template_decl);
+ if (*body_p != cur_stmt_list)
+ {
+ /* There can be either no cleanup at all, if the condition
+ declaration doesn't have non-trivial destructor, or a single
+ one if it does. In that case extract it into *CLEANUP_P. */
+ gcc_assert (stmt_list_stack->length () > 1
+ && (*stmt_list_stack)[stmt_list_stack->length ()
+ - 2] == *body_p);
+ tree_stmt_iterator last = tsi_last (*body_p);
+ gcc_assert (tsi_one_before_end_p (last)
+ && TREE_CODE (tsi_stmt (last)) == CLEANUP_STMT
+ && CLEANUP_BODY (tsi_stmt (last)) == cur_stmt_list
+ && tsi_end_p (tsi_last (cur_stmt_list))
+ && !CLEANUP_EH_ONLY (tsi_stmt (last)));
+ *cleanup_p = CLEANUP_EXPR (tsi_stmt (last));
+ tsi_delink (&last);
+ }
+ current_binding_level->keep = true;
+ *prep_p = *body_p;
+ *body_p = push_stmt_list ();
}
/* Finish a goto-statement. */
@@ -1368,7 +1387,8 @@ tree
begin_while_stmt (void)
{
tree r;
- r = build_stmt (input_location, WHILE_STMT, NULL_TREE, NULL_TREE, NULL_TREE);
+ r = build_stmt (input_location, WHILE_STMT, NULL_TREE, NULL_TREE, NULL_TREE,
+ NULL_TREE, NULL_TREE);
add_stmt (r);
WHILE_BODY (r) = do_pushlevel (sk_block);
begin_cond (&WHILE_COND (r));
@@ -1406,7 +1426,9 @@ finish_while_stmt_cond (tree cond, tree while_stmt, bool ivdep,
build_int_cst (integer_type_node,
annot_expr_no_vector_kind),
integer_zero_node);
- simplify_loop_decl_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt));
+ adjust_loop_decl_cond (&WHILE_BODY (while_stmt),
+ &WHILE_COND_PREP (while_stmt),
+ &WHILE_COND_CLEANUP (while_stmt));
}
/* Finish a while-statement, which may be given by WHILE_STMT. */
@@ -1415,8 +1437,14 @@ void
finish_while_stmt (tree while_stmt)
{
end_maybe_infinite_loop (boolean_true_node);
- WHILE_BODY (while_stmt) = do_poplevel (WHILE_BODY (while_stmt));
+ WHILE_BODY (while_stmt)
+ = (WHILE_COND_PREP (while_stmt)
+ ? pop_stmt_list (WHILE_BODY (while_stmt))
+ : do_poplevel (WHILE_BODY (while_stmt)));
finish_loop_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt));
+ if (WHILE_COND_PREP (while_stmt))
+ WHILE_COND_PREP (while_stmt) = do_poplevel (WHILE_COND_PREP (while_stmt));
+ set_one_cleanup_loc (WHILE_COND_CLEANUP (while_stmt), input_location);
}
/* Begin a do-statement. Returns a newly created DO_STMT if
@@ -1547,7 +1575,8 @@ begin_for_stmt (tree scope, tree init)
tree r;
r = build_stmt (input_location, FOR_STMT, NULL_TREE, NULL_TREE,
- NULL_TREE, NULL_TREE, NULL_TREE, NULL_TREE);
+ NULL_TREE, NULL_TREE, NULL_TREE, NULL_TREE,
+ NULL_TREE, NULL_TREE);
if (scope == NULL_TREE)
{
@@ -1605,7 +1634,8 @@ finish_for_cond (tree cond, tree for_stmt, bool ivdep, tree unroll,
build_int_cst (integer_type_node,
annot_expr_no_vector_kind),
integer_zero_node);
- simplify_loop_decl_cond (&FOR_COND (for_stmt), FOR_BODY (for_stmt));
+ adjust_loop_decl_cond (&FOR_BODY (for_stmt), &FOR_COND_PREP (for_stmt),
+ &FOR_COND_CLEANUP (for_stmt));
}
/* Finish the increment-EXPRESSION in a for-statement, which may be
@@ -1679,11 +1709,17 @@ finish_for_stmt (tree for_stmt)
RANGE_FOR_BODY (for_stmt) = do_poplevel (RANGE_FOR_BODY (for_stmt));
else
{
- FOR_BODY (for_stmt) = do_poplevel (FOR_BODY (for_stmt));
+ FOR_BODY (for_stmt)
+ = (FOR_COND_PREP (for_stmt)
+ ? pop_stmt_list (FOR_BODY (for_stmt))
+ : do_poplevel (FOR_BODY (for_stmt)));
if (FOR_COND (for_stmt))
finish_loop_cond (&FOR_COND (for_stmt),
FOR_EXPR (for_stmt) ? integer_one_node
: FOR_BODY (for_stmt));
+ if (FOR_COND_PREP (for_stmt))
+ FOR_COND_PREP (for_stmt) = do_poplevel (FOR_COND_PREP (for_stmt));
+ set_one_cleanup_loc (FOR_COND_CLEANUP (for_stmt), input_location);
}
/* Pop the scope for the body of the loop. */