Patchwork [C++,RFC,/] PR 54080, PR 52875 and more (aka SFINAE vs template recursion depth)

login
register
mail settings
Submitter Paolo Carlini
Date Aug. 8, 2013, 7:54 p.m.
Message ID <5203F75A.9080206@oracle.com>
Download mbox | patch
Permalink /patch/265804/
State New
Headers show

Comments

Paolo Carlini - Aug. 8, 2013, 7:54 p.m.
Hi,

this is, IMHO, a rather interesting issue. I was working on PR 54080, 
where currently with ICE pretty badly for Error reporting routines 
re-entered without producing any sensible error message. Figured out 
that the core of the issue are the error messages in push_tinst_level 
about template instantiation depth exceeded. Drafted the attached, the 
error message is great with no ICE:

54080.C: In instantiation of ‘decltype (foo<template<class T> class 
vector>(foo(input, func1), funcrest)) foo(const vector<int>&, const 
Func1&, FuncRest) [with OutType = vector; Func1 = int; FuncRest = int; 
decltype (foo<vector>(foo(input, func1), funcrest)) = vector<int>]’:
54080.C:25:22: required from here
54080.C:19:2: error: return-statement with no value, in function 
returning ‘vector<int>’ [-fpermissive]
return;

when I ran the testsuite, I found something (tidied):

FAIL: g++.dg/cpp0x/decltype26.C (test for errors, line 6)
FAIL: g++.dg/cpp0x/decltype26.C (test for excess errors)
FAIL: g++.dg/cpp0x/decltype28.C (test for errors, line 11)
FAIL: g++.dg/cpp0x/decltype28.C (test for warnings, line 15)
FAIL: g++.dg/cpp0x/decltype29.C (test for errors, line 12)
FAIL: g++.dg/cpp0x/decltype29.C (test for excess errors)

the really interesting one is decltype28.C, which we don't reject 
anymore, we simply accept it. What is happening is that the overload 
which leads to excessive template instantiation depth is SFINAE-ed away 
and the other one "wins"! Thus, this is the core of my message: it seems 
that we behave wrt this issue - SFINAE vs template instantiation depth - 
in a different way vs current clang++ and icc: we produce hard error 
messages in SFINAE contexts. Is that intended? I find the issue 
interesting, arguably a template instantiation depth exceeded isn't just 
like any other error.

With the attached draft we handle quite a few other testcases I have 
seen around in a different way, for example c++/52875 is automatically 
fixed (assuming we want to behave like clang++ and icc).

Thanks!
Paolo.
Jason Merrill - Aug. 8, 2013, 11:40 p.m.
On 08/08/2013 03:54 PM, Paolo Carlini wrote:
> the really interesting one is decltype28.C, which we don't reject
> anymore, we simply accept it. What is happening is that the overload
> which leads to excessive template instantiation depth is SFINAE-ed away
> and the other one "wins"! Thus, this is the core of my message: it seems
> that we behave wrt this issue - SFINAE vs template instantiation depth -
> in a different way vs current clang++ and icc: we produce hard error
> messages in SFINAE contexts. Is that intended?

Yes, that is intended.  Changing that could mean that the meaning of 
code depends on what max depth the user selected.

Jason
Paolo Carlini - Aug. 9, 2013, 6:43 a.m.
Hi,

>Yes, that is intended.  Changing that could mean that the meaning of
>code depends on what max depth the user selected.

Indeed. Yesterday I wondered what would happen if the front-end had a way to detect, in some very specific and special cases only of course, really infinite recursions, in the sense that no increase in the depth would "cure" them. Would it be ok in that case to sfinae away?

Paolo
Paolo Carlini - Aug. 9, 2013, 7:13 a.m.
.. another thought I had, less esoteric ;) is the following: we use 
tf_none for two rather different reasons: for SFINAE and to avoid 
recursive Error routines calls, when we call tsubst (... tf_none, ...) 
from dump_template_bindings.

