[v2] c++: ICE with auto(0) in requires-expression [PR103408]

Message ID YagXOulx445ojtAo@redhat.com
State New
Headers
Series [v2] c++: ICE with auto(0) in requires-expression [PR103408] |

Commit Message

Marek Polacek Dec. 2, 2021, 12:45 a.m. UTC
  On Wed, Dec 01, 2021 at 12:17:47PM -0500, Patrick Palka wrote:
> On Wed, Dec 1, 2021 at 10:26 AM Marek Polacek via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
> >
> > Here we ICE on
> >
> >   int f() requires (auto(0));
> >
> > in do_auto_deduction when handling the auto: we're in a non-templated
> > requires-expression which are parsed under processing_template_decl == 1
> > and empty current_template_parms, so 'current_template_args ()' will
> > crash.  This code is invalid as per "C++20 CA378: Remove non-templated
> > constrained functions", but of course we shouldn't crash.
> 
> FWIW it looks like we can trip over the same bug with valid code:
> 
>   static_assert(requires { auto(0); });

Right, I hadn't thought of this at all.  So it's not just an ICE-on-invalid!

> > Since in the scenario above it's expected that current_template_parms is
> > null, I've just added a check, and let grokfndecl issue an error.
> > I guess another approch would be to fake up a template parameter list
> > before calling do_auto_deduction.
> >
> > For good measure, I've added several well-formed cases with auto(x) in
> > a requires-expression.
> >
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> >
> >         PR c++/103408
> >
> > gcc/cp/ChangeLog:
> >
> >         * pt.c (do_auto_deduction): Check current_template_parms before
> >         current_template_args ().
> >
> > gcc/testsuite/ChangeLog:
> >
> >         * g++.dg/cpp23/auto-fncast9.C: New test.
> > ---
> >  gcc/cp/pt.c                               |  2 +-
> >  gcc/testsuite/g++.dg/cpp23/auto-fncast9.C | 27 +++++++++++++++++++++++
> >  2 files changed, 28 insertions(+), 1 deletion(-)
> >  create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast9.C
> >
> > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > index f4b9d9673fb..012ca5d06c0 100644
> > --- a/gcc/cp/pt.c
> > +++ b/gcc/cp/pt.c
> > @@ -30041,7 +30041,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
> >         (but we still may have used them for constraint checking above).  */;
> >    else if (context == adc_unify)
> >      targs = add_to_template_args (outer_targs, targs);
> > -  else if (processing_template_decl)
> > +  else if (processing_template_decl && current_template_parms)
> >      targs = add_to_template_args (current_template_args (), targs);
> >    return tsubst (type, targs, complain, NULL_TREE);
> 
> Won't this mean the call to tsubst here will end up lowering the level
> of the auto from 2 to 1 rather than replacing it with the actual
> deduced type?

Yes, exactly -- targs has depth 1 but the level of auto is 2.  :(
 
> It also looks like this approach doesn't handle static_assert(requires
> { auto(auto(0)); }), probably due to this substitution issue.  I guess
> we could add a dummy level to 'targs' to work around this..

Thanks for pointing this out.  Here's a v2 which uses a dummy level.  The
description hopefully explains what's going on here...

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
Here we ICE on

  int f() requires (auto(0));

in do_auto_deduction when handling the auto: we're in a non-templated
requires-expression which are parsed under processing_template_decl == 1
and empty current_template_parms, so 'current_template_args ()' will
crash.  This code is invalid as per "C++20 CA378: Remove non-templated
constrained functions", but of course we shouldn't crash.  However, as
Patrick pointed out, this is valid:

static_assert(requires { auto(0); }); // #1
static_assert(requires { auto(auto(0)); }); // #2

To make #1 and #2 work, I'm playing games with faking up a dummy template
parameter level to current_template_parms, which need elaborating a bit.
autos are created with the level processing_template_decl + 1.  When we
call do_auto_deduction for the inner auto(0) in #2, the auto will therefore
have level 2.  type_unification_real in do_auto_deduction deduces targs to
<int> (TMPL_ARGS_DEPTH = 1), so the

  return tsubst (type, targs, complain, NULL_TREE);

will only reduce the level of the auto to 1.  So it remains undeduced,
causing the "invalid use of auto" error.  If I create the dummy template
parameter level, then this at the end of do_auto_deduction

  else if (processing_template_decl)
    targs = add_to_template_args (current_template_args (), targs);

transforms <int> into <, int>, which has TMPL_ARGS_DEPTH = 2 and so the
following tsubst actually replaces the auto with int, and things work.

For good measure, I've added several well-formed cases with auto(x) in
a requires-expression.

	PR c++/103408

gcc/cp/ChangeLog:

	* cp-tree.h (add_dummy_template_parameter_level): Declare.
	* pt.c (add_dummy_template_parameter_level): New function.
	(begin_template_parm_list): Use it.
	* semantics.c (finish_compound_literal): Call
	add_dummy_template_parameter_level if there are no template parameters.
	* typeck2.c (build_functional_cast_1): Likewise.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/auto-fncast9.C: New test.
