diff mbox series

C++ PATCHes for c++/82882, ICE with lambda in template default argument

Message ID CADzB+2nb=LjRMp=EfD1LxGWAJnkYNu3FGnX3-Dp1BJCdKBwpQA@mail.gmail.com
State New
Headers show
Series C++ PATCHes for c++/82882, ICE with lambda in template default argument | expand

Commit Message

Jason Merrill June 15, 2018, 8:21 p.m. UTC
This testcase was broken by the lambda rewrite.  This turned out to be
because the lambda in the template's default argument had namespace
scope, while the instantiation had function scope, because of
tsubst_default_argument calling push_access_scope and do_pushtag using
the resulting value of current_function_decl.

The fix is to teach do_pushtag to do better.  For GCC 8 I'm applying a
focused patch that just recognizes when we're trying to give a lambda
a function scope we aren't actually in.  This re-broke bug 59949, so
to avoid that I needed to fix tsubst_lambda_expr to use and update the
discriminator for global lambda scope when the template lambda has no
extra scope.

Looking at do_pushtag led me to wonder why we were relying on
current_* for the scope in the first place; since we're going to add
the tag to current_binding_level, we should get the scope from there
as well.  Making that change revealed a few more issues:

* With a lambda in a default argument, we would frequently be in the
binding_level for the calling function.  Fixed by calling
push_to_top_level.
* Redeclarations of C++11 enums with specified underlying type were
getting pushed into an arbitrary binding level.  Fixed by removing the
unnecessary permissiveness around such redeclarations with underlying
types that are not template-equivalent, so a redeclaration isn't
pushed anywhere.
* Lambdas in default member initializers were also getting pushed into
an arbitrary binding level, due to the code for allowing type
definition in a compound literal in a static data member initializer.
Fixed by no longer looking through the binding_level for a complete
class, but instead not pushing the tag anywhere if the class is
complete; a lambda type isn't found by name anyway.

Tested x86_64-pc-linux-gnu.  First patch applied to trunk and 8,
others only to trunk.

Comments

Jason Merrill June 27, 2018, 5:29 p.m. UTC | #1
On Fri, Jun 15, 2018 at 4:21 PM, Jason Merrill <jason@redhat.com> wrote:
> This testcase was broken by the lambda rewrite.  This turned out to be
> because the lambda in the template's default argument had namespace
> scope, while the instantiation had function scope, because of
> tsubst_default_argument calling push_access_scope and do_pushtag using
> the resulting value of current_function_decl.
>
> The fix is to teach do_pushtag to do better.  For GCC 8 I'm applying a
> focused patch that just recognizes when we're trying to give a lambda
> a function scope we aren't actually in.  This re-broke bug 59949, so
> to avoid that I needed to fix tsubst_lambda_expr to use and update the
> discriminator for global lambda scope when the template lambda has no
> extra scope.
>
> Looking at do_pushtag led me to wonder why we were relying on
> current_* for the scope in the first place; since we're going to add
> the tag to current_binding_level, we should get the scope from there
> as well.  Making that change revealed a few more issues:
>
> * With a lambda in a default argument, we would frequently be in the
> binding_level for the calling function.  Fixed by calling
> push_to_top_level.
> * Redeclarations of C++11 enums with specified underlying type were
> getting pushed into an arbitrary binding level.  Fixed by removing the
> unnecessary permissiveness around such redeclarations with underlying
> types that are not template-equivalent, so a redeclaration isn't
> pushed anywhere.
> * Lambdas in default member initializers were also getting pushed into
> an arbitrary binding level, due to the code for allowing type
> definition in a compound literal in a static data member initializer.
> Fixed by no longer looking through the binding_level for a complete
> class, but instead not pushing the tag anywhere if the class is
> complete; a lambda type isn't found by name anyway.

One more: looking through one class binding level then stopped at its
enclosing template parms level even if that's still inside another
class binding level.

Tested x86_64-pc-linux-gnu, applying to trunk.
commit fa24a18a557cc0f7a7cb42416b0150a4847916c6
Author: Jason Merrill <jason@redhat.com>
Date:   Wed Jun 27 12:25:07 2018 -0400

    Avoid crash on friend in nested class template.
    
            * name-lookup.c (do_pushtag): If we skip a class level, also skip
            its template level.

diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index a30c37428ad..e0500d83071 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -6509,20 +6509,30 @@ do_pushtag (tree name, tree type, tag_scope scope)
   tree decl;
 
   cp_binding_level *b = current_binding_level;