I understand, given your reply, that in general in the first case, thus 
SFINAE, we should avoid all hard errors *but* the one about too deep 
recursions (barring some sort of powerful infinite recursion detector). 
What about the second case, however? Should it be different? An error 
message is being produced in any case, for a reason or another, it 
shouldn't be prevented or made more difficult only because there is deep 
recursion somewhere. I think that in that second case we should suppress 
the error message about too deep recursion too. But how to tell it 
apart? Looks like we want some sort of separate tf_*, a 
tf_in_diagnostic, or something, very similar to tf_none, but truly with 
no exceptions. Actually this vague idea occured to me a number of times, 
I think having that would help in a number of situations.

What do you think?

Thanks,
Paolo.

(*) Maybe there is third one, like in some recent tweaks Jakub did, but 
let's leave it alone here.
Florian Weimer - Aug. 9, 2013, 7:13 a.m.
On 08/09/2013 08:43 AM, Paolo Carlini wrote:

>> Yes, that is intended.  Changing that could mean that the meaning of
>> code depends on what max depth the user selected.
>
> Indeed. Yesterday I wondered what would happen if the front-end had a way to detect, in some very specific and special cases only of course, really infinite recursions, in the sense that no increase in the depth would "cure" them. Would it be ok in that case to sfinae away?

Eh, hopefully not.  Otherwise, program behavior would depend on how well 
the compiler solves the halting problem.

It's interesting question, but hopefully we can make all errors due to 
exceeded implementation limits hard errors, not subject to SFINAE.
Paolo Carlini - Aug. 9, 2013, 7:28 a.m.
Hi,

Florian Weimer <fweimer@redhat.com> ha scritto:
>On 08/09/2013 08:43 AM, Paolo Carlini wrote:
>
>>> Yes, that is intended.  Changing that could mean that the meaning of
>>> code depends on what max depth the user selected.
>>
>> Indeed. Yesterday I wondered what would happen if the front-end had a
>way to detect, in some very specific and special cases only of course,
>really infinite recursions, in the sense that no increase in the depth
>would "cure" them. Would it be ok in that case to sfinae away?
>
>Eh, hopefully not.  Otherwise, program behavior would depend on how
>well
>the compiler solves the halting problem.

I see. You know, I was trying to figure out the logic other compilers - two of them, actually - are following, because the really appear to sfinae away infinite recursions. Was trying to imagine cases in which it would be safe.

Paolo
Florian Weimer - Aug. 9, 2013, 7:33 a.m.
On 08/09/2013 09:28 AM, Paolo Carlini wrote:

> I see. You know, I was trying to figure out the logic other compilers - two of them, actually - are following, because the really appear to sfinae away infinite recursions. Was trying to imagine cases in which it would be safe.

Could their behavior just be bugs?  Depending on their error recovery 
implementation, not flagging infinite recursion as a hard error in 
SFINAE context could be an easy mistake to make.
Paolo Carlini - Aug. 9, 2013, 8:20 a.m.
Hi,

Florian Weimer <fweimer@redhat.com> ha scritto:
>Could their behavior just be bugs?  Depending on their error recovery
>implementation, not flagging infinite recursion as a hard error in
>SFINAE context could be an easy mistake to make.

Sure can be. In a sense, as I tried to explain in another sub-thread, we do have the complementary one.

Paolo
Gabriel Dos Reis - Aug. 9, 2013, 8:40 a.m.
On Thu, Aug 8, 2013 at 6:40 PM, Jason Merrill <jason@redhat.com> wrote:
> On 08/08/2013 03:54 PM, Paolo Carlini wrote:
>>
>> the really interesting one is decltype28.C, which we don't reject
>> anymore, we simply accept it. What is happening is that the overload
>> which leads to excessive template instantiation depth is SFINAE-ed away
>> and the other one "wins"! Thus, this is the core of my message: it seems
>> that we behave wrt this issue - SFINAE vs template instantiation depth -
>> in a different way vs current clang++ and icc: we produce hard error
>> messages in SFINAE contexts. Is that intended?
>
>
> Yes, that is intended.  Changing that could mean that the meaning of code
> depends on what max depth the user selected.