---
 gcc/cp/cp-tree.h                          |  1 +
 gcc/cp/pt.c                               | 15 +++++++---
 gcc/cp/semantics.c                        |  3 ++
 gcc/cp/typeck2.c                          |  3 ++
 gcc/testsuite/g++.dg/cpp23/auto-fncast9.C | 35 +++++++++++++++++++++++
 5 files changed, 53 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast9.C


base-commit: 860c56b5bc356960a4d0445dadc43ceddbe3c7e2
  

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 1ee2c57e83c..ad220980476 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7174,6 +7174,7 @@  extern tree get_innermost_template_args		(tree, int);
 extern void maybe_begin_member_template_processing (tree);
 extern void maybe_end_member_template_processing (void);
 extern tree finish_member_template_decl		(tree);
+extern void add_dummy_template_parameter_level	();
 extern void begin_template_parm_list		(void);
 extern bool begin_specialization		(void);
 extern void reset_specialization		(void);
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index f4b9d9673fb..f1ef1dbde0f 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -689,6 +689,16 @@  strip_innermost_template_args (tree args, int extra_levels)
   return new_args;
 }
 
+/* Add a dummy template parameter level to current_template_parms.  */
+
+void
+add_dummy_template_parameter_level ()
+{
+  current_template_parms = tree_cons (size_int (processing_template_decl),
+				      make_tree_vec (0),
+				      current_template_parms);
+}
+
 /* We've got a template header coming up; push to a new level for storing
    the parms.  */
 
@@ -715,10 +725,7 @@  begin_template_parm_list (void)
   note_template_header (0);
 
   /* Add a dummy parameter level while we process the parameter list.  */
-  current_template_parms
-    = tree_cons (size_int (processing_template_decl),
-		 make_tree_vec (0),
-		 current_template_parms);
+  add_dummy_template_parameter_level ();
 }
 
 /* This routine is called when a specialization is declared.  If it is
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index cd1956497f8..7e2179412b2 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -3143,6 +3143,9 @@  finish_compound_literal (tree type, tree compound_literal,
 	pedwarn (input_location, OPT_Wc__23_extensions,
 		 "%<auto{x}%> only available with "
 		 "%<-std=c++2b%> or %<-std=gnu++2b%>");
+      auto ctp = make_temp_override (current_template_parms);
+      if (processing_template_decl && !current_template_parms)
+	add_dummy_template_parameter_level ();
       type = do_auto_deduction (type, compound_literal, type, complain,
 				adc_variable_type);
       if (type == error_mark_node)
diff --git a/gcc/cp/typeck2.c b/gcc/cp/typeck2.c
index 3fb651a02ba..af20315c9e4 100644
--- a/gcc/cp/typeck2.c
+++ b/gcc/cp/typeck2.c
@@ -2202,6 +2202,7 @@  build_functional_cast_1 (location_t loc, tree exp, tree parms,
   if (tree anode = type_uses_auto (type))
     {
       tree init;
+      auto ctp = make_temp_override (current_template_parms);
       if (CLASS_PLACEHOLDER_TEMPLATE (anode))
 	init = parms;
       /* C++23 auto(x).  */
@@ -2213,6 +2214,8 @@  build_functional_cast_1 (location_t loc, tree exp, tree parms,
 	    pedwarn (loc, OPT_Wc__23_extensions,
 		     "%<auto(x)%> only available with "
 		     "%<-std=c++2b%> or %<-std=gnu++2b%>");
+	  if (processing_template_decl && !current_template_parms)
+	    add_dummy_template_parameter_level ();
 	}
       else
 	{
diff --git a/gcc/testsuite/g++.dg/cpp23/auto-fncast9.C b/gcc/testsuite/g++.dg/cpp23/auto-fncast9.C
new file mode 100644
index 00000000000..b92a5f7b320
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/auto-fncast9.C
@@ -0,0 +1,35 @@ 
+// PR c++/103408
+// { dg-do compile { target c++23 } }
+
+int bad1() requires (auto(true)); // { dg-error "constraints on a non-templated function" }
+
+template<bool B>
+int f1() requires (auto(B));
+
+template<typename T>
+struct S { T t; constexpr operator bool() { return true; } };
+
+int bad2() requires (bool(S{1})); // { dg-error "constraints on a non-templated function" }
+int bad3() requires (bool(S(1))); // { dg-error "constraints on a non-templated function" }
+
+template<int N>
+int f2() requires (bool(S{N}));
+
+template<int N>
+int f3() requires (bool(S(N)));
+
+void
+g ()
+{
+  f1<true>();
+  f2<42>();
+  f3<42>();
+}
+
+static_assert(requires { auto(0); });
+static_assert(requires { auto(auto(0)); });
+static_assert(requires { auto(auto(auto(0))); });
+
+static_assert(requires { auto{0}; });
+static_assert(requires { auto{auto{0}}; });
+static_assert(requires { auto{auto{auto{0}}}; });