diff mbox series

C++ PATCH for c++/88294 - ICE with non-constant noexcept-specifier

Message ID 20190220203109.GA20014@redhat.com
State New
Headers show
Series C++ PATCH for c++/88294 - ICE with non-constant noexcept-specifier | expand

Commit Message

Marek Polacek Feb. 20, 2019, 8:31 p.m. UTC
Here we ICE when substituting a deferred noexcept-specifier, because it
contains 'this', a PARM_DECL, in an evaluated context.  This is different
from "noexcept(noexcept(this))" where the noexcept operator's operand is
an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
of a 'this' PARM_DECL:
15488           gcc_assert (cp_unevaluated_operand != 0)
It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
since we only check the expression's constness after substituting in
maybe_instantiate_noexcept, one fix would be the following.

This is not just about 'this', as the 87844 test shows: here we also have
a parameter whose value we're trying to determine -- it's a template arg
of a late-specified return type.  Returning the original value in tsubst_copy
and leaving the later code to complain seems to work here as well.  Just
removing the assert should work as well.

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

2019-02-20  Marek Polacek  <polacek@redhat.com>

	PR c++/88294 - ICE with non-constant noexcept-specifier.
	PR c++/87844 - ICE with non-constant template argument.
	* pt.c (tsubst_copy): Turn gcc_assert into a condition.

	* g++.dg/cpp0x/noexcept34.C: New test.
	* g++.dg/cpp1y/auto-fn56.C: New test.

Comments

Jason Merrill Feb. 20, 2019, 11:53 p.m. UTC | #1
On 2/20/19 10:31 AM, Marek Polacek wrote:
> Here we ICE when substituting a deferred noexcept-specifier, because it
> contains 'this', a PARM_DECL, in an evaluated context.  This is different
> from "noexcept(noexcept(this))" where the noexcept operator's operand is
> an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
> of a 'this' PARM_DECL:
> 15488           gcc_assert (cp_unevaluated_operand != 0)
> It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
> since we only check the expression's constness after substituting in
> maybe_instantiate_noexcept, one fix would be the following.
> 
> This is not just about 'this', as the 87844 test shows: here we also have
> a parameter whose value we're trying to determine -- it's a template arg
> of a late-specified return type.  Returning the original value in tsubst_copy
> and leaving the later code to complain seems to work here as well.  Just
> removing the assert should work as well.

I'm reluctant to mess with this assert, as it catches a lot of lambda bugs.

I think we want to register_parameter_specializations when instantiating 
deferred noexcept, so that tsubst_copy can find the right decls.

Jason
Marek Polacek Feb. 21, 2019, 8:56 p.m. UTC | #2
On Wed, Feb 20, 2019 at 01:53:18PM -1000, Jason Merrill wrote:
> On 2/20/19 10:31 AM, Marek Polacek wrote:
> > Here we ICE when substituting a deferred noexcept-specifier, because it
> > contains 'this', a PARM_DECL, in an evaluated context.  This is different
> > from "noexcept(noexcept(this))" where the noexcept operator's operand is
> > an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
> > of a 'this' PARM_DECL:
> > 15488           gcc_assert (cp_unevaluated_operand != 0)
> > It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
> > since we only check the expression's constness after substituting in
> > maybe_instantiate_noexcept, one fix would be the following.
> > 
> > This is not just about 'this', as the 87844 test shows: here we also have
> > a parameter whose value we're trying to determine -- it's a template arg
> > of a late-specified return type.  Returning the original value in tsubst_copy
> > and leaving the later code to complain seems to work here as well.  Just
> > removing the assert should work as well.
> 
> I'm reluctant to mess with this assert, as it catches a lot of lambda bugs.

Makes sense -- I wasn't aware of that.

> I think we want to register_parameter_specializations when instantiating
> deferred noexcept, so that tsubst_copy can find the right decls.

Thanks for the suggestion -- that works with one catch: we need to replace the
fake 'this' in the noexcept- specifier with a real 'this' (the template one),
one that has DECL_CONTEXT set.  The fake one comes from inject_this_parameter,
when we were parsing the noexcept-specifier.  The real one was set in grokfndecl.

Before calling register_parameter_specializations one apparently has to set up
a local specializations stack.

Does this look reasonable?  It no longer fixes the other PR; we'll likely have
to play some register_local_specialization games there, too.

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