that would be disturbing…

-- gaby
Gabriel Dos Reis - Aug. 9, 2013, 8:42 a.m.
On Fri, Aug 9, 2013 at 2:13 AM, Florian Weimer <fweimer@redhat.com> wrote:
> On 08/09/2013 08:43 AM, Paolo Carlini wrote:
>
>>> Yes, that is intended.  Changing that could mean that the meaning of
>>> code depends on what max depth the user selected.
>>
>>
>> Indeed. Yesterday I wondered what would happen if the front-end had a way
>> to detect, in some very specific and special cases only of course, really
>> infinite recursions, in the sense that no increase in the depth would "cure"
>> them. Would it be ok in that case to sfinae away?
>
>
> Eh, hopefully not.  Otherwise, program behavior would depend on how well the
> compiler solves the halting problem.
>
> It's interesting question, but hopefully we can make all errors due to
> exceeded implementation limits hard errors, not subject to SFINAE.

agreed.

-- Gaby
Gabriel Dos Reis - Aug. 9, 2013, 8:45 a.m.
On Fri, Aug 9, 2013 at 2:33 AM, Florian Weimer <fweimer@redhat.com> wrote:
> On 08/09/2013 09:28 AM, Paolo Carlini wrote:
>
>> I see. You know, I was trying to figure out the logic other compilers -
>> two of them, actually - are following, because the really appear to sfinae
>> away infinite recursions. Was trying to imagine cases in which it would be
>> safe.
>
>
> Could their behavior just be bugs?  Depending on their error recovery
> implementation, not flagging infinite recursion as a hard error in SFINAE
> context could be an easy mistake to make.

Indeed.  The fact we try to recreate template instantiations,
as opposed to using what is already known at the point where
we get the diagnostics is a worrisome aspect of our current
infrastructure.  That obviously manifests itself with the "error re-entered…"
stuff.

-- Gaby
Gabriel Dos Reis - Aug. 9, 2013, 8:46 a.m.
On Fri, Aug 9, 2013 at 2:13 AM, Paolo Carlini <paolo.carlini@oracle.com> wrote:
> .. another thought I had, less esoteric ;) is the following: we use tf_none
> for two rather different reasons: for SFINAE and to avoid recursive Error
> routines calls, when we call tsubst (... tf_none, ...) from
> dump_template_bindings.
>
> I understand, given your reply, that in general in the first case, thus
> SFINAE, we should avoid all hard errors *but* the one about too deep
> recursions (barring some sort of powerful infinite recursion detector). What
> about the second case, however? Should it be different? An error message is
> being produced in any case, for a reason or another, it shouldn't be
> prevented or made more difficult only because there is deep recursion
> somewhere. I think that in that second case we should suppress the error
> message about too deep recursion too. But how to tell it apart? Looks like
> we want some sort of separate tf_*, a tf_in_diagnostic, or something, very
> similar to tf_none, but truly with no exceptions. Actually this vague idea
> occured to me a number of times, I think having that would help in a number
> of situations.
>
> What do you think?
>
> Thanks,
> Paolo.
>
> (*) Maybe there is third one, like in some recent tweaks Jakub did, but
> let's leave it alone here.

I think we should find ways to have the pretty printer in the
diagnostic framework stop trying to redo most of the work
done by the type checker.  In its current form, that is fragile.

-- Gaby
Paolo Carlini - Aug. 9, 2013, 9:28 a.m.
Hi,