-  while (/* Cleanup scopes are not scopes from the point of view of
-	    the language.  */
-	 b->kind == sk_cleanup
-	 /* Neither are function parameter scopes.  */
-	 || b->kind == sk_function_parms
-	 /* Neither are the scopes used to hold template parameters
-	    for an explicit specialization.  For an ordinary template
-	    declaration, these scopes are not scopes from the point of
-	    view of the language.  */
-	 || (b->kind == sk_template_parms
-	     && (b->explicit_spec_p || scope == ts_global))
-	 || (b->kind == sk_class
-	     && scope != ts_current))
-    b = b->level_chain;
+  while (true)
+    {
+      if (/* Cleanup scopes are not scopes from the point of view of
+	     the language.  */
+	  b->kind == sk_cleanup
+	  /* Neither are function parameter scopes.  */
+	  || b->kind == sk_function_parms
+	  /* Neither are the scopes used to hold template parameters
+	     for an explicit specialization.  For an ordinary template
+	     declaration, these scopes are not scopes from the point of
+	     view of the language.  */
+	  || (b->kind == sk_template_parms
+	      && (b->explicit_spec_p || scope == ts_global)))
+	b = b->level_chain;
+      else if (b->kind == sk_class
+	       && scope != ts_current)
+	{
+	  b = b->level_chain;
+	  if (b->kind == sk_template_parms)
+	    b = b->level_chain;
+	}
+      else
+	break;
+    }
 
   gcc_assert (identifier_p (name));
 
diff --git a/gcc/testsuite/g++.dg/template/friend66.C b/gcc/testsuite/g++.dg/template/friend66.C
new file mode 100644
index 00000000000..f8c95f844b3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/friend66.C
@@ -0,0 +1,9 @@
+template <class T>
+struct A
+{
+  template <class U>
+  struct B
+  {
+    friend struct C;
+  };
+};
diff --git a/gcc/testsuite/g++.old-deja/g++.law/visibility13.C b/gcc/testsuite/g++.old-deja/g++.law/visibility13.C
index 025b0b1ef54..451ef1afaf8 100644
--- a/gcc/testsuite/g++.old-deja/g++.law/visibility13.C
+++ b/gcc/testsuite/g++.old-deja/g++.law/visibility13.C
@@ -15,9 +15,11 @@ using namespace std;
 
 const int ArraySize = 12;
 
+template <class> class Array_RC;
+
 template <class Type>
-class Array { // { dg-error "" } .struct Array_RC redecl.*
-friend class Array_RC;
+class Array {
+  friend class Array_RC<Type>;
 public:
     Array(const Type *ar, int sz) { init(ar,sz); }
     virtual ~Array() { delete [] ia; }
@@ -76,8 +78,8 @@ Array_RC<Type>::Array_RC(const Type *ar, int sz) : Array<Type>(ar, sz) {}
 
 template <class Type>
 Type &Array_RC<Type>::operator[](int ix) {
-    assert(ix >= 0 && ix < size);// { dg-error "" } member .size.*
-    return ia[ix];// { dg-error "" } member .ia.*
+    assert(ix >= 0 && ix < this->size);
+    return this->ia[ix];
 }
 
 //    -------------------   Test routine   ----------------------
diff mbox series

Patch

commit 190aed44d9e0fcbf89cedddcc2003b41a0899374
Author: Jason Merrill <jason@redhat.com>
Date:   Thu Jun 14 21:56:33 2018 -0400

            * pt.c (tsubst_default_argument): Use push_to/pop_from_top_level.
    
            * name-lookup.c (do_pushtag): Don't look through complete types, but
            don't add to them either.  Get context from current_binding_level.

diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index 7990029d70c..ec001016d3e 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -6521,12 +6521,7 @@  do_pushtag (tree name, tree type, tag_scope scope)
 	 || (b->kind == sk_template_parms
 	     && (b->explicit_spec_p || scope == ts_global))
 	 || (b->kind == sk_class
-	     && (scope != ts_current
-		 /* We may be defining a new type in the initializer
-		    of a static member variable. We allow this when
-		    not pedantic, and it is particularly useful for
-		    type punning via an anonymous union.  */
-		 || COMPLETE_TYPE_P (b->this_entity))))
+	     && scope != ts_current))
     b = b->level_chain;
 
   gcc_assert (identifier_p (name));