2019-02-21  Marek Polacek  <polacek@redhat.com>

	PR c++/88294 - ICE with non-constant noexcept-specifier.
	* pt.c (replace_this_r): New function.
	(maybe_instantiate_noexcept): Set up the list of local
	specializations.  Replace the fake 'this' with the real one.

	* g++.dg/cpp0x/noexcept34.C: New test.

diff --git gcc/cp/pt.c gcc/cp/pt.c
index a212be8c747..abbec8f53b9 100644
--- gcc/cp/pt.c
+++ gcc/cp/pt.c
@@ -24158,6 +24158,20 @@ always_instantiate_p (tree decl)
 	      && decl_maybe_constant_var_p (decl)));
 }
 
+/* Replace all fake 'this' parameters (i.e. the ones created by
+   inject_this_parameter) with the real 'this'.  */
+
+static tree
+replace_this_r (tree *t, int *, void *data_)
+{
+  tree *d = static_cast<tree *>(data_);
+  if (TREE_CODE (*t) == PARM_DECL
+      && DECL_NAME (*t) == this_identifier
+      && DECL_CONTEXT (*t) == NULL_TREE)
+    *t = *d;
+  return NULL_TREE;
+}
+
 /* If FN has a noexcept-specifier that hasn't been instantiated yet,
    instantiate it now, modifying TREE_TYPE (fn).  Returns false on
    error, true otherwise.  */
@@ -24203,12 +24217,34 @@ maybe_instantiate_noexcept (tree fn, tsubst_flags_t complain)
 	  push_access_scope (fn);
 	  push_deferring_access_checks (dk_no_deferred);
 	  input_location = DECL_SOURCE_LOCATION (fn);
-	  noex = tsubst_copy_and_build (DEFERRED_NOEXCEPT_PATTERN (noex),
-					DEFERRED_NOEXCEPT_ARGS (noex),
-					tf_warning_or_error, fn,
-					/*function_p=*/false,
-					/*integral_constant_expression_p=*/true);
-	  spec = build_noexcept_spec (noex, tf_warning_or_error);
+
+	  /* A new stack interferes with pop_access_scope.  */
+	  {
+	    /* Set up the list of local specializations.  */
+	    local_specialization_stack lss (lss_copy);
+
+	    /* Replace the fake 'this' with the real 'this'.  */
+	    tree tdecl = DECL_TEMPLATE_RESULT (DECL_TI_TEMPLATE (fn));
+	    if (DECL_NONSTATIC_MEMBER_FUNCTION_P (tdecl))
+	      {
+		tree real_this = DECL_ARGUMENTS (tdecl);
+		tree *p = &DEFERRED_NOEXCEPT_PATTERN (noex);
+		cp_walk_tree_without_duplicates (p, replace_this_r,
+						 &real_this);
+	      }
+
+	    /* Create substitution entries for the parameters.  */
+	    register_parameter_specializations (tdecl, fn);
+
+	    /* Do deferred instantiation of the noexcept-specifier.  */
+	    noex = tsubst_copy_and_build (DEFERRED_NOEXCEPT_PATTERN (noex),
+					  DEFERRED_NOEXCEPT_ARGS (noex),
+					  tf_warning_or_error, fn,
+					  /*function_p=*/false,
+					  /*i_c_e_p=*/true);
+	    spec = build_noexcept_spec (noex, tf_warning_or_error);
+	  }
+
 	  pop_deferring_access_checks ();
 	  pop_access_scope (fn);
 	  pop_tinst_level ();
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept34.C gcc/testsuite/g++.dg/cpp0x/noexcept34.C
new file mode 100644
index 00000000000..495cc260d9b
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept34.C
@@ -0,0 +1,29 @@
+// PR c++/88294
+// { dg-do compile { target c++11 } }
+
+constexpr int foo (bool b) { return b; }
+
+template<typename> struct A
+{
+  constexpr int f () { return 0; }
+  bool b = true;
+  void g () noexcept (f()) { } // { dg-error ".this. is not a constant expression" }
+  void g2 () noexcept (this->f()) { } // { dg-error ".this. is not a constant expression" }
+  void g3 () noexcept (b) { } // { dg-error ".this. is not a constant expression|use of .this." }
+  void g4 (int i) noexcept (i) { } // { dg-error "use of parameter outside function body" }
+  void g5 () noexcept (A::f()) { } // { dg-error ".this. is not a constant expression" }
+  void g6 () noexcept (foo(b)) { } // { dg-error ".this. is not a constant expression|use of .this. in a constant expression" }
+  void g7 () noexcept (int{f()}) { } // { dg-error ".this. is not a constant expression" }
+};
+
+int main ()
+{
+  A<int> a;
+  a.g ();
+  a.g2 ();
+  a.g3 ();
+  a.g4 (1);
+  a.g5 ();
+  a.g6 ();
+  a.g7 ();
+}
Jason Merrill Feb. 21, 2019, 9:22 p.m. UTC | #3
On 2/21/19 10:56 AM, Marek Polacek wrote:
> On Wed, Feb 20, 2019 at 01:53:18PM -1000, Jason Merrill wrote:
>> On 2/20/19 10:31 AM, Marek Polacek wrote:
>>> Here we ICE when substituting a deferred noexcept-specifier, because it
>>> contains 'this', a PARM_DECL, in an evaluated context.  This is different
>>> from "noexcept(noexcept(this))" where the noexcept operator's operand is
>>> an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
>>> of a 'this' PARM_DECL:
>>> 15488           gcc_assert (cp_unevaluated_operand != 0)
>>> It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
>>> since we only check the expression's constness after substituting in
>>> maybe_instantiate_noexcept, one fix would be the following.
>>>
>>> This is not just about 'this', as the 87844 test shows: here we also have
>>> a parameter whose value we're trying to determine -- it's a template arg
>>> of a late-specified return type.  Returning the original value in tsubst_copy
>>> and leaving the later code to complain seems to work here as well.  Just
>>> removing the assert should work as well.
>>
>> I'm reluctant to mess with this assert, as it catches a lot of lambda bugs.
> 
> Makes sense -- I wasn't aware of that.
> 
>> I think we want to register_parameter_specializations when instantiating
>> deferred noexcept, so that tsubst_copy can find the right decls.
> 
> Thanks for the suggestion -- that works with one catch: we need to replace the
> fake 'this' in the noexcept- specifier with a real 'this' (the template one),
> one that has DECL_CONTEXT set.  The fake one comes from inject_this_parameter,
> when we were parsing the noexcept-specifier.  The real one was set in grokfndecl.