On 08/09/2013 10:46 AM, Gabriel Dos Reis wrote:
> I think we should find ways to have the pretty printer in the 
> diagnostic framework stop trying to redo most of the work done by the 
> type checker. In its current form, that is fragile. -- Gaby 
Yeah. That tsubst (..., tf_none, ...) from dump_template_bindings, 
always seemed a little weird to me. Fact is, we have got quite a few 
serious diagnostic bugs where we print *very little* sensible before the 
Error reporting routines re-entered message. Like 54080 and 52875, but 
I'm sure there are more.

Do you have any tips about a reasonable way to handle the latter in the 
short term?

Thanks!
Paolo.
Gabriel Dos Reis - Aug. 9, 2013, 10:17 a.m.
On Fri, Aug 9, 2013 at 4:28 AM, Paolo Carlini <paolo.carlini@oracle.com> wrote:
> Hi,
>
>
> On 08/09/2013 10:46 AM, Gabriel Dos Reis wrote:
>>
>> I think we should find ways to have the pretty printer in the diagnostic
>> framework stop trying to redo most of the work done by the type checker. In
>> its current form, that is fragile. -- Gaby
>
> Yeah. That tsubst (..., tf_none, ...) from dump_template_bindings, always
> seemed a little weird to me. Fact is, we have got quite a few serious
> diagnostic bugs where we print *very little* sensible before the Error
> reporting routines re-entered message. Like 54080 and 52875, but I'm sure
> there are more.
>
> Do you have any tips about a reasonable way to handle the latter in the
> short term?

I arrived on the west coast with little sleep, and I can't find sleep just right
now (and you may argue reading gcc list isn't a good way to find sleep
either); let
me think about it more.

-- Gaby

Patch

