diff options
author | Jakub Jelinek <jakub@redhat.com> | 2024-04-10 10:08:12 +0200 |
---|---|---|
committer | Jakub Jelinek <jakub@redhat.com> | 2024-04-10 10:19:04 +0200 |
commit | 4be1cc5f50578fafcdcbd09160235066d76a3f86 (patch) | |
tree | a763e327dd8ec081d05b7299de58c38efd149b00 /gcc/cp/semantics.cc | |
parent | 4923ed49b93352bcf9e43cafac38345e4a54c3f8 (diff) | |
download | gcc-4be1cc5f50578fafcdcbd09160235066d76a3f86.zip gcc-4be1cc5f50578fafcdcbd09160235066d76a3f86.tar.gz gcc-4be1cc5f50578fafcdcbd09160235066d76a3f86.tar.bz2 |
c++: Implement C++26 P2809R3 - Trivial infinite loops are not Undefined Behavior
The following patch attempts to implement P2809R3, which has been voted
in as a DR.
The middle-end has its behavior documented:
'-ffinite-loops'
Assume that a loop with an exit will eventually take the exit and
not loop indefinitely. This allows the compiler to remove loops
that otherwise have no side-effects, not considering eventual
endless looping as such.
This option is enabled by default at '-O2' for C++ with -std=c++11
or higher.
So, the following patch attempts to detect trivial infinite loops by detecting
trivially empty loops, if their condition is not INTEGER_CST (that case is
handled by the middle-end right already) trying to constant evaluate with
mce=true their condition and if it evaluates to true (and -ffinite-loops and
not processing_template_decl) wraps the condition into an ANNOTATE_EXPR which
tells the middle-end that the loop shouldn't be loop->finite_p despite
-ffinite-loops).
Furthermore, the patch adds -Wtautological-compare warnings for loop
conditions containing std::is_constant_evaluated(), either if those
always evaluate to true, or always evaluate to false, or will evaluate
to true just when checking if it is trivial infinite loop (and if in non-constexpr
function also say that it will evaluate to false otherwise).
The user is doing something weird in all those cases.
2024-04-10 Jakub Jelinek <jakub@redhat.com>
PR c++/114462
gcc/
* tree-core.h (enum annot_expr_kind): Add
annot_expr_maybe_infinite_kind enumerator.
* gimplify.cc (gimple_boolify): Handle annot_expr_maybe_infinite_kind.
* tree-cfg.cc (replace_loop_annotate_in_block): Likewise.
(replace_loop_annotate): Likewise. Move loop->finite_p initialization
before the replace_loop_annotate_in_block calls.
* tree-pretty-print.cc (dump_generic_node): Handle
annot_expr_maybe_infinite_kind.
gcc/cp/
* semantics.cc: Implement C++26 P2809R3 - Trivial infinite
loops are not Undefined Behavior.
(maybe_warn_for_constant_evaluated): Add trivial_infinite argument
and emit special diagnostics for that case.
(finish_if_stmt_cond): Adjust caller.
(finish_loop_cond): New function.
(finish_while_stmt): Use it.
(finish_do_stmt): Likewise.
(finish_for_stmt): Likewise.
gcc/testsuite/
* g++.dg/cpp26/trivial-infinite-loop1.C: New test.
* g++.dg/cpp26/trivial-infinite-loop2.C: New test.
* g++.dg/cpp26/trivial-infinite-loop3.C: New test.
Diffstat (limited to 'gcc/cp/semantics.cc')
-rw-r--r-- | gcc/cp/semantics.cc | 75 |
1 files changed, 72 insertions, 3 deletions
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 329c524..abaa4a3 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -1090,7 +1090,8 @@ find_std_constant_evaluated_r (tree *tp, int *walk_subtrees, void *) (e.g., in a non-constexpr non-consteval function) so give the user a clue. */ static void -maybe_warn_for_constant_evaluated (tree cond, bool constexpr_if) +maybe_warn_for_constant_evaluated (tree cond, bool constexpr_if, + bool trivial_infinite) { if (!warn_tautological_compare) return; @@ -1108,6 +1109,18 @@ maybe_warn_for_constant_evaluated (tree cond, bool constexpr_if) warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare, "%<std::is_constant_evaluated%> always evaluates to " "true in %<if constexpr%>"); + else if (trivial_infinite) + { + auto_diagnostic_group d; + if (warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare, + "%<std::is_constant_evaluated%> evaluates to " + "true when checking if trivially empty iteration " + "statement is trivial infinite loop") + && !maybe_constexpr_fn (current_function_decl)) + inform (EXPR_LOCATION (cond), + "and evaluates to false when actually evaluating " + "the condition in non-%<constexpr%> function"); + } else if (!maybe_constexpr_fn (current_function_decl)) warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare, "%<std::is_constant_evaluated%> always evaluates to " @@ -1126,7 +1139,8 @@ tree finish_if_stmt_cond (tree orig_cond, tree if_stmt) { tree cond = maybe_convert_cond (orig_cond); - maybe_warn_for_constant_evaluated (cond, IF_STMT_CONSTEXPR_P (if_stmt)); + maybe_warn_for_constant_evaluated (cond, IF_STMT_CONSTEXPR_P (if_stmt), + /*trivial_infinite=*/false); if (IF_STMT_CONSTEXPR_P (if_stmt) && !type_dependent_expression_p (cond) && require_constant_expression (cond) @@ -1205,6 +1219,48 @@ finish_if_stmt (tree if_stmt) add_stmt (do_poplevel (scope)); } +/* Determine if iteration statement with *CONDP condition and + loop BODY is trivially empty iteration statement or even + trivial infinite loop. In the latter case for -ffinite-loops + add ANNOTATE_EXPR to mark the loop as maybe validly infinite. + Also, emit -Wtautological-compare warning for std::is_constant_evaluated () + calls in the condition when needed. */ + +static void +finish_loop_cond (tree *condp, tree body) +{ + if (TREE_CODE (*condp) == INTEGER_CST) + return; + bool trivially_empty = expr_first (body) == NULL_TREE; + bool trivial_infinite = false; + if (trivially_empty) + { + tree c = fold_non_dependent_expr (*condp, tf_none, + /*manifestly_const_eval=*/true); + trivial_infinite = c && integer_nonzerop (c); + } + if (warn_tautological_compare) + { + tree cond = *condp; + while (TREE_CODE (cond) == ANNOTATE_EXPR) + cond = TREE_OPERAND (cond, 0); + if (trivial_infinite + && !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)) + maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false, + /*trivial_infinite=*/true); + else if (!trivially_empty + || !processing_template_decl + || DECL_IMMEDIATE_FUNCTION_P (current_function_decl)) + maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false, + /*trivial_infinite=*/false); + } + if (trivial_infinite && flag_finite_loops && !processing_template_decl) + *condp = build3 (ANNOTATE_EXPR, TREE_TYPE (*condp), *condp, + build_int_cst (integer_type_node, + annot_expr_maybe_infinite_kind), + integer_zero_node); +} + /* Begin a while-statement. Returns a newly created WHILE_STMT if appropriate. */ @@ -1260,6 +1316,7 @@ finish_while_stmt (tree while_stmt) { end_maybe_infinite_loop (boolean_true_node); WHILE_BODY (while_stmt) = do_poplevel (WHILE_BODY (while_stmt)); + finish_loop_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt)); } /* Begin a do-statement. Returns a newly created DO_STMT if @@ -1317,6 +1374,12 @@ finish_do_stmt (tree cond, tree do_stmt, bool ivdep, tree unroll, build_int_cst (integer_type_node, annot_expr_no_vector_kind), integer_zero_node); DO_COND (do_stmt) = cond; + tree do_body = DO_BODY (do_stmt); + if (CONVERT_EXPR_P (do_body) + && integer_zerop (TREE_OPERAND (do_body, 0)) + && VOID_TYPE_P (TREE_TYPE (do_body))) + do_body = NULL_TREE; + finish_loop_cond (&DO_COND (do_stmt), do_body); } /* Finish a return-statement. The EXPRESSION returned, if any, is as @@ -1487,7 +1550,13 @@ finish_for_stmt (tree for_stmt) if (TREE_CODE (for_stmt) == RANGE_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) = 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)); + } /* Pop the scope for the body of the loop. */ tree *scope_ptr = (TREE_CODE (for_stmt) == RANGE_FOR_STMT |