If you set current_class_ptr appropriately tsubst_copy will use it, so 
you shouldn't need to do a walk_tree.

Jason
Marek Polacek Feb. 21, 2019, 11:34 p.m. UTC | #4
On Thu, Feb 21, 2019 at 11:22:41AM -1000, Jason Merrill wrote:
> On 2/21/19 10:56 AM, Marek Polacek wrote:
> > On Wed, Feb 20, 2019 at 01:53:18PM -1000, Jason Merrill wrote:
> > > On 2/20/19 10:31 AM, Marek Polacek wrote:
> > > > Here we ICE when substituting a deferred noexcept-specifier, because it
> > > > contains 'this', a PARM_DECL, in an evaluated context.  This is different
> > > > from "noexcept(noexcept(this))" where the noexcept operator's operand is
> > > > an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
> > > > of a 'this' PARM_DECL:
> > > > 15488           gcc_assert (cp_unevaluated_operand != 0)
> > > > It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
> > > > since we only check the expression's constness after substituting in
> > > > maybe_instantiate_noexcept, one fix would be the following.
> > > > 
> > > > This is not just about 'this', as the 87844 test shows: here we also have
> > > > a parameter whose value we're trying to determine -- it's a template arg
> > > > of a late-specified return type.  Returning the original value in tsubst_copy
> > > > and leaving the later code to complain seems to work here as well.  Just
> > > > removing the assert should work as well.
> > > 
> > > I'm reluctant to mess with this assert, as it catches a lot of lambda bugs.
> > 
> > Makes sense -- I wasn't aware of that.
> > 
> > > I think we want to register_parameter_specializations when instantiating
> > > deferred noexcept, so that tsubst_copy can find the right decls.
> > 
> > Thanks for the suggestion -- that works with one catch: we need to replace the
> > fake 'this' in the noexcept- specifier with a real 'this' (the template one),
> > one that has DECL_CONTEXT set.  The fake one comes from inject_this_parameter,
> > when we were parsing the noexcept-specifier.  The real one was set in grokfndecl.
> 
> If you set current_class_ptr appropriately tsubst_copy will use it, so you
> shouldn't need to do a walk_tree.