Index: cp-tree.h
===================================================================
--- cp-tree.h	(revision 201588)
+++ cp-tree.h	(working copy)
@@ -5541,7 +5541,7 @@  extern tree fold_non_dependent_expr_sfinae	(tree,
 extern bool alias_type_or_template_p            (tree);
 extern bool alias_template_specialization_p     (const_tree);
 extern bool explicit_class_specialization_p     (tree);
-extern int push_tinst_level                     (tree);
+extern int push_tinst_level                     (tree, tsubst_flags_t);
 extern void pop_tinst_level                     (void);
 extern struct tinst_level *outermost_tinst_level(void);
 extern void init_template_processing		(void);
Index: mangle.c
===================================================================
--- mangle.c	(revision 201588)
+++ mangle.c	(working copy)
@@ -3429,7 +3429,7 @@  mangle_decl_string (const tree decl)
     {
       struct tinst_level *tl = current_instantiation ();
       if ((!tl || tl->decl != decl)
-	  && push_tinst_level (decl))
+	  && push_tinst_level (decl, tf_warning_or_error))
 	{
 	  template_p = true;
 	  saved_fn = current_function_decl;
Index: pt.c
===================================================================
--- pt.c	(revision 201588)
+++ pt.c	(working copy)
@@ -6995,7 +6995,7 @@  add_pending_template (tree d)
   level = !current_tinst_level || current_tinst_level->decl != d;
 
   if (level)
-    push_tinst_level (d);
+    push_tinst_level (d, tf_warning_or_error);
 
   pt = ggc_alloc_pending_template ();
   pt->next = NULL;
@@ -8021,24 +8021,28 @@  static GTY(()) struct tinst_level *last_error_tins
    for diagnostics and to restore it later.  */
 
 int
-push_tinst_level (tree d)
+push_tinst_level (tree d, tsubst_flags_t complain)
 {
   struct tinst_level *new_level;
 
   if (tinst_depth >= max_tinst_depth)
     {
       last_error_tinst_level = current_tinst_level;
-      if (TREE_CODE (d) == TREE_LIST)
-	error ("template instantiation depth exceeds maximum of %d (use "
-	       "-ftemplate-depth= to increase the maximum) substituting %qS",
-	       max_tinst_depth, d);
-      else
-	error ("template instantiation depth exceeds maximum of %d (use "
-	       "-ftemplate-depth= to increase the maximum) instantiating %qD",
-	       max_tinst_depth, d);
 
-      print_instantiation_context ();
+      if (complain & tf_error)
+	{
+	  if (TREE_CODE (d) == TREE_LIST)
+	    error ("template instantiation depth exceeds maximum of %d (use "
+		   "-ftemplate-depth= to increase the maximum) "
+		   "substituting %qS", max_tinst_depth, d);
+	  else
+	    error ("template instantiation depth exceeds maximum of %d (use "
+		   "-ftemplate-depth= to increase the maximum) "
+		   "instantiating %qD", max_tinst_depth, d);
 
+	  print_instantiation_context ();
+	}
+
       return 0;
     }
 
@@ -8691,7 +8695,7 @@  instantiate_class_template_1 (tree type)
     return type;
 
   /* If we've recursively instantiated too many templates, stop.  */
-  if (! push_tinst_level (type))
+  if (! push_tinst_level (type, tf_warning_or_error))
     return type;
 
   /* Now we're really doing the instantiation.  Mark the type as in
@@ -15005,7 +15009,7 @@  instantiate_alias_template (tree tmpl, tree args,
   if (tmpl == error_mark_node || args == error_mark_node)
     return error_mark_node;
   tree tinst = build_tree_list (tmpl, args);
-  if (!push_tinst_level (tinst))
+  if (!push_tinst_level (tinst, complain))
     {
       ggc_free (tinst);
       return error_mark_node;
@@ -15219,7 +15223,7 @@  fn_type_unification (tree fn,
         }
 
       TREE_VALUE (tinst) = explicit_targs;
-      if (!push_tinst_level (tinst))
+      if (!push_tinst_level (tinst, complain))
 	{
 	  excessive_deduction_depth = true;
 	  goto fail;
@@ -15270,7 +15274,7 @@  fn_type_unification (tree fn,
      any errors (e.g. from class instantiations triggered by instantiation
      of default template arguments) come from.  If we are explaining, this
      context is redundant.  */
-  if (!explain_p && !push_tinst_level (tinst))
+  if (!explain_p && !push_tinst_level (tinst, complain))
     {
       excessive_deduction_depth = true;
       goto fail;
@@ -15327,7 +15331,7 @@  fn_type_unification (tree fn,
      substitution results in an invalid type, as described above,
      type deduction fails.  */
   TREE_VALUE (tinst) = targs;
-  if (!push_tinst_level (tinst))
+  if (!push_tinst_level (tinst, complain))
     {
       excessive_deduction_depth = true;
       goto fail;
@@ -18876,7 +18880,7 @@  maybe_instantiate_noexcept (tree fn)
 
   if (TREE_CODE (noex) == DEFERRED_NOEXCEPT)
     {
-      if (push_tinst_level (fn))
+      if (push_tinst_level (fn, tf_warning_or_error))
 	{
 	  push_access_scope (fn);
 	  push_deferring_access_checks (dk_no_deferred);
@@ -19000,7 +19004,7 @@  instantiate_decl (tree d, int defer_ok,
 	      || spec == NULL_TREE);
 
   /* This needs to happen before any tsubsting.  */
-  if (! push_tinst_level (d))
+  if (! push_tinst_level (d, tf_warning_or_error))
     return d;
 
   timevar_push (TV_TEMPLATE_INST);
Index: typeck2.c
===================================================================
--- typeck2.c	(revision 201588)
+++ typeck2.c	(working copy)
@@ -1564,7 +1564,7 @@  build_x_arrow (location_t loc, tree expr, tsubst_f
 	    return error_mark_node;
 
 	  if (fn && DECL_USE_TEMPLATE (fn))
-	    push_tinst_level (fn);
+	    push_tinst_level (fn, complain);
 	  fn = NULL;
 
 	  if (vec_member (TREE_TYPE (expr), types_memoized))