c++: ICE in tsubst_expr with comma expr [PR125539]

Message ID 20260602215943.1119054-1-polacek@redhat.com
State New
Headers
Series c++: ICE in tsubst_expr with comma expr [PR125539] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap success Build passed

Commit Message

Marek Polacek June 2, 2026, 9:59 p.m. UTC
  Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/branches?

-- >8 --
Here we crash because a TARGET_EXPR gets into tsubst_expr with something
like:

  template<typename>
  constexpr static A a = (A{}, A{});

Pre r14-4796, when we had tsubst_copy, we didn't crash because we had
an early exit when substituting with args=NULL_TREE.  tsubst_expr
deliberately doesn't have that early exit.

Note that

  template<typename>
  constexpr static A a = A{};

works because expand_aggr_init_1 sees a COMPOUND_LITERAL_P and does the
early exit without calling expand_default_init.  But with a COMPOUND_EXPR
we don't take that path.

The TARGET_EXPR is created via check_initializer -> build_aggr_init_full_exprs
-> build_aggr_init -> expand_aggr_init_1 -> expand_default_init -> ocp_convert
-> build_cplus_new -> build_target_expr.  I tried adjusting the big
and ugly check in check_initializer before build_aggr_init_full_exprs
but that didn't work out.  I also tried avoiding the call to ocp_convert
but that broke some reflection tests.  But avoiding the call to
build_cplus_new seems to work.

	PR c++/125539

gcc/cp/ChangeLog:

	* cvt.cc (ocp_convert): Don't call build_cplus_new in a
	template.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp1y/var-templ89.C: New test.
---
 gcc/cp/cvt.cc                            |  4 ++-
 gcc/testsuite/g++.dg/cpp1y/var-templ89.C | 32 ++++++++++++++++++++++++
 2 files changed, 35 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/var-templ89.C


base-commit: ee7d0c7b4102b8ef0bcaa725c75e141248b774cb
  

Comments

Jason Merrill June 3, 2026, 7:41 p.m. UTC | #1
On 6/2/26 5:59 PM, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/branches?
> 
> -- >8 --
> Here we crash because a TARGET_EXPR gets into tsubst_expr with something
> like:
> 
>    template<typename>
>    constexpr static A a = (A{}, A{});
> 
> Pre r14-4796, when we had tsubst_copy, we didn't crash because we had
> an early exit when substituting with args=NULL_TREE.  tsubst_expr
> deliberately doesn't have that early exit.
> 
> Note that
> 
>    template<typename>
>    constexpr static A a = A{};
> 
> works because expand_aggr_init_1 sees a COMPOUND_LITERAL_P and does the
> early exit without calling expand_default_init.  But with a COMPOUND_EXPR
> we don't take that path.
> 
> The TARGET_EXPR is created via check_initializer -> build_aggr_init_full_exprs
> -> build_aggr_init -> expand_aggr_init_1 -> expand_default_init -> ocp_convert
> -> build_cplus_new -> build_target_expr.  I tried adjusting the big
> and ugly check in check_initializer before build_aggr_init_full_exprs
> but that didn't work out.  I also tried avoiding the call to ocp_convert
> but that broke some reflection tests.  But avoiding the call to
> build_cplus_new seems to work.
> 
> 	PR c++/125539
> 
> gcc/cp/ChangeLog:
> 
> 	* cvt.cc (ocp_convert): Don't call build_cplus_new in a
> 	template.

I notice that build_aggr_init_expr already shortcuts 
processing_template_decl.  What if we do that in build_cplus_new as 
well, if init has the right type?

> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp1y/var-templ89.C: New test.
> ---
>   gcc/cp/cvt.cc                            |  4 ++-
>   gcc/testsuite/g++.dg/cpp1y/var-templ89.C | 32 ++++++++++++++++++++++++
>   2 files changed, 35 insertions(+), 1 deletion(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1y/var-templ89.C
> 
> diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc
> index b1176317d99..571f2e64d03 100644
> --- a/gcc/cp/cvt.cc
> +++ b/gcc/cp/cvt.cc
> @@ -985,7 +985,9 @@ ocp_convert (tree type, tree expr, int convtype, int flags,
>   					    type, flags, complain);
>   	}
>         if (ctor)
> -	return build_cplus_new (type, ctor, complain);
> +	return (processing_template_decl
> +		? ctor
> +		: build_cplus_new (type, ctor, complain));
>       }
>   
>     if (complain & tf_error)
> diff --git a/gcc/testsuite/g++.dg/cpp1y/var-templ89.C b/gcc/testsuite/g++.dg/cpp1y/var-templ89.C
> new file mode 100644
> index 00000000000..5f98a95580c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/var-templ89.C
> @@ -0,0 +1,32 @@
> +// PR c++/125539
> +// { dg-do compile { target c++14 } }
> +
> +struct A {};
> +struct B { int i; };
> +constexpr void foo () {}
> +
> +template<typename T>
> +constexpr static A a1 = (foo (), A {});
> +template<typename T>
> +constexpr static A a2 = (A{}, A{});
> +template<typename T>
> +constexpr static A a3 = A{};
> +template<typename T>
> +constexpr static A a4 = {};
> +template<typename T>
> +constexpr static A a5{};
> +template<typename T>
> +constexpr static A a6 = (A{}, A{}, A{}, A{}, A{}, A{}, A{});
> +template<int N>
> +constexpr static B b1 = B{N};
> +template<int N>
> +constexpr static B b2 = (B{N}, B{N});
> +
> +template const A a1<int>;
> +template const A a2<int>;
> +template const A a3<int>;
> +template const A a4<int>;
> +template const A a5<int>;
> +template const A a6<int>;
> +template const B b1<42>;
> +template const B b2<42>;
> 
> base-commit: ee7d0c7b4102b8ef0bcaa725c75e141248b774cb
  

Patch

diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc
index b1176317d99..571f2e64d03 100644
--- a/gcc/cp/cvt.cc
+++ b/gcc/cp/cvt.cc
@@ -985,7 +985,9 @@  ocp_convert (tree type, tree expr, int convtype, int flags,
 					    type, flags, complain);
 	}
       if (ctor)
-	return build_cplus_new (type, ctor, complain);
+	return (processing_template_decl
+		? ctor
+		: build_cplus_new (type, ctor, complain));
     }
 
   if (complain & tf_error)
diff --git a/gcc/testsuite/g++.dg/cpp1y/var-templ89.C b/gcc/testsuite/g++.dg/cpp1y/var-templ89.C
new file mode 100644
index 00000000000..5f98a95580c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/var-templ89.C
@@ -0,0 +1,32 @@ 
+// PR c++/125539
+// { dg-do compile { target c++14 } }
+
+struct A {};
+struct B { int i; };
+constexpr void foo () {}
+
+template<typename T>
+constexpr static A a1 = (foo (), A {});
+template<typename T>
+constexpr static A a2 = (A{}, A{});
+template<typename T>
+constexpr static A a3 = A{};
+template<typename T>
+constexpr static A a4 = {};
+template<typename T>
+constexpr static A a5{};
+template<typename T>
+constexpr static A a6 = (A{}, A{}, A{}, A{}, A{}, A{}, A{});
+template<int N>
+constexpr static B b1 = B{N};
+template<int N>
+constexpr static B b2 = (B{N}, B{N});
+
+template const A a1<int>;
+template const A a2<int>;
+template const A a3<int>;
+template const A a4<int>;
+template const A a5<int>;
+template const A a6<int>;
+template const B b1<42>;
+template const B b2<42>;