Unfortunately that broke a lot of libstdc++ tests.  I can poke at it more if
you feel stronly about it.

Marek
Jason Merrill Feb. 22, 2019, 12:57 a.m. UTC | #5
On 2/21/19 1:34 PM, Marek Polacek wrote:
> On Thu, Feb 21, 2019 at 11:22:41AM -1000, Jason Merrill wrote:
>> On 2/21/19 10:56 AM, Marek Polacek wrote:
>>> On Wed, Feb 20, 2019 at 01:53:18PM -1000, Jason Merrill wrote:
>>>> On 2/20/19 10:31 AM, Marek Polacek wrote:
>>>>> Here we ICE when substituting a deferred noexcept-specifier, because it
>>>>> contains 'this', a PARM_DECL, in an evaluated context.  This is different
>>>>> from "noexcept(noexcept(this))" where the noexcept operator's operand is
>>>>> an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
>>>>> of a 'this' PARM_DECL:
>>>>> 15488           gcc_assert (cp_unevaluated_operand != 0)
>>>>> It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
>>>>> since we only check the expression's constness after substituting in
>>>>> maybe_instantiate_noexcept, one fix would be the following.
>>>>>
>>>>> This is not just about 'this', as the 87844 test shows: here we also have
>>>>> a parameter whose value we're trying to determine -- it's a template arg
>>>>> of a late-specified return type.  Returning the original value in tsubst_copy
>>>>> and leaving the later code to complain seems to work here as well.  Just
>>>>> removing the assert should work as well.
>>>>
>>>> I'm reluctant to mess with this assert, as it catches a lot of lambda bugs.
>>>
>>> Makes sense -- I wasn't aware of that.
>>>
>>>> I think we want to register_parameter_specializations when instantiating
>>>> deferred noexcept, so that tsubst_copy can find the right decls.
>>>
>>> Thanks for the suggestion -- that works with one catch: we need to replace the
>>> fake 'this' in the noexcept- specifier with a real 'this' (the template one),
>>> one that has DECL_CONTEXT set.  The fake one comes from inject_this_parameter,
>>> when we were parsing the noexcept-specifier.  The real one was set in grokfndecl.
>>
>> If you set current_class_ptr appropriately tsubst_copy will use it, so you
>> shouldn't need to do a walk_tree.
> 
> Unfortunately that broke a lot of libstdc++ tests.  I can poke at it more if
> you feel stronly about it.

Please do poke a bit more, that surprises me.

Jason
Marek Polacek Feb. 22, 2019, 11:17 p.m. UTC | #6
On Thu, Feb 21, 2019 at 02:57:28PM -1000, Jason Merrill wrote:
> On 2/21/19 1:34 PM, Marek Polacek wrote:
> > On Thu, Feb 21, 2019 at 11:22:41AM -1000, Jason Merrill wrote:
> > > On 2/21/19 10:56 AM, Marek Polacek wrote:
> > > > On Wed, Feb 20, 2019 at 01:53:18PM -1000, Jason Merrill wrote:
> > > > > On 2/20/19 10:31 AM, Marek Polacek wrote:
> > > > > > Here we ICE when substituting a deferred noexcept-specifier, because it
> > > > > > contains 'this', a PARM_DECL, in an evaluated context.  This is different
> > > > > > from "noexcept(noexcept(this))" where the noexcept operator's operand is
> > > > > > an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
> > > > > > of a 'this' PARM_DECL:
> > > > > > 15488           gcc_assert (cp_unevaluated_operand != 0)
> > > > > > It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
> > > > > > since we only check the expression's constness after substituting in
> > > > > > maybe_instantiate_noexcept, one fix would be the following.
> > > > > > 
> > > > > > This is not just about 'this', as the 87844 test shows: here we also have
> > > > > > a parameter whose value we're trying to determine -- it's a template arg
> > > > > > of a late-specified return type.  Returning the original value in tsubst_copy
> > > > > > and leaving the later code to complain seems to work here as well.  Just
> > > > > > removing the assert should work as well.
> > > > > 
> > > > > I'm reluctant to mess with this assert, as it catches a lot of lambda bugs.
> > > > 
> > > > Makes sense -- I wasn't aware of that.
> > > > 
> > > > > I think we want to register_parameter_specializations when instantiating
> > > > > deferred noexcept, so that tsubst_copy can find the right decls.
> > > > 
> > > > Thanks for the suggestion -- that works with one catch: we need to replace the
> > > > fake 'this' in the noexcept- specifier with a real 'this' (the template one),
> > > > one that has DECL_CONTEXT set.  The fake one comes from inject_this_parameter,
> > > > when we were parsing the noexcept-specifier.  The real one was set in grokfndecl.
> > > 
> > > If you set current_class_ptr appropriately tsubst_copy will use it, so you
> > > shouldn't need to do a walk_tree.
> > 
> > Unfortunately that broke a lot of libstdc++ tests.  I can poke at it more if
> > you feel stronly about it.
> 
> Please do poke a bit more, that surprises me.