@@ -6540,15 +6535,18 @@  do_pushtag (tree name, tree type, tag_scope scope)
 
       if (! context)
 	{
-	  tree cs = current_scope ();
+	  cp_binding_level *cb = b;
+	  while (cb->kind != sk_namespace
+		 && cb->kind != sk_class
+		 && (cb->kind != sk_function_parms
+		     || !cb->this_entity))
+	    cb = cb->level_chain;
+	  tree cs = cb->this_entity;
 
-	  /* Avoid setting the lambda context to a current_function_decl that
-	     we aren't actually inside, e.g. one set by push_access_scope
-	     during tsubst_default_argument.  */
-	  if (cs && TREE_CODE (cs) == FUNCTION_DECL
-	      && LAMBDA_TYPE_P (type)
-	      && !at_function_scope_p ())
-	    cs = DECL_CONTEXT (cs);
+	  gcc_checking_assert (TREE_CODE (cs) == FUNCTION_DECL
+			       ? cs == current_function_decl
+			       : TYPE_P (cs) ? cs == current_class_type
+			       : cs == current_namespace);
 
 	  if (scope == ts_current
 	      || (cs && TREE_CODE (cs) == FUNCTION_DECL))
@@ -6587,11 +6585,11 @@  do_pushtag (tree name, tree type, tag_scope scope)
 
       if (b->kind == sk_class)
 	{
-	  if (!TYPE_BEING_DEFINED (current_class_type)
-	      && !LAMBDA_TYPE_P (type))
-	    return error_mark_node;
-
-	  if (!PROCESSING_REAL_TEMPLATE_DECL_P ())
+	  if (!TYPE_BEING_DEFINED (current_class_type))
+	    /* Don't push anywhere if the class is complete; a lambda in an
+	       NSDMI is not a member of the class.  */
+	    ;
+	  else if (!PROCESSING_REAL_TEMPLATE_DECL_P ())
 	    /* Put this TYPE_DECL on the TYPE_FIELDS list for the
 	       class.  But if it's a member template class, we want
 	       the TEMPLATE_DECL, not the TYPE_DECL, so this is done
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index ed634ddeefb..4ee84b201e9 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -12675,8 +12675,6 @@  tree
 tsubst_default_argument (tree fn, int parmnum, tree type, tree arg,
 			 tsubst_flags_t complain)
 {
-  tree saved_class_ptr = NULL_TREE;
-  tree saved_class_ref = NULL_TREE;
   int errs = errorcount + sorrycount;
 
   /* This can happen in invalid code.  */
@@ -12709,19 +12707,10 @@  tsubst_default_argument (tree fn, int parmnum, tree type, tree arg,
 
      we must be careful to do name lookup in the scope of S<T>,
      rather than in the current class.  */
+  push_to_top_level ();
   push_access_scope (fn);
-  /* The "this" pointer is not valid in a default argument.  */
-  if (cfun)
-    {
-      saved_class_ptr = current_class_ptr;
-      cp_function_chain->x_current_class_ptr = NULL_TREE;
-      saved_class_ref = current_class_ref;
-      cp_function_chain->x_current_class_ref = NULL_TREE;
-    }
-
   start_lambda_scope (parm);
 
-  push_deferring_access_checks(dk_no_deferred);
   /* The default argument expression may cause implicitly defined
      member functions to be synthesized, which will result in garbage
      collection.  We must treat this situation as if we were within
@@ -12732,17 +12721,9 @@  tsubst_default_argument (tree fn, int parmnum, tree type, tree arg,
 		     complain, NULL_TREE,
 		     /*integral_constant_expression_p=*/false);
   --function_depth;
-  pop_deferring_access_checks();
 
   finish_lambda_scope ();
 
-  /* Restore the "this" pointer.  */
-  if (cfun)
-    {
-      cp_function_chain->x_current_class_ptr = saved_class_ptr;
-      cp_function_chain->x_current_class_ref = saved_class_ref;
-    }
-
   if (errorcount+sorrycount > errs
       && (complain & tf_warning_or_error))
     inform (input_location,
@@ -12752,6 +12733,7 @@  tsubst_default_argument (tree fn, int parmnum, tree type, tree arg,
   arg = check_default_argument (type, arg, complain);
 
   pop_access_scope (fn);
+  pop_from_top_level ();
 
   if (arg != error_mark_node && !cp_unevaluated_operand)
     {
diff --git a/gcc/testsuite/g++.dg/template/crash108.C b/gcc/testsuite/g++.dg/template/crash108.C
index 9bcabc6009b..3ffadc14c25 100644
--- a/gcc/testsuite/g++.dg/template/crash108.C
+++ b/gcc/testsuite/g++.dg/template/crash108.C
@@ -1,5 +1,5 @@ 
 // PR c++/50861
 
-template<class T> struct A {A(int b=k(0));}; // { dg-error "parameter|argument" }
-void f(int k){A<int> a;} // // { dg-message "declared" }
-// { dg-message "note" "note" { target *-*-* } 3 }
+template<class T> struct A {A(int b=k(0));}; // { dg-error "not declared" }
+ // { dg-error "that depend on a template parameter" "" { target *-*-* } .-1 }
+void f(int k){A<int> a;}