c++: ICE with requires and -Wsequence-point [PR126066]

Message ID 20260707221649.1116351-1-polacek@redhat.com
State New
Headers
Series c++: ICE with requires and -Wsequence-point [PR126066] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap pending Patch applied

Commit Message

Marek Polacek July 7, 2026, 10:16 p.m. UTC
  Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/16?

-- >8 --
We trip on the assert in lvalue_kind/MODOP_EXPR whose comment says
that we expect to see MODOP_EXPRs only during template processing.
In this test we get there with processing_template_decl==0.  The
MODOP_EXPR is created in:

    /* Parse the requirement body. */
    ++processing_template_decl;
    reqs = cp_parser_requirement_body (parser);
    --processing_template_decl;

but we're not in a template when calling maybe_convert_cond which
calls verify_sequence_points which ends up calling lvalue_p on
the MODOP_EXPR.  verify_sequence_points is a c-family/ function
so we couldn't make it stop recursing on REQUIRES_EXPR, so I suppose
we can do the following.

	PR c++/126066

gcc/cp/ChangeLog:

	* tree.cc (lvalue_kind) <case MODOP_EXPR>: If
	!processing_template_decl, assert that the expression isn't
	type-dependent.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-pr126066.C: New test.
---
 gcc/cp/tree.cc                                 |  8 ++++++--
 gcc/testsuite/g++.dg/cpp2a/concepts-pr126066.C | 15 +++++++++++++++
 2 files changed, 21 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-pr126066.C


base-commit: d15703b27aa4924ca3438066ae0759e7379b3e0b
  

Comments

Jason Merrill July 8, 2026, 12:59 p.m. UTC | #1
On 7/7/26 6:16 PM, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/16?
> 
> -- >8 --
> We trip on the assert in lvalue_kind/MODOP_EXPR whose comment says
> that we expect to see MODOP_EXPRs only during template processing.
> In this test we get there with processing_template_decl==0.  The
> MODOP_EXPR is created in:
> 
>      /* Parse the requirement body. */
>      ++processing_template_decl;
>      reqs = cp_parser_requirement_body (parser);
>      --processing_template_decl;
> 
> but we're not in a template when calling maybe_convert_cond which
> calls verify_sequence_points which ends up calling lvalue_p on
> the MODOP_EXPR.  verify_sequence_points is a c-family/ function
> so we couldn't make it stop recursing on REQUIRES_EXPR, so I suppose
> we can do the following.

Eh, this just looks like a workaround for a single testcase.

I see several problems here:

1) We're retaining template trees in a non-template function,
2) verify_sequence_points is walking the unevaluated operand of a 
requires because verify_tree assumes that all unknown expressions are 
evaluated, and
3) REQURES_EXPR looks like a normal expression (tcc_expression).

I think the right way to approach this would be to attack 3 by changing 
REQUIRES_EXPR to tcc_exceptional.  Then 1 doesn't matter because the 
generic code (such as verify_tree) sees that it's magic and doesn't try 
to walk into it.

We could also attack 2 by defining unevaluated_p in the C front-end as 
well, and checking it in verify_tree.  2 also affects other unevaluated 
codes like NOEXCEPT_EXPR, potentially leading to wrong -Wsequence-point 
results.

Jason
  

Patch

diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index e543cf14519..9094449ae24 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -281,8 +281,12 @@  lvalue_kind (const_tree ref)
 
     case MODOP_EXPR:
       /* We expect to see unlowered MODOP_EXPRs only during
-	 template processing.  */
-      gcc_assert (processing_template_decl);
+	 template processing, but we can get here e.g. from the body of
+	 a compound-requirement as well in which case make sure we
+	 aren't operating on a type-dependent expr.  */
+      gcc_assert (processing_template_decl
+		  || !type_dependent_expression_p_push
+		       (const_cast<tree> (ref)));
       if (CLASS_TYPE_P (TREE_TYPE (TREE_OPERAND (ref, 0))))
 	goto default_;
       else
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-pr126066.C b/gcc/testsuite/g++.dg/cpp2a/concepts-pr126066.C
new file mode 100644
index 00000000000..ff8a3a49261
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-pr126066.C
@@ -0,0 +1,15 @@ 
+// PR c++/126066
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wall" }
+
+struct S { S& operator=(int); };
+
+void
+fn ()
+{
+  if constexpr (requires(int &a) { a = 0; }) { }
+  if constexpr (requires(S &s) { s = 0; }) { }
+}
+
+constexpr bool b = requires(int &a) { a = 0; };
+static_assert(requires(int &a) { a = 0; });