Apparently *someone* needs to learn how to properly restore c_c_{ptr,ref}...

noexcept35.C is a new test reduced from something from libstdc++.  Since it
exercises codepaths that nothing in dg.exp does, I think it's worth adding.

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

2019-02-22  Marek Polacek  <polacek@redhat.com>

	PR c++/88294 - ICE with non-constant noexcept-specifier.
	* pt.c (maybe_instantiate_noexcept): Set up the list of local
	specializations.  Set current_class_{ptr,ref}.

	* g++.dg/cpp0x/noexcept34.C: New test.
	* g++.dg/cpp0x/noexcept35.C: New test.

diff --git gcc/cp/pt.c gcc/cp/pt.c
index 42dd095a6b0..d678e278078 100644
--- gcc/cp/pt.c
+++ gcc/cp/pt.c
@@ -24203,12 +24203,39 @@ maybe_instantiate_noexcept (tree fn, tsubst_flags_t complain)
 	  push_access_scope (fn);
 	  push_deferring_access_checks (dk_no_deferred);
 	  input_location = DECL_SOURCE_LOCATION (fn);
-	  noex = tsubst_copy_and_build (DEFERRED_NOEXCEPT_PATTERN (noex),
-					DEFERRED_NOEXCEPT_ARGS (noex),
-					tf_warning_or_error, fn,
-					/*function_p=*/false,
-					/*integral_constant_expression_p=*/true);
-	  spec = build_noexcept_spec (noex, tf_warning_or_error);
+
+	  /* A new stack interferes with pop_access_scope.  */
+	  {
+	    /* Set up the list of local specializations.  */
+	    local_specialization_stack lss (lss_copy);
+
+	    tree save_ccp = current_class_ptr;
+	    tree save_ccr = current_class_ref;
+	    /* If needed, set current_class_ptr for the benefit of
+	       tsubst_copy/PARM_DECL.  */
+	    tree tdecl = DECL_TEMPLATE_RESULT (DECL_TI_TEMPLATE (fn));
+	    if (DECL_NONSTATIC_MEMBER_FUNCTION_P (tdecl))
+	      {
+		tree this_parm = DECL_ARGUMENTS (tdecl);
+		current_class_ptr = NULL_TREE;
+		current_class_ref = cp_build_fold_indirect_ref (this_parm);
+		current_class_ptr = this_parm;
+	      }
+
+	    /* Create substitution entries for the parameters.  */
+	    register_parameter_specializations (tdecl, fn);
+
+	    /* Do deferred instantiation of the noexcept-specifier.  */
+	    noex = tsubst_copy_and_build (DEFERRED_NOEXCEPT_PATTERN (noex),
+					  DEFERRED_NOEXCEPT_ARGS (noex),
+					  tf_warning_or_error, fn,
+					  /*function_p=*/false,
+					  /*i_c_e_p=*/true);
+	    current_class_ptr = save_ccp;
+	    current_class_ref = save_ccr;
+	    spec = build_noexcept_spec (noex, tf_warning_or_error);
+	  }
+
 	  pop_deferring_access_checks ();
 	  pop_access_scope (fn);
 	  pop_tinst_level ();
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept34.C gcc/testsuite/g++.dg/cpp0x/noexcept34.C
new file mode 100644
index 00000000000..dce35652ef5
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept34.C
@@ -0,0 +1,29 @@
+// PR c++/88294
+// { dg-do compile { target c++11 } }
+
+constexpr int foo (bool b) { return b; }
+
+template<typename> struct A
+{
+  constexpr int f () { return 0; }
+  bool b = true;
+  void g () noexcept (f()) { } // { dg-error "use of parameter" }
+  void g2 () noexcept (this->f()) { } // { dg-error "use of parameter" }
+  void g3 () noexcept (b) { } // { dg-error "use of .this. in a constant expression|use of parameter" }
+  void g4 (int i) noexcept (i) { } // { dg-error "use of parameter" }
+  void g5 () noexcept (A::f()) { } // { dg-error "use of parameter" }
+  void g6 () noexcept (foo(b)) { } // { dg-error "use of .this. in a constant expression|use of parameter" }
+  void g7 () noexcept (int{f()}) { } // { dg-error "use of parameter" }
+};
+
+int main ()
+{
+  A<int> a;
+  a.g ();
+  a.g2 ();
+  a.g3 ();
+  a.g4 (1);
+  a.g5 ();
+  a.g6 ();
+  a.g7 ();
+}
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept35.C gcc/testsuite/g++.dg/cpp0x/noexcept35.C
new file mode 100644
index 00000000000..8606b1ad28c
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept35.C
@@ -0,0 +1,21 @@
+// { dg-do compile { target c++11 } }
+
+template <typename _Tp, _Tp __v> struct A { static constexpr _Tp value = __v; };
+typedef A<bool, false> false_type;
+struct is_same : false_type {};
+template <bool> struct enable_if;
+template <typename> using __remove_cvref_t = int;
+template <typename _Tp> class reference_wrapper {
+  static _Tp _S_fun();
+  template <typename _Up, typename = __remove_cvref_t<_Up>>
+  using __not_same = enable_if<is_same::value>;
+
+public:
+  template <typename _Up, typename = __not_same<_Up>>
+  reference_wrapper(_Up) noexcept(noexcept(reference_wrapper::_S_fun));
+};
+
+reference_wrapper<int> fn1() {
+  int __t = 10;
+  return __t;
+}
Jason Merrill Feb. 23, 2019, 7:16 a.m. UTC | #7
On Fri, Feb 22, 2019 at 1:17 PM Marek Polacek <polacek@redhat.com> wrote:
> On Thu, Feb 21, 2019 at 02:57:28PM -1000, Jason Merrill wrote:
> > On 2/21/19 1:34 PM, Marek Polacek wrote:
> > > On Thu, Feb 21, 2019 at 11:22:41AM -1000, Jason Merrill wrote:
> > > > On 2/21/19 10:56 AM, Marek Polacek wrote:
> > > > > On Wed, Feb 20, 2019 at 01:53:18PM -1000, Jason Merrill wrote:
> > > > > > On 2/20/19 10:31 AM, Marek Polacek wrote:
> > > > > > > Here we ICE when substituting a deferred noexcept-specifier, because it
> > > > > > > contains 'this', a PARM_DECL, in an evaluated context.  This is different
> > > > > > > from "noexcept(noexcept(this))" where the noexcept operator's operand is
> > > > > > > an unevaluated operand.  We crash within tsubst_copy's PARM_DECL handling
> > > > > > > of a 'this' PARM_DECL:
> > > > > > > 15488           gcc_assert (cp_unevaluated_operand != 0)
> > > > > > > It'd be wrong to mess with cp_unevaluated_operand (or current_class_*), and
> > > > > > > since we only check the expression's constness after substituting in
> > > > > > > maybe_instantiate_noexcept, one fix would be the following.
> > > > > > >
> > > > > > > This is not just about 'this', as the 87844 test shows: here we also have
> > > > > > > a parameter whose value we're trying to determine -- it's a template arg
> > > > > > > of a late-specified return type.  Returning the original value in tsubst_copy
> > > > > > > and leaving the later code to complain seems to work here as well.  Just
> > > > > > > removing the assert should work as well.
> > > > > >
> > > > > > I'm reluctant to mess with this assert, as it catches a lot of lambda bugs.
> > > > >
> > > > > Makes sense -- I wasn't aware of that.
> > > > >
> > > > > > I think we want to register_parameter_specializations when instantiating
> > > > > > deferred noexcept, so that tsubst_copy can find the right decls.
> > > > >
> > > > > Thanks for the suggestion -- that works with one catch: we need to replace the
> > > > > fake 'this' in the noexcept- specifier with a real 'this' (the template one),
> > > > > one that has DECL_CONTEXT set.  The fake one comes from inject_this_parameter,
> > > > > when we were parsing the noexcept-specifier.  The real one was set in grokfndecl.
> > > >
> > > > If you set current_class_ptr appropriately tsubst_copy will use it, so you
> > > > shouldn't need to do a walk_tree.
> > >
> > > Unfortunately that broke a lot of libstdc++ tests.  I can poke at it more if
> > > you feel stronly about it.
> >
> > Please do poke a bit more, that surprises me.
>
> Apparently *someone* needs to learn how to properly restore c_c_{ptr,ref}...
>
> noexcept35.C is a new test reduced from something from libstdc++.  Since it
> exercises codepaths that nothing in dg.exp does, I think it's worth adding.
>
> Bootstrapped/regtested on x86_64-linux, ok for trunk?
>
> 2019-02-22  Marek Polacek  <polacek@redhat.com>
>
>         PR c++/88294 - ICE with non-constant noexcept-specifier.
>         * pt.c (maybe_instantiate_noexcept): Set up the list of local
>         specializations.  Set current_class_{ptr,ref}.

OK, thanks.

Jason
diff mbox series

Patch

diff --git gcc/cp/pt.c gcc/cp/pt.c
index 8c5a1b312fc..5286b7dac59 100644
--- gcc/cp/pt.c
+++ gcc/cp/pt.c
@@ -15484,8 +15484,12 @@  tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 
 	  /* This can happen for a parameter name used later in a function
 	     declaration (such as in a late-specified return type).  Just
-	     make a dummy decl, since it's only used for its type.  */
-	  gcc_assert (cp_unevaluated_operand != 0);
+	     make a dummy decl, since it's only used for its type.
+	     We may also end up here in invalid code trying to get a value
+	     of a parameter when parsing a deferred noexcept.  Just return
+	     the original value so that we can issue an error later.  */
+	  if (cp_unevaluated_operand == 0)
+	    return t;
 	  r = tsubst_decl (t, args, complain);
 	  /* Give it the template pattern as its context; its true context
 	     hasn't been instantiated yet and this is good enough for
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept34.C gcc/testsuite/g++.dg/cpp0x/noexcept34.C
new file mode 100644
index 00000000000..495cc260d9b
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept34.C
@@ -0,0 +1,29 @@ 
+// PR c++/88294
+// { dg-do compile { target c++11 } }
+
+constexpr int foo (bool b) { return b; }
+
+template<typename> struct A
+{
+  constexpr int f () { return 0; }
+  bool b = true;
+  void g () noexcept (f()) { } // { dg-error ".this. is not a constant expression" }
+  void g2 () noexcept (this->f()) { } // { dg-error ".this. is not a constant expression" }
+  void g3 () noexcept (b) { } // { dg-error ".this. is not a constant expression|use of .this." }
+  void g4 (int i) noexcept (i) { } // { dg-error "use of parameter outside function body" }
+  void g5 () noexcept (A::f()) { } // { dg-error ".this. is not a constant expression" }
+  void g6 () noexcept (foo(b)) { } // { dg-error ".this. is not a constant expression|use of .this. in a constant expression" }
+  void g7 () noexcept (int{f()}) { } // { dg-error ".this. is not a constant expression" }
+};
+
+int main ()
+{
+  A<int> a;
+  a.g ();
+  a.g2 ();
+  a.g3 ();
+  a.g4 (1);
+  a.g5 ();
+  a.g6 ();
+  a.g7 ();
+}
diff --git gcc/testsuite/g++.dg/cpp1y/auto-fn56.C gcc/testsuite/g++.dg/cpp1y/auto-fn56.C
new file mode 100644
index 00000000000..ecf8a0b776e
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp1y/auto-fn56.C
@@ -0,0 +1,18 @@ 
+// PR c++/87844
+// { dg-do compile { target c++14 } }
+
+struct C {
+  static constexpr bool call(bool) { return true; }
+};
+
+template<bool b>
+struct B {};
+
+auto foo(bool b) {
+  auto f = [](auto c) -> B<decltype(c)::call(b)> { }; // { dg-error "use of parameter from containing function" }
+  f(C()); // { dg-error "no match for call" }
+}
+
+int main() {
+  foo(true);